Extjs基本组件:带复选框的下拉树
首先,我们对于ExtJs实现的带复选框的下拉树应该有一个简单的了解,从下面的例子中可以看出,treepanel是通过节点中checked的值(true,false)来实现复选框的两种状态(勾选,不选)的。
代码段:
Ext.create('Ext.window.Window', {
title: 'Test',
height: 230,
width: 400,
layout: 'fit',
autoShow:true,
items: {
xtype: 'treepanel',
store: Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [
{ text: 'child1', leaf: true, checked: false},
{ text: 'child2', expanded: true, checked: false, children: [
{ text: 'child2-1', expanded: true, checked: false, children:[
{ text: 'child2-1-1', leaf: true, checked: true},
{ text: 'child2-1-2', leaf: true, checked: false}
] },
{ text: 'child2-2', leaf: true, checked: true}
] },
{ text: 'child3', leaf: true, checked: false }
]
}
}),
rootVisible: false
}
});
展示图:
半选的实现原理
其实实现checkbox半选很简单,首先我们应该知道,显示在界面上的效果不过就是一张图片而已,而checked的值(true/false)就像两个开关一样,随着值得改变,由css样式去更改图片;而我们现在要做的就是在此基础上再加一个开关(indeterminate),优先级比checked高,当indeterminate = true时,由css样式去更改我们自己的半选图片,indeterminate = false时,由checked去负责复选框的选中和不选两种状态。
需要注意的一点就是,由于点击半选的复选框时,复选框需要处于不选状态中,所以当复选框处于半选状态时,checked此时应该等于true。
ok,从上面的原理可以看出我们实现半选的几个难点:
- 随着checkbox的点击,正确的改变节点中checked和indeterminate的值
- 根据checked和indeterminate的值如何修改样式
那么,该怎么解决呢?
自定义组件
组件的自定义封装是Extjs的基本功了,我们需要封装一个组件来实现我们上述的难点1
Ext.define('component.CheckTree', {
extend: 'Ext.tree.Panel',
requires: [
'Ext.data.TreeStore'
],
xtype: 'checktree',
store: {},
rootVisible: false,
useArrows: true,
frame: true,
title: 'Check Tree',
header: false,
bufferedRenderer: false,
animate: true,
minHeight: 200,
maxHeight: 200,
bodyStyle: 'overflow-x:no; overflow-y:auto',
SELSTATUS: {
NONE: 0, //没有选中
HALFSEL: 1, //半选
ALLSEL: 2 //全选
},
listeners: {
checkchange: function(node, checked) {
this.setChildChecked(node, checked);
this.setParentChecked(node, checked);
}
},
initComponent: function() {
Ext.apply(this, {
store: this.store,
});
this.callParent();
},
//遍历所有子节点,全选或全不选所有子节点,indeterminate设置为false
setChildChecked: function(node, checked) {
var me = this;
node.expand();
node.set('indeterminate', false);
node.data.checked = !checked;
node.set('checked', checked);
if (node.hasChildNodes()) {
node.eachChild(function(child) {
this.setChildChecked(child, checked);
}, me)
}
},
//遍历所有的父节点,设置复选框的三种状态
//注意:checked的set方法只有在当前值与要改变的值不同时,才回去修改样式
setParentChecked: function(node, checked, selStatus) {
var me = this,
parentNode = node.parentNode,
isHalfSel = false, //半选
isAllSel = false, //全选
isNone = true,
indeterminate,
checkedArr = [],
indexT, indexF;
if (parentNode) {
if(typeof(selStatus) == 'number'){
switch (selStatus){
case 0:
node.set('indeterminate', false);
node.data.checked = true;
node.set('checked', false);
me.setParentChecked(node, checked, null);
break;
case 1:
node.set('indeterminate', true);
node.data.checked = false;
node.set('checked', true);
me.setParentChecked(parentNode, checked, me.SELSTATUS['HALFSEL']);
break;
case 2:
node.set('indeterminate', false);
node.data.checked = false;
node.set('checked', true);
me.setParentChecked(node, checked, null);
break;
default:
break;
}
}else{
selStatus = null;
parentNode.eachChild(function(childnode) {
indeterminate = childnode.get('indeterminate');
if (typeof(indeterminate) == 'boolean' && indeterminate) {
selStatus = me.SELSTATUS['HALFSEL'];
}
if(!isHalfSel){
checkedArr.push(childnode.get('checked'));
}
});
if(!selStatus){
indexT = checkedArr.indexOf(true);
indexF = checkedArr.indexOf(false);
switch (true){
case -1 == indexT:
selStatus = me.SELSTATUS['NONE'];
break;
case -1 == indexF:
selStatus = me.SELSTATUS['ALLSEL'];
break;
default:
selStatus = me.SELSTATUS['HALFSEL'];
break;
}
}
me.setParentChecked(parentNode, checked, selStatus);
}
}
}
});
样式的添加与控制
已经来到了最后一步,黎明的曙光即将到来。
首先,我们需要明确几点:
1. 只有当node.set('checked', true/false),并且node.get('checked')的值与改变的值不同时,才会去修改样式;
2. 当复选框为不选状态的时候,checkboxCls = ‘.x-tree-checkbox’ ,当为选中的时候,checkboxCls = ‘.x-tree-checkbox .x-tree-checkbox-checked’ ;
.x-tree-checkbox-checked {
background-position: 0 -15px;
}
.x-tree-checkbox {
margin-right: 4px;
top: 5px;
width: 15px;
height: 15px;
background-image: url(images/form/checkbox.png);
}
ok,说了这么多,相信大家的思路应该比较清晰了,通过set方法进入源码,可以发现可以在Ext.tree.Column中的treeRenderer方法添加我们需要的样式,那么,我们可以重写这个方法,其中.x-tree-checkbox-indeterminate是我们实现半选的样式
代码段:
Ext.define('Ext.overrides.tree.Column', {
override: 'Ext.tree.Column',
treeRenderer: function(value, metaData, record, rowIdx, colIdx, store, view) {
var me = this,
cls = record.get('cls'),
rendererData;
// The initial render will inject the cls into the TD's attributes.
// If cls is ever *changed*, then the full rendering path is followed.
if (metaData && cls) {
metaData.tdCls += ' ' + cls;
}
rendererData = me.initTemplateRendererData(value, metaData, record, rowIdx, colIdx, store, view);
//导航树复选框的半选状态实现
var indeterminate = record.get('indeterminate');
if('boolean' === typeof(indeterminate) && indeterminate){
rendererData.checkboxCls += ' x-tree-checkbox-indeterminate';
}
return me.getTpl('cellTpl').apply(rendererData);
}
});
当然,.x-tree-checkbox-indeterminate是不存在的,是我们自己需要的,要达到我们的目的,就要让组件在渲染时,能够找到它,因此,我们可以在主题中给该组件添加这个样式,并把.x-tree-checkbox中的图片文件改成我们需要的
.x-tree-checkbox {
margin-right: 4px;
top: 5px;
width: 15px;
height: 15px;
background-image: url(images/form/checkbox1.png);
}
.x-tree-checkbox-indeterminate {
background-position: 0 -30px;
}
半选实现
代码段:
Ext.create('Ext.window.Window', {
title: 'Test',
height: 230,
width: 400,
layout: 'fit',
autoShow:true,
items: {
xtype: 'checktree',
store: Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [
{ text: 'child1', leaf: true, checked: false, indeterminate: false},
{ text: 'child2', expanded: true, checked: false, indeterminate: false, children: [
{ text: 'child2-1', expanded: true, checked: false, indeterminate: false, children:[
{ text: 'child2-1-1', leaf: true, checked: false, indeterminate: false},
{ text: 'child2-1-2', leaf: true, checked: false, indeterminate: false}
] },
{ text: 'child2-2', leaf: true, checked: false, indeterminate: false}
] },
{ text: 'child3', leaf: true, checked: false, indeterminate: false}
]
}
}),
rootVisible: false
}
});
点击后的效果: