/* Extending/depending on:
~ = modified function (when updating from SVN be sure to check these for changes, especially to Ext.tree.TreeNodeUI.render() )
+ = added function
TreeSelectionModel.js
Ext.tree.CheckNodeMultiSelectionModel : ~init(), ~onNodeClick(), +extendSelection(), ~onKeyDown()
TreeNodeUI.js
Ext.tree.CheckboxNodeUI : ~render(), +checked(), +check(), +toggleCheck()
TreePanel.js
Ext.tree.TreePanel : +getChecked()
TreeLoader.js
Ext.tree.TreeLoader : ~createNode()
*/
/**
* 原始功能只能实现选中或不选两种状态
* 但我们现在需要非叶子节点可以实现三种状态,既:
* 节点的子节点都被选中时,显示都被选中的图标
* 节点的子节点都没选中时,显示都没选中的图标
* 节点的子节点部分选中时,显示部分选中的图标
*
* 显示都被选中,和部分选中时,当前节点的状态是被选中
* 显示都没选中时,当前节点的状态是没选中
*
* 显示都被选中时,单击当前节点,触发的事件是,当前节点变成未选中状态,所有子节点变成未选中状态
* 显示都没选中时,单击当前节点,触发的事件是,当前节点变成都选中状态,所有子节点变成选中状态
* 显示部分选中时,单击当前节点,触发的事件是,当前节点变成都选中状态,所有子节点变成选中状态
*
* 子节点变成未选中状态时,对应父节点判断所有子节点是否未选中,如果都未选中,变成未选中状态,否则变成部分选中状态
* 子节点变成选中状态时,对应父节点判断所有子节点是否都选中,如果都选中,变成都选中状态,否则变成部分选中状态
*
* @return {Array} 选中节点的id数组
*/
Ext.tree.TreePanel.prototype.getChecked = function(node){
var checked = [], i;
if( typeof node == 'undefined' ) {
node = this.rootVisible ? this.getRootNode() : this.getRootNode().firstChild;
}
if( node.attributes.checked ) {
checked.push(node.id);
if( !node.isLeaf() ) {
for( i = 0; i < node.childNodes.length; i++ ) {
checked = checked.concat( this.getChecked(node.childNodes[i]) );
}
}
}
return checked;
};
/**
* @class Ext.tree.CustomUITreeLoader
* @extends Ext.tree.TreeLoader
* 重写createNode(),强制uiProvider是任意的TreeNodeUI来保存广度
*/
Ext.tree.CustomUITreeLoader = function() {
Ext.tree.CustomUITreeLoader.superclass.constructor.apply(this, arguments);
};
Ext.extend(Ext.tree.CustomUITreeLoader, Ext.tree.TreeLoader, {
createNode : function(attr){
Ext.apply(attr, this.baseAttr || {});
if(this.applyLoader !== false){
attr.loader = this;
}
// 如果uiProvider是字符串,那么要么从uiProviders数组里取一个对应的,要么解析字符串获得uiProvider
if(typeof attr.uiProvider == 'string'){
attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
}
// 返回的时候,如果是叶子,就返回普通TreeNode,如果不是叶子,就返回异步读取树
return(attr.leaf ? new Ext.tree.TreeNode(attr) : new Ext.tree.AsyncTreeNode(attr));
}
});
/**
* @class Ext.tree.CheckboxNodeUI
* @extends Ext.tree.TreeNodeUI
* 给所有节点添加checkbox
*/
Ext.tree.CheckboxNodeUI = function() {
Ext.tree.CheckboxNodeUI.superclass.constructor.apply(this, arguments);
};
Ext.extend(Ext.tree.CheckboxNodeUI, Ext.tree.TreeNodeUI, {
/**
* 重写renderElements
*/
renderElements : function(n, a, targetNode, bulkRender){
// add some indent caching, this helps performance when rendering a large tree
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
var cb = typeof a.checked == 'boolean';
cb = true;
var href = a.href ? a.href : Ext.isGecko ? "" : "#";
/*
var buf = ['
',
'
',this.indentMarkup,"",
'
',
'
',
cb ? ('
' : '/>')) : '',
'
',n.text," ",
'',
""].join('');
*/
// modification,添加checkbox
var buf = ['',
'
',this.indentMarkup,"",
'
',
'
',
'
',
'
',n.text," ",
'',
""].join('');
var nel;
if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
}else{
this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
}
this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
var index = 3;
if(cb){
this.checkbox = cs[3]; // modification,添加checkbox
this.checkboxImg = cs[3]; // 修改,添加仿造checkbox的图片
index++;
//Ext.log(this.checkbox);
//Ext.log(index);
// modification: 添加handlers,避免修改Ext.tree.TreeNodeUI
Ext.fly(this.checkbox).on('click', this.check.createDelegate(this, [null]));
n.on('dblclick', function(e) {
if (this.isLeaf()) {
this.getUI().toggleCheck();
}
});
}
this.anchor = cs[index];
this.textNode = cs[index].firstChild;
}
// 这个节点是否被选中了
, checked : function() {
// return this.checkbox.checked;
return this.checkboxImg.className != "x-tree-node-checkbox-none";
},
// flag可能是:(NULL)
// 以当前节点状态为准判断,ALL,SOME -> NONE -> ALL,SOME
// 否则按照设置的flag为准:SOME,ALL,NONE
check : function(forParent, forChildren) {
var flag = null;
var cls = this.checkboxImg.className;
flag = (cls == "x-tree-node-checkbox-none") ? "x-tree-node-checkbox-all" : "x-tree-node-checkbox-none";
/*
if (this.node.isLeaf) {
flag = (cls == "x-tree-node-checkbox-all") ? "x-tree-node-checkbox-none" : "x-tree-node-checkbox-all";
} else {
if (cls == 'x-tree-node-checkbox-none') {
// 全部选中
flag = 'x-tree-node-checkbox-all';
} else {
// 全部反选
flag = 'x-tree-node-checkbox-none';
}
}
*/
if (typeof forParent == "undefined" || forParent == null) {
forParent = (typeof this.node.parentNode != "undefined") && (this.node.parentNode != null);
}
if (typeof forChildren == "undefined" || forChildren == null) {
forChildren = !this.node.ifLeaf;
}
//Ext.log(this);
//Ext.log(flag + "," + forParent + "," + forChildren);
var n = this.node;
var tree = n.getOwnerTree();
var parentNode = n.parentNode;
// 如果下级节点都尚未渲染过,就展开当前节点,并渲染下面的所有节点
if (!n.isLeaf && !n.expanded && !n.childrenRendered) {
n.expand(false, false, this.check.createDelegate(this, [forParent, forChildren]));
return;
}
// 如果包含子节点
if (forChildren && !n.isLeaf) {
var cs = n.childNodes;
for(var i = 0; i < cs.length; i++) {
cs[i].getUI().checkChild(flag == "x-tree-node-checkbox-all");
}
}
//this.checkboxImg.className = "x-tree-node-checkbox-" + this.isAllChildrenChecked();
this.checkboxImg.className = flag;
if (this.checkboxImg.className == "x-tree-node-checkbox-none") {
this.node.attributes.checked = false;
} else {
this.node.attributes.checked = true;
}
if (parentNode.getUI().checkParent) {
parentNode.getUI().checkParent();
}
}
, checkParent : function() {
this.checkboxImg.className = "x-tree-node-checkbox-" + this.isAllChildrenChecked();
if (this.checkboxImg.className == "x-tree-node-checkbox-none") {
this.node.attributes.checked = false;
} else {
this.node.attributes.checked = true;
}
if (this.node.parentNode.getUI().checkParent) {
this.node.parentNode.getUI().checkParent();
}
}
, checkChild : function(flag) {
this.node.attributes.checked = flag;
if (!node.isLeaf) {
node.getUI().checkChild(flag);
}
}
, onDblClick : function(e){
e.preventDefault();
if(this.disabled){
return;
}
if(this.checkbox){
this.toggleCheck();
}
//if(!this.animating && this.node.hasChildNodes()){
// this.node.toggle();
//}
this.fireEvent("dblclick", this.node, e);
}
, toggleCheck : function(state) {
this.check();
}
// 是否为叶子节点
, isLeaf : function() {
return this.node.attributes.leaf;
}
// 获得子节点
, getChildren : function() {
return this.node.attributes.children;
}
// 是否为选中状态
, isChecked : function() {
return this.node.attributes.checked;
}
// 获得className
, getClassName : function() {
return this.checkboxImg.className;
}
// 初始化时,决定显示的图片
, isAllChildrenChecked : function() {
if (!this.isLeaf()) {
try{
var status = this.alreadyCheckAllChildren(this.getChildren(), this.isChecked());
return 'x-tree-node-checkbox-' + status;
} catch(e){
console.debug(e);
}
} else {
return this.isChecked() ? "x-tree-node-checkbox-all" : "x-tree-node-checkbox-none";
}
}
// 计算点击后,应该显示的图片
, getNextStatus : function() {
return this.getClassName() == 'x-tree-node-checkbox-none' ?
'x-tree-node-checkbox-all' :
'x-tree-node-checkbox-none';
/*
if (this.isLeaf()) {
return this.getClassName() == "x-tree-node-checkbox-all" ? "x-tree-node-checkbox-none" : "x-tree-node-checkbox-all";
} else {
if (this.getClassName() == "x-tree-node-checkbox-none") {
return "x-tree-node-checkbox-all";
} else {
return "x-tree-node-checkbox-none";
}
if (this.getClassName() == "x-tree-node-checkbox-all") {
return "x-tree-node-checkbox-none";
} else if (this.getClassName() == "x-tree-node-checkbox-none") {
return "x-tree-node-checkbox-some";
} else {
return "x-tree-node-checkbox-all";
}
}
*/
}
, alreadyCheckAllChildren : function(children, isChecked) {
//console.error(children);
//console.debug(isChecked);
var all = 0;
var some = 0;
if (children) {
for (var i = 0; i < children.length; i++) {
var c = children[i];
if (c.leaf) {
if (c.checked) {
all++;
}
} else {
if (this.alreadyCheckAllChildren(c.children, c.checked) == 'all') {
all++;
} else if (this.alreadyCheckAllChildren(c.children, c.checked) == 'some') {
some++;
}
}
}
if (all == children.length) {
return 'all'
} if (isChecked || (all + some > 0)) {
return 'some';
} else {
return 'none';
}
} else {
if (isChecked) {
return 'all';
} else {
return 'none';
}
}
}
// 改变父节点的className
, changeParentStatus : function() {
var status = this.alreadyCheckAllChildren(this.getChildren());
return 'x-tree-node-checkbox-' + status;
}
// 处理点击节点事件
, check : function() {
this.checkboxImg.className = this.getNextStatus();
this.node.attributes.checked = "x-tree-node-checkbox-none" != this.getClassName();
if (this.node.parentNode.getUI().checkParent) {
this.node.parentNode.getUI().checkParent();
}
if (!this.isLeaf()) {
this.node.expand(false, false);
var children = this.node.childNodes;
for (var i = 0; i < children.length; i++) {
children[i].getUI().checkChild("x-tree-node-checkbox-all" == this.getClassName());
}
}
}
// 点击节点后,事件传播到父节点
// 向父节点传播的事件可能是all, none, some
, checkParent : function() {
this.checkboxImg.className = this.changeParentStatus();
this.node.attributes.checked = "x-tree-node-checkbox-none" != this.getClassName();
if (this.node.parentNode.getUI().checkParent) {
this.node.parentNode.getUI().checkParent();
}
}
// 点击节点后,事件传播到子节点
// 向子节点传播的事件,只有all和none两种
, checkChild : function(isCheck) {
if (this.isLeaf()) {
this.checkboxImg.className = isCheck ? "x-tree-node-checkbox-all" : "x-tree-node-checkbox-none";
this.node.attributes.checked = "x-tree-node-checkbox-none" != this.checkboxImg.className;
} else {
if (isCheck) {
// 只当下面节点没选中的时候,才强制改变成some
// 否则保持some或all状态
if (this.checkboxImg.className == "x-tree-node-checkbox-none") {
this.checkboxImg.className = "x-tree-node-checkbox-all";
}
this.node.attributes.checked = true;
} else {
this.checkboxImg.className = "x-tree-node-checkbox-none";
this.node.attributes.checked = false;
this.node.expand(false, false);
}
var children = this.node.childNodes;
for (var i = 0; i < children.length; i++) {
children[i].getUI().checkChild("x-tree-node-checkbox-all" == this.getClassName());
}
}
}
});
/**
* @class Ext.tree.CheckNodeMultiSelectionModel
* @extends Ext.tree.MultiSelectionModel
* Multi selection for a TreePanel containing Ext.tree.CheckboxNodeUI.
* Adds enhanced selection routines for selecting multiple items
* and key processing to check/clear checkboxes.
*/
Ext.tree.CheckNodeMultiSelectionModel = function(){
Ext.tree.CheckNodeMultiSelectionModel.superclass.constructor.call(this);
};
Ext.extend(Ext.tree.CheckNodeMultiSelectionModel, Ext.tree.MultiSelectionModel, {
init : function(tree){
this.tree = tree;
tree.el.on("keydown", this.onKeyDown, this);
tree.on("click", this.onNodeClick, this);
},
/**
* Handle a node click
* If ctrl key is down and node is selected will unselect the node.
* If the shift key is down it will create a contiguous selection
* (see {@link Ext.tree.CheckNodeMultiSelectionModel#extendSelection} for the limitations)
*/
onNodeClick : function(node, e){
if( e.shiftKey && this.extendSelection(node) ) {
return true;
}
if( e.ctrlKey && this.isSelected(node) ) {
this.unselect(node);
} else {
this.select(node, e, e.ctrlKey);
}
},
/**
* Selects all nodes between the previously selected node and the one that the user has just selected.
* Will not span multiple depths, so only children of the same parent will be selected.
*/
extendSelection : function(node) {
var last = this.lastSelNode;
if( node == last || !last ) {
return false; /* same selection, process normally normally */
}
if( node.parentNode == last.parentNode ) {
var cs = node.parentNode.childNodes;
var i = 0, attr='id', selecting=false, lastSelect=false;
this.clearSelections(true);
for( i = 0; i < cs.length; i++ ) {
// We have to traverse the entire tree b/c don't know of a way to find
// a numerical representation of a nodes position in a tree.
if( cs[i].attributes[attr] == last.attributes[attr] || cs[i].attributes[attr] == node.attributes[attr] ) {
// lastSelect ensures that we select the final node in the list
lastSelect = selecting;
selecting = !selecting;
}
if( selecting || lastSelect ) {
this.select(cs[i], null, true);
// if we're selecting the last node break to avoid traversing the entire tree
if( lastSelect ) {
break;
}
}
}
return true;
} else {
return false;
}
}
});