413 lines
12 KiB
JavaScript
413 lines
12 KiB
JavaScript
YAHOO.namespace('ext.data');
|
|
|
|
/**
|
|
* @class YAHOO.ext.data.Tree
|
|
* @extends YAHOO.ext.util.Observable
|
|
* The class represents a tree data structure and bubbles all the events for it's nodes. The nodes
|
|
* in the tree have most standard DOM functionality.
|
|
* @constructor
|
|
* @param {Node} root (optional) The root node
|
|
*/
|
|
YAHOO.ext.data.Tree = function(root){
|
|
this.nodeHash = {};
|
|
this.root = null;
|
|
if(root){
|
|
this.setRootNode(root);
|
|
}
|
|
this.events = {
|
|
'append' : true,
|
|
'remove' : true,
|
|
'move' : true,
|
|
'insert' : true,
|
|
'beforeappend' : true,
|
|
'beforeremove' : true,
|
|
'beforemove' : true,
|
|
'beforeinsert' : true
|
|
};
|
|
};
|
|
|
|
YAHOO.extendX(YAHOO.ext.data.Tree, YAHOO.ext.util.Observable, {
|
|
pathSeparator: '/',
|
|
|
|
getRootNode : function(){
|
|
return this.root;
|
|
},
|
|
|
|
setRootNode : function(node){
|
|
this.root = node;
|
|
node.ownerTree = this;
|
|
node.isRoot = true;
|
|
return node;
|
|
},
|
|
|
|
getNodeById : function(id){
|
|
return this.nodeHash[id];
|
|
},
|
|
|
|
registerNode : function(node){
|
|
this.nodeHash[node.id] = node;
|
|
},
|
|
|
|
unregisterNode : function(node){
|
|
delete this.nodeHash[node.id];
|
|
},
|
|
|
|
toString : function(){
|
|
return '[Tree'+(this.id?' '+this.id:'')+']';
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @class YAHOO.ext.tree.Node
|
|
* @extends YAHOO.ext.util.Observable
|
|
* @cfg {String} text The text for this node
|
|
* @cfg {String} id The id for this node
|
|
* @constructor
|
|
* @param {Object} attributes The attributes/config for the node
|
|
*/
|
|
YAHOO.ext.data.Node = function(attributes){
|
|
this.attributes = attributes || {};
|
|
this.leaf = this.attributes.leaf;
|
|
this.id = this.attributes.id;
|
|
if(!this.id){
|
|
this.id = YAHOO.util.Dom.generateId(null, 'ynode-');
|
|
this.attributes.id = this.id;
|
|
}
|
|
|
|
this.childNodes = [];
|
|
if(!this.childNodes.indexOf){ // indexOf is a must
|
|
this.childNodes.indexOf = function(o){
|
|
for(var i = 0, len = this.length; i < len; i++){
|
|
if(this[i] == o) return i;
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
this.parentNode = null;
|
|
this.firstChild = null;
|
|
this.lastChild = null;
|
|
this.previousSibling = null;
|
|
this.nextSibling = null;
|
|
|
|
this.events = {
|
|
'append' : true,
|
|
'remove' : true,
|
|
'move' : true,
|
|
'insert' : true,
|
|
'beforeappend' : true,
|
|
'beforeremove' : true,
|
|
'beforemove' : true,
|
|
'beforeinsert' : true
|
|
};
|
|
};
|
|
|
|
YAHOO.extendX(YAHOO.ext.data.Node, YAHOO.ext.util.Observable, {
|
|
fireEvent : function(evtName){
|
|
// first do standard event for this node
|
|
if(YAHOO.ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
|
|
return false;
|
|
}
|
|
// then bubble it up to the tree if the event wasn't cancelled
|
|
if(this.ownerTree){
|
|
if(this.ownerTree.fireEvent.apply(this.ownerTree, arguments) === false){
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
isLeaf : function(){
|
|
return this.leaf === true;
|
|
},
|
|
|
|
setFirstChild : function(node){
|
|
this.firstChild = node;
|
|
},
|
|
|
|
setLastChild : function(node){
|
|
this.lastChild = node;
|
|
},
|
|
|
|
isLast : function(){
|
|
return (!this.parentNode ? true : this.parentNode.lastChild == this);
|
|
},
|
|
|
|
isFirst : function(){
|
|
return (!this.parentNode ? true : this.parentNode.firstChild == this);
|
|
},
|
|
|
|
hasChildNodes : function(){
|
|
return !this.isLeaf() && this.childNodes.length > 0;
|
|
},
|
|
|
|
appendChild : function(node){
|
|
var multi = false;
|
|
if(node instanceof Array){
|
|
multi = node;
|
|
}else if(arguments.length > 1){
|
|
multi = arguments;
|
|
}
|
|
// if passed an array or multiple args do them one by one
|
|
if(multi){
|
|
for(var i = 0, len = multi.length; i < len; i++) {
|
|
this.appendChild(multi[i]);
|
|
}
|
|
}else{
|
|
if(this.fireEvent('beforeappend', this.ownerTree, this, node) === false){
|
|
return false;
|
|
}
|
|
var index = this.childNodes.length;
|
|
var oldParent = node.parentNode;
|
|
// it's a move, make sure we move it cleanly
|
|
if(oldParent){
|
|
if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index) === false){
|
|
return false;
|
|
}
|
|
oldParent.removeChild(node);
|
|
}
|
|
var index = this.childNodes.length;
|
|
if(index == 0){
|
|
this.setFirstChild(node);
|
|
}
|
|
this.childNodes.push(node);
|
|
node.parentNode = this;
|
|
var ps = this.childNodes[index-1];
|
|
if(ps){
|
|
node.previousSibling = ps;
|
|
ps.nextSibling = node;
|
|
}
|
|
this.setLastChild(node);
|
|
node.setOwnerTree(this.getOwnerTree());
|
|
this.fireEvent('append', this.ownerTree, this, node, index);
|
|
if(oldParent){
|
|
node.fireEvent('move', this.ownerTree, node, oldParent, this, index);
|
|
}
|
|
return node;
|
|
}
|
|
},
|
|
|
|
removeChild : function(node){
|
|
var index = this.childNodes.indexOf(node);
|
|
if(index == -1){
|
|
return false;
|
|
}
|
|
if(this.fireEvent('beforeremove', this.ownerTree, this, node) === false){
|
|
return false;
|
|
}
|
|
|
|
// remove it from childNodes collection
|
|
this.childNodes.splice(index, 1);
|
|
|
|
// update siblings
|
|
if(node.previousSibling){
|
|
node.previousSibling.nextSibling = node.nextSibling;
|
|
}
|
|
if(node.nextSibling){
|
|
node.nextSibling.previousSibling = node.previousSibling;
|
|
}
|
|
|
|
// update child refs
|
|
if(this.firstChild == node){
|
|
this.setFirstChild(node.nextSibling);
|
|
}
|
|
if(this.lastChild == node){
|
|
this.setLastChild(node.previousSibling);
|
|
}
|
|
|
|
node.setOwnerTree(null);
|
|
// clear any references from the node
|
|
node.parentNode = null;
|
|
node.previousSibling = null;
|
|
node.nextSibling = null;
|
|
this.fireEvent('remove', this.ownerTree, this, node);
|
|
return node;
|
|
},
|
|
|
|
insertBefore : function(node, refNode){
|
|
if(!refNode){ // like standard Dom, refNode can be null for append
|
|
return this.appendChild(node);
|
|
}
|
|
// nothing to do
|
|
if(node == refNode){
|
|
return false;
|
|
}
|
|
|
|
if(this.fireEvent('beforeinsert', this.ownerTree, this, node, refNode) === false){
|
|
return false;
|
|
}
|
|
var index = this.childNodes.indexOf(refNode);
|
|
var oldParent = node.parentNode;
|
|
var refIndex = index;
|
|
|
|
// when moving internally, indexes will change after remove
|
|
if(oldParent == this && this.childNodes.indexOf(node) < index){
|
|
refIndex--;
|
|
}
|
|
|
|
// it's a move, make sure we move it cleanly
|
|
if(oldParent){
|
|
if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
|
|
return false;
|
|
}
|
|
oldParent.removeChild(node);
|
|
}
|
|
if(refIndex == 0){
|
|
this.setFirstChild(node);
|
|
}
|
|
this.childNodes.splice(refIndex, 0, node);
|
|
node.parentNode = this;
|
|
var ps = this.childNodes[refIndex-1];
|
|
if(ps){
|
|
node.previousSibling = ps;
|
|
ps.nextSibling = node;
|
|
}
|
|
node.nextSibling = refNode;
|
|
node.setOwnerTree(this.getOwnerTree());
|
|
this.fireEvent('insert', this.ownerTree, this, node, refNode);
|
|
if(oldParent){
|
|
node.fireEvent('move', this.ownerTree, node, oldParent, this, refIndex, refNode);
|
|
}
|
|
return node;
|
|
},
|
|
|
|
item : function(index){
|
|
return this.childNodes[index];
|
|
},
|
|
|
|
replaceChild : function(newChild, oldChild){
|
|
this.insertBefore(newChild, oldChild);
|
|
this.removeChild(oldChild);
|
|
return oldChild;
|
|
},
|
|
|
|
indexOf : function(child){
|
|
return this.childNodes.indexOf(child);
|
|
},
|
|
|
|
getOwnerTree : function(){
|
|
// if it doesn't have one, look for one
|
|
if(!this.ownerTree){
|
|
var p = this;
|
|
while(p){
|
|
if(p.ownerTree){
|
|
this.ownerTree = p.ownerTree;
|
|
break;
|
|
}
|
|
p = p.parentNode;
|
|
}
|
|
}
|
|
return this.ownerTree;
|
|
},
|
|
|
|
setOwnerTree : function(tree){
|
|
// if it's move, we need to update everyone
|
|
if(tree != this.ownerTree){
|
|
if(this.ownerTree){
|
|
this.ownerTree.unregisterNode(this);
|
|
}
|
|
this.ownerTree = tree;
|
|
var cs = this.childNodes;
|
|
for(var i = 0, len = cs.length; i < len; i++) {
|
|
cs[i].setOwnerTree(tree);
|
|
}
|
|
if(tree){
|
|
tree.registerNode(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
getPath : function(attr){
|
|
attr = attr || 'id';
|
|
var p = this.parentNode;
|
|
var b = [this.attributes[attr]];
|
|
while(p){
|
|
b.unshift(p.attributes[attr]);
|
|
p = p.parentNode;
|
|
}
|
|
var sep = this.getOwnerTree().pathSeparator;
|
|
return sep + b.join(sep);
|
|
},
|
|
|
|
bubble : function(fn, scope, args){
|
|
var p = this;
|
|
while(p){
|
|
if(fn.call(scope || p, args || p) === false){
|
|
break;
|
|
}
|
|
p = p.parentNode;
|
|
}
|
|
},
|
|
|
|
cascade : function(fn, scope, args){
|
|
if(fn.call(scope || this, args || this) !== false){
|
|
var cs = this.childNodes;
|
|
for(var i = 0, len = cs.length; i < len; i++) {
|
|
cs[i].cascade(fn, scope, args);
|
|
}
|
|
}
|
|
},
|
|
|
|
eachChild : function(fn, scope, args){
|
|
var cs = this.childNodes;
|
|
for(var i = 0, len = cs.length; i < len; i++) {
|
|
if(fn.call(scope || this, args || cs[i]) === false){
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
findChild : function(attribute, value){
|
|
var cs = this.childNodes;
|
|
for(var i = 0, len = cs.length; i < len; i++) {
|
|
if(cs[i].attributes[attribute] == value){
|
|
return cs[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Sorts this nodes children using the supplied sort function
|
|
* @param {Function} fn
|
|
* @param {Object} scope
|
|
*/
|
|
sort : function(fn, scope){
|
|
var cs = this.childNodes;
|
|
var len = cs.length;
|
|
if(len > 0){
|
|
var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
|
|
cs.sort(sortFn);
|
|
for(var i = 0; i < len; i++){
|
|
var n = cs[i];
|
|
n.previousSibling = cs[i-1];
|
|
n.nextSibling = cs[i+1];
|
|
if(i == 0){
|
|
this.setFirstChild(n);
|
|
}
|
|
if(i == len-1){
|
|
this.setLastChild(n);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
contains : function(node){
|
|
return node.isAncestor(this);
|
|
},
|
|
|
|
isAncestor : function(node){
|
|
var p = this.parentNode;
|
|
while(p){
|
|
if(p == node){
|
|
return true;
|
|
}
|
|
p = p.parentNode;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
toString : function(){
|
|
return '[Node'+(this.id?' '+this.id:'')+']';
|
|
}
|
|
});
|