Yii的CTreeView树形图组件是对jQuery插件treeview的封装,所以,要了解该组件的细节,需要直接去查阅jquery-plug-treeview 。从该作者的提示来看,这个插件的开发有点处于停滞状态,不过对于一般的使用,该插件仍然适用。一开始也考虑了zTree插件(也有人进行了简单的Yii封装),但因为现成的CTreeView能满足需求,并且框架本身的组件在兼容性上有保证,所以还是采用了它。
撇开细节,从数据提供者角度来说,生成CTreeView的方式有两种:静态的(设置data属性)或动态的(Ajax,设置url)
每个节点格式为 $node = array('text' =>显示文本, 'id'=>动态加载必须此id, 'hasChildren'=>动态加载必须, 'children'=>非叶子节点需要),节点描述是一个array,父节点由子节点数组构成,所以是array(array(..),...),前面的'children'对应的值就是这样。
静态生成相对简单,只需要在view中写死代码(但需要注意层次)。
<?php
$this->widget('CTreeView', array(
'animated' => 'normal',
'data' => array(
array(
'text' => '<span>标题A</span>',
'children' => array(array(
'text' => '<a href="http://127.0.0.1/index.php?r=site/index'或
'"'.$this->createUrl('/site/index').'">标题B</a>',
'text' => '<a href="http://127.0.0.1/index.php?r=site/index'或
'"'.$this->createUrl('/site/index').'">标题BB</a>',
), ),
),
array(
'expanded' => false,
'text' => '<span>标题C</span>',
'children' => array(
array('text' => '标题D'),
array('text' => '标题DD', ),
),
),
),
));
?>
静态生成基本上当导航使用,和直接编辑一个没有特别的优势。
动态生成网上的例子基本上是来源于Yii的论坛的例子,大致做法如下:
先创建一个描述节点父子关系的表
CREATE TABLE `menu` ( `id` int(11) NOT NULL AUTO_INCREMENT, `id_parent` int(11) DEFAULT NULL, `title` varchar(25) COLLATE utf8_unicode_ci NOT NULL, `position` int(2) NOT NULL, `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `icon` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), KEY `id_parent` (`id_parent`) ) ENGINE=InnoDB
menu外键约束到自身:id_parent references menu(id)
在view视图中写入:(注意url属性指向 ajaxFillTree,表示数据由该控制器方法提供,代码可以用cookie持久化,通过缓存可以提高性能)
<?php $this->widget( 'CTreeView', array( 'animated'=>'fast', //quick animation 'collapsed' => true, 'url' => array('/backOffice/sideMenu/ajaxFillTree'), 'htmlOptions'=>array( 'class'=>'treeview-famfamfam', ) ) ); ?>
控制器中添加Ajax特性的方法ajaxFillTree:
public function actionAjaxFillTree(){ if (!Yii::app()->request->isAjaxRequest) { exit(); } $parentId = "NULL"; if (isset($_GET['root']) && $_GET['root'] !== 'source') { $parentId = (int) $_GET['root']; } $sql = "SELECT m1.id, m1.title AS text, m2.id IS NOT NULL AS hasChildren,m1.url " . "FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.id=m2.id_parent " . "WHERE m1.id_parent <=> $parentId " . "GROUP BY m1.id ORDER BY m1.position ASC"; $req = Yii::app()->db->createCommand($sql); $children = $req->queryAll(); $children = $this->createLinks($children); echo str_replace( '"hasChildren":"0"', '"hasChildren":false', CTreeView::saveDataAsJson($children) ); exit(); } private function createLinks($children){ $child = array(); $return = array(); foreach($children AS $key=>$value){ $child['id']=$value['id']; $child['text']=$value['text']; $child['hasChildren']=$value['hasChildren']; if(strlen($value['url'])>0){ $child['text'] = $this->format($value['text'],$value['url'],Yii::app()->request->url); } $return[] = $child; $child = array(); } return $return; } private function format($text, $url,$icon = NULL) { $img = ''; if(isset($icon)) $img = '<img src="'.app()->theme->baseUrl.'/images/icons/'.$icon.'">'; return sprintf('<span>%s</span>',CHtml::link(($img.' '.$text), app()->createUrl($url))); }
但在我的使用场合中,本身表中包含了编码字段,编码是分级的,但存放于同一表格,而且似乎SQL Server不支持安全比较<=>(我猜安全比较是带null的比较),所以,我们将顶级节点id命名为'0'而非null,同时用模型方法来生成数据。
在视图中,我们用:
<?php
$this->widget('CTreeView', array(
'persist' => 'cookie', 'cookieId' => 'treeview-zsy001',
'animated' => 'fast',
'url' => array('ajaxFillTree'),
'htmlOptions' => array('id' => 'treeview-zsy001', 'class' => 'treeview-zsy001')
));
?>
在控制器中:
public function actionAjaxFillTree()
{
if (!Yii::app()->request->isAjaxRequest) {
Yii::app()->end();
}
$data = Yzsszd::buildCTreeViewData($this->createUrl('admin'), 'tcode');
$parentId = (isset($_GET['root']) && $_GET['root'] != 'source') ? $_GET['root'] : '0';
echo CTreeView::saveDataAsJson(array_values($data[$parentId]));
Yii::app()->end();
}
在模型中创建方法来构造CTreeView所需的数据:
//构建CTreeView用的父子关系表 [父码]=array([子码]=>array子码描述)
//用法:CTreeView::saveDataAsJson(array_values($data[$parentId]))
public static function buildCTreeViewData($orgUrl, $key, $op='&', $showCodeInName = true)
{
$data = array();
$models = Yzsszd::model()->findAll(array('order'=>'bmdiccode asc'));
foreach($models as $model){
$url = $orgUrl.$op.$key.'='.$model->bmdiccode;
//$url = 'javascript:void(0);';
$text = ($showCodeInName ? $model->bmdiccode : '')."\t".$model->mcdicname;
$options = array('href'=>"$url", 'id'=>'treenode-'.$model->bmdiccode, 'class'=>'treenode');
if($model->point == 1){
$data['0'][$model->bmdiccode] = array('id'=>$model->bmdiccode,
'text'=> CHtml::openTag('a', $options).$text.CHtml::closeTag('a'));
}else{
$data[$model->getParentCode(2)][$model->parentCode]['hasChildren'] = true;
$data[$model->parentCode][$model->bmdiccode] = array('id'=>$model->bmdiccode,
'text'=> CHtml::openTag('a', $options).$text.CHtml::closeTag('a'));
}
}
return $data;
}
在本质上,动态加载是一开始加载顶级下的节点,然后展开某个节点时,动态加载该节点的子节点,点击展开某个节点时,会向url指向的方法GET方式传递$_GET['root'],对于顶级来说,该值固定为'source',对于被点击的节点,该值为对应节点的id 。每次加载的数据,就是 子节点列表 array(array(...)...),点击节点后,就把一个array(array(...)...)嵌套到前一个结构,所以,动态加载的过程就是分步生成静态加载时的树形数据的过程。
但在我们的例子中,结果绑定到节点的链接会导致页面跳转,并且尝试把链接修改成CHtml::ajaxLink无效。后来查阅了以下帖子:http://blog.csdn.net/qqamoon/article/details/6315216
才知道动态生成的元素会无法响应一般方法定义的用户事件,然后知道用
$(document).on('click', '元素',function () { alert(this.hash); }); 或者 bind()函数可以解决该问题
所以,在视图添加javascript注册:
<?php
Yii::app()->clientScript->registerScript('**注册名***', "
$(document).on('click', 'a.treenode', function(){
alert($(this).attr('href'));
jQuery('#zsy001zj1-grid').yiiGridView('update', {
url : $(this).attr('href')
});
return false;
});
");
?>
后来发现,Yii框架生成的Javascript基本上使用了上述方式,例如,CGridView对应的Javascript代码