4 * Copyright(c) 2006-2007, Ext JS, LLC.
6 * Originally Released Under LGPL - original licence link has changed is not relivant.
9 * <script type="text/javascript">
14 * @class Roo.data.Tree
15 * @extends Roo.util.Observable
16 * Represents a tree data structure and bubbles all the events for its nodes. The nodes
17 * in the tree have most standard DOM functionality.
19 * @param {Node} root (optional) The root node
21 Roo.data.Tree = function(root){
24 * The root node for this tree
29 this.setRootNode(root);
34 * Fires when a new child node is appended to a node in this tree.
35 * @param {Tree} tree The owner tree
36 * @param {Node} parent The parent node
37 * @param {Node} node The newly appended node
38 * @param {Number} index The index of the newly appended node
43 * Fires when a child node is removed from a node in this tree.
44 * @param {Tree} tree The owner tree
45 * @param {Node} parent The parent node
46 * @param {Node} node The child node removed
51 * Fires when a node is moved to a new location in the tree
52 * @param {Tree} tree The owner tree
53 * @param {Node} node The node moved
54 * @param {Node} oldParent The old parent of this node
55 * @param {Node} newParent The new parent of this node
56 * @param {Number} index The index it was moved to
61 * Fires when a new child node is inserted in a node in this tree.
62 * @param {Tree} tree The owner tree
63 * @param {Node} parent The parent node
64 * @param {Node} node The child node inserted
65 * @param {Node} refNode The child node the node was inserted before
70 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
71 * @param {Tree} tree The owner tree
72 * @param {Node} parent The parent node
73 * @param {Node} node The child node to be appended
75 "beforeappend" : true,
78 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
79 * @param {Tree} tree The owner tree
80 * @param {Node} parent The parent node
81 * @param {Node} node The child node to be removed
83 "beforeremove" : true,
86 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
87 * @param {Tree} tree The owner tree
88 * @param {Node} node The node being moved
89 * @param {Node} oldParent The parent of the node
90 * @param {Node} newParent The new parent the node is moving to
91 * @param {Number} index The index it is being moved to
96 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
97 * @param {Tree} tree The owner tree
98 * @param {Node} parent The parent node
99 * @param {Node} node The child node to be inserted
100 * @param {Node} refNode The child node the node is being inserted before
102 "beforeinsert" : true
105 Roo.data.Tree.superclass.constructor.call(this);
108 Roo.extend(Roo.data.Tree, Roo.util.Observable, {
111 proxyNodeEvent : function(){
112 return this.fireEvent.apply(this, arguments);
116 * Returns the root node for this tree.
119 getRootNode : function(){
124 * Sets the root node for this tree.
128 setRootNode : function(node){
130 node.ownerTree = this;
132 this.registerNode(node);
137 * Gets a node in this tree by its id.
141 getNodeById : function(id){
142 return this.nodeHash[id];
145 registerNode : function(node){
146 this.nodeHash[node.id] = node;
149 unregisterNode : function(node){
150 delete this.nodeHash[node.id];
153 toString : function(){
154 return "[Tree"+(this.id?" "+this.id:"")+"]";
159 * @class Roo.data.Node
160 * @extends Roo.util.Observable
161 * @cfg {Boolean} leaf true if this node is a leaf and does not have children
162 * @cfg {String} id The id for this node. If one is not specified, one is generated.
164 * @param {Object} attributes The attributes/config for the node
166 Roo.data.Node = function(attributes){
168 * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
171 this.attributes = attributes || {};
172 this.leaf = this.attributes.leaf;
174 * The node id. @type String
176 this.id = this.attributes.id;
178 this.id = Roo.id(null, "ynode-");
179 this.attributes.id = this.id;
184 * All child nodes of this node. @type Array
186 this.childNodes = [];
187 if(!this.childNodes.indexOf){ // indexOf is a must
188 this.childNodes.indexOf = function(o){
189 for(var i = 0, len = this.length; i < len; i++){
198 * The parent node for this node. @type Node
200 this.parentNode = null;
202 * The first direct child node of this node, or null if this node has no child nodes. @type Node
204 this.firstChild = null;
206 * The last direct child node of this node, or null if this node has no child nodes. @type Node
208 this.lastChild = null;
210 * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
212 this.previousSibling = null;
214 * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
216 this.nextSibling = null;
221 * Fires when a new child node is appended
222 * @param {Tree} tree The owner tree
223 * @param {Node} this This node
224 * @param {Node} node The newly appended node
225 * @param {Number} index The index of the newly appended node
230 * Fires when a child node is removed
231 * @param {Tree} tree The owner tree
232 * @param {Node} this This node
233 * @param {Node} node The removed node
238 * Fires when this node is moved to a new location in the tree
239 * @param {Tree} tree The owner tree
240 * @param {Node} this This node
241 * @param {Node} oldParent The old parent of this node
242 * @param {Node} newParent The new parent of this node
243 * @param {Number} index The index it was moved to
248 * Fires when a new child node is inserted.
249 * @param {Tree} tree The owner tree
250 * @param {Node} this This node
251 * @param {Node} node The child node inserted
252 * @param {Node} refNode The child node the node was inserted before
256 * @event beforeappend
257 * Fires before a new child is appended, return false to cancel the append.
258 * @param {Tree} tree The owner tree
259 * @param {Node} this This node
260 * @param {Node} node The child node to be appended
262 "beforeappend" : true,
264 * @event beforeremove
265 * Fires before a child is removed, return false to cancel the remove.
266 * @param {Tree} tree The owner tree
267 * @param {Node} this This node
268 * @param {Node} node The child node to be removed
270 "beforeremove" : true,
273 * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
274 * @param {Tree} tree The owner tree
275 * @param {Node} this This node
276 * @param {Node} oldParent The parent of this node
277 * @param {Node} newParent The new parent this node is moving to
278 * @param {Number} index The index it is being moved to
282 * @event beforeinsert
283 * Fires before a new child is inserted, return false to cancel the insert.
284 * @param {Tree} tree The owner tree
285 * @param {Node} this This node
286 * @param {Node} node The child node to be inserted
287 * @param {Node} refNode The child node the node is being inserted before
289 "beforeinsert" : true
291 this.listeners = this.attributes.listeners;
292 Roo.data.Node.superclass.constructor.call(this);
295 Roo.extend(Roo.data.Node, Roo.util.Observable, {
296 fireEvent : function(evtName){
297 // first do standard event for this node
298 if(Roo.data.Node.superclass.fireEvent.apply(this, arguments) === false){
301 // then bubble it up to the tree if the event wasn't cancelled
302 var ot = this.getOwnerTree();
304 if(ot.proxyNodeEvent.apply(ot, arguments) === false){
312 * Returns true if this node is a leaf
316 return this.leaf === true;
320 setFirstChild : function(node){
321 this.firstChild = node;
325 setLastChild : function(node){
326 this.lastChild = node;
331 * Returns true if this node is the last child of its parent
335 return (!this.parentNode ? true : this.parentNode.lastChild == this);
339 * Returns true if this node is the first child of its parent
342 isFirst : function(){
343 return (!this.parentNode ? true : this.parentNode.firstChild == this);
346 hasChildNodes : function(){
347 return !this.isLeaf() && this.childNodes.length > 0;
351 * Insert node(s) as the last child node of this node.
352 * @param {Node/Array} node The node or Array of nodes to append
353 * @return {Node} The appended node if single append, or null if an array was passed
355 appendChild : function(node){
357 if(node instanceof Array){
359 }else if(arguments.length > 1){
363 // if passed an array or multiple args do them one by one
365 for(var i = 0, len = multi.length; i < len; i++) {
366 this.appendChild(multi[i]);
369 if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
372 var index = this.childNodes.length;
373 var oldParent = node.parentNode;
374 // it's a move, make sure we move it cleanly
376 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
379 oldParent.removeChild(node);
382 index = this.childNodes.length;
384 this.setFirstChild(node);
386 this.childNodes.push(node);
387 node.parentNode = this;
388 var ps = this.childNodes[index-1];
390 node.previousSibling = ps;
391 ps.nextSibling = node;
393 node.previousSibling = null;
395 node.nextSibling = null;
396 this.setLastChild(node);
397 node.setOwnerTree(this.getOwnerTree());
398 this.fireEvent("append", this.ownerTree, this, node, index);
400 this.ownerTree.fireEvent("appendnode", this, node, index);
403 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
410 * Removes a child node from this node.
411 * @param {Node} node The node to remove
412 * @return {Node} The removed node
414 removeChild : function(node){
415 var index = this.childNodes.indexOf(node);
419 if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
423 // remove it from childNodes collection
424 this.childNodes.splice(index, 1);
427 if(node.previousSibling){
428 node.previousSibling.nextSibling = node.nextSibling;
430 if(node.nextSibling){
431 node.nextSibling.previousSibling = node.previousSibling;
435 if(this.firstChild == node){
436 this.setFirstChild(node.nextSibling);
438 if(this.lastChild == node){
439 this.setLastChild(node.previousSibling);
442 node.setOwnerTree(null);
443 // clear any references from the node
444 node.parentNode = null;
445 node.previousSibling = null;
446 node.nextSibling = null;
447 this.fireEvent("remove", this.ownerTree, this, node);
452 * Inserts the first node before the second node in this nodes childNodes collection.
453 * @param {Node} node The node to insert
454 * @param {Node} refNode The node to insert before (if null the node is appended)
455 * @return {Node} The inserted node
457 insertBefore : function(node, refNode){
458 if(!refNode){ // like standard Dom, refNode can be null for append
459 return this.appendChild(node);
466 if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
469 var index = this.childNodes.indexOf(refNode);
470 var oldParent = node.parentNode;
471 var refIndex = index;
473 // when moving internally, indexes will change after remove
474 if(oldParent == this && this.childNodes.indexOf(node) < index){
478 // it's a move, make sure we move it cleanly
480 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
483 oldParent.removeChild(node);
486 this.setFirstChild(node);
488 this.childNodes.splice(refIndex, 0, node);
489 node.parentNode = this;
490 var ps = this.childNodes[refIndex-1];
492 node.previousSibling = ps;
493 ps.nextSibling = node;
495 node.previousSibling = null;
497 node.nextSibling = refNode;
498 refNode.previousSibling = node;
499 node.setOwnerTree(this.getOwnerTree());
500 this.fireEvent("insert", this.ownerTree, this, node, refNode);
502 node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
508 * Returns the child node at the specified index.
509 * @param {Number} index
512 item : function(index){
513 return this.childNodes[index];
517 * Replaces one child node in this node with another.
518 * @param {Node} newChild The replacement node
519 * @param {Node} oldChild The node to replace
520 * @return {Node} The replaced node
522 replaceChild : function(newChild, oldChild){
523 this.insertBefore(newChild, oldChild);
524 this.removeChild(oldChild);
529 * Returns the index of a child node
531 * @return {Number} The index of the node or -1 if it was not found
533 indexOf : function(child){
534 return this.childNodes.indexOf(child);
538 * Returns the tree this node is in.
541 getOwnerTree : function(){
542 // if it doesn't have one, look for one
547 this.ownerTree = p.ownerTree;
553 return this.ownerTree;
557 * Returns depth of this node (the root node has a depth of 0)
560 getDepth : function(){
571 setOwnerTree : function(tree){
572 // if it's move, we need to update everyone
573 if(tree != this.ownerTree){
575 this.ownerTree.unregisterNode(this);
577 this.ownerTree = tree;
578 var cs = this.childNodes;
579 for(var i = 0, len = cs.length; i < len; i++) {
580 cs[i].setOwnerTree(tree);
583 tree.registerNode(this);
589 * Returns the path for this node. The path can be used to expand or select this node programmatically.
590 * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
591 * @return {String} The path
593 getPath : function(attr){
595 var p = this.parentNode;
596 var b = [this.attributes[attr]];
598 b.unshift(p.attributes[attr]);
601 var sep = this.getOwnerTree().pathSeparator;
602 return sep + b.join(sep);
606 * Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
607 * function call will be the scope provided or the current node. The arguments to the function
608 * will be the args provided or the current node. If the function returns false at any point,
609 * the bubble is stopped.
610 * @param {Function} fn The function to call
611 * @param {Object} scope (optional) The scope of the function (defaults to current node)
612 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
614 bubble : function(fn, scope, args){
617 if(fn.call(scope || p, args || p) === false){
625 * Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
626 * function call will be the scope provided or the current node. The arguments to the function
627 * will be the args provided or the current node. If the function returns false at any point,
628 * the cascade is stopped on that branch.
629 * @param {Function} fn The function to call
630 * @param {Object} scope (optional) The scope of the function (defaults to current node)
631 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
633 cascade : function(fn, scope, args){
634 if(fn.call(scope || this, args || this) !== false){
635 var cs = this.childNodes;
636 for(var i = 0, len = cs.length; i < len; i++) {
637 cs[i].cascade(fn, scope, args);
643 * Interates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of
644 * function call will be the scope provided or the current node. The arguments to the function
645 * will be the args provided or the current node. If the function returns false at any point,
646 * the iteration stops.
647 * @param {Function} fn The function to call
648 * @param {Object} scope (optional) The scope of the function (defaults to current node)
649 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
651 eachChild : function(fn, scope, args){
652 var cs = this.childNodes;
653 for(var i = 0, len = cs.length; i < len; i++) {
654 if(fn.call(scope || this, args || cs[i]) === false){
661 * Finds the first child that has the attribute with the specified value.
662 * @param {String} attribute The attribute name
663 * @param {Mixed} value The value to search for
664 * @return {Node} The found child or null if none was found
666 findChild : function(attribute, value){
667 var cs = this.childNodes;
668 for(var i = 0, len = cs.length; i < len; i++) {
669 if(cs[i].attributes[attribute] == value){
677 * Finds the first child by a custom function. The child matches if the function passed
679 * @param {Function} fn
680 * @param {Object} scope (optional)
681 * @return {Node} The found child or null if none was found
683 findChildBy : function(fn, scope){
684 var cs = this.childNodes;
685 for(var i = 0, len = cs.length; i < len; i++) {
686 if(fn.call(scope||cs[i], cs[i]) === true){
694 * Sorts this nodes children using the supplied sort function
695 * @param {Function} fn
696 * @param {Object} scope (optional)
698 sort : function(fn, scope){
699 var cs = this.childNodes;
702 var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
704 for(var i = 0; i < len; i++){
706 n.previousSibling = cs[i-1];
707 n.nextSibling = cs[i+1];
709 this.setFirstChild(n);
712 this.setLastChild(n);
719 * Returns true if this node is an ancestor (at any point) of the passed node.
723 contains : function(node){
724 return node.isAncestor(this);
728 * Returns true if the passed node is an ancestor (at any point) of this node.
732 isAncestor : function(node){
733 var p = this.parentNode;
743 toString : function(){
744 return "[Node"+(this.id?" "+this.id:"")+"]";