2
* Ext JS Library 3.0 RC2
3
* Copyright(c) 2006-2009, Ext JS, LLC.
6
* http://extjs.com/license
10
* @class Ext.data.Tree
11
* @extends Ext.util.Observable
12
* Represents a tree data structure and bubbles all the events for its nodes. The nodes
13
* in the tree have most standard DOM functionality.
15
* @param {Node} root (optional) The root node
17
Ext.data.Tree = function(root){
20
* The root node for this tree
25
this.setRootNode(root);
30
* Fires when a new child node is appended to a node in this tree.
31
* @param {Tree} tree The owner tree
32
* @param {Node} parent The parent node
33
* @param {Node} node The newly appended node
34
* @param {Number} index The index of the newly appended node
39
* Fires when a child node is removed from a node in this tree.
40
* @param {Tree} tree The owner tree
41
* @param {Node} parent The parent node
42
* @param {Node} node The child node removed
47
* Fires when a node is moved to a new location in the tree
48
* @param {Tree} tree The owner tree
49
* @param {Node} node The node moved
50
* @param {Node} oldParent The old parent of this node
51
* @param {Node} newParent The new parent of this node
52
* @param {Number} index The index it was moved to
57
* Fires when a new child node is inserted in a node in this tree.
58
* @param {Tree} tree The owner tree
59
* @param {Node} parent The parent node
60
* @param {Node} node The child node inserted
61
* @param {Node} refNode The child node the node was inserted before
66
* Fires before a new child is appended to a node in this tree, return false to cancel the append.
67
* @param {Tree} tree The owner tree
68
* @param {Node} parent The parent node
69
* @param {Node} node The child node to be appended
74
* Fires before a child is removed from a node in this tree, return false to cancel the remove.
75
* @param {Tree} tree The owner tree
76
* @param {Node} parent The parent node
77
* @param {Node} node The child node to be removed
82
* Fires before a node is moved to a new location in the tree. Return false to cancel the move.
83
* @param {Tree} tree The owner tree
84
* @param {Node} node The node being moved
85
* @param {Node} oldParent The parent of the node
86
* @param {Node} newParent The new parent the node is moving to
87
* @param {Number} index The index it is being moved to
92
* Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
93
* @param {Tree} tree The owner tree
94
* @param {Node} parent The parent node
95
* @param {Node} node The child node to be inserted
96
* @param {Node} refNode The child node the node is being inserted before
101
Ext.data.Tree.superclass.constructor.call(this);
104
Ext.extend(Ext.data.Tree, Ext.util.Observable, {
106
* @cfg {String} pathSeparator
107
* The token used to separate paths in node ids (defaults to '/').
112
proxyNodeEvent : function(){
113
return this.fireEvent.apply(this, arguments);
117
* Returns the root node for this tree.
120
getRootNode : function(){
125
* Sets the root node for this tree.
129
setRootNode : function(node){
131
node.ownerTree = this;
133
this.registerNode(node);
138
* Gets a node in this tree by its id.
142
getNodeById : function(id){
143
return this.nodeHash[id];
147
registerNode : function(node){
148
this.nodeHash[node.id] = node;
152
unregisterNode : function(node){
153
delete this.nodeHash[node.id];
156
toString : function(){
157
return "[Tree"+(this.id?" "+this.id:"")+"]";
162
* @class Ext.data.Node
163
* @extends Ext.util.Observable
164
* @cfg {Boolean} leaf true if this node is a leaf and does not have children
165
* @cfg {String} id The id for this node. If one is not specified, one is generated.
167
* @param {Object} attributes The attributes/config for the node
169
Ext.data.Node = function(attributes){
171
* The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
174
this.attributes = attributes || {};
175
this.leaf = this.attributes.leaf;
177
* The node id. @type String
179
this.id = this.attributes.id;
181
this.id = Ext.id(null, "xnode-");
182
this.attributes.id = this.id;
185
* All child nodes of this node. @type Array
187
this.childNodes = [];
188
if(!this.childNodes.indexOf){ // indexOf is a must
189
this.childNodes.indexOf = function(o){
190
for(var i = 0, len = this.length; i < len; i++){
191
if(this[i] == o) return i;
197
* The parent node for this node. @type Node
199
this.parentNode = null;
201
* The first direct child node of this node, or null if this node has no child nodes. @type Node
203
this.firstChild = null;
205
* The last direct child node of this node, or null if this node has no child nodes. @type Node
207
this.lastChild = null;
209
* The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
211
this.previousSibling = null;
213
* The node immediately following this node in the tree, or null if there is no sibling node. @type Node
215
this.nextSibling = null;
220
* Fires when a new child node is appended
221
* @param {Tree} tree The owner tree
222
* @param {Node} this This node
223
* @param {Node} node The newly appended node
224
* @param {Number} index The index of the newly appended node
229
* Fires when a child node is removed
230
* @param {Tree} tree The owner tree
231
* @param {Node} this This node
232
* @param {Node} node The removed node
237
* Fires when this node is moved to a new location in the tree
238
* @param {Tree} tree The owner tree
239
* @param {Node} this This node
240
* @param {Node} oldParent The old parent of this node
241
* @param {Node} newParent The new parent of this node
242
* @param {Number} index The index it was moved to
247
* Fires when a new child node is inserted.
248
* @param {Tree} tree The owner tree
249
* @param {Node} this This node
250
* @param {Node} node The child node inserted
251
* @param {Node} refNode The child node the node was inserted before
255
* @event beforeappend
256
* Fires before a new child is appended, return false to cancel the append.
257
* @param {Tree} tree The owner tree
258
* @param {Node} this This node
259
* @param {Node} node The child node to be appended
261
"beforeappend" : true,
263
* @event beforeremove
264
* Fires before a child is removed, return false to cancel the remove.
265
* @param {Tree} tree The owner tree
266
* @param {Node} this This node
267
* @param {Node} node The child node to be removed
269
"beforeremove" : true,
272
* Fires before this node is moved to a new location in the tree. Return false to cancel the move.
273
* @param {Tree} tree The owner tree
274
* @param {Node} this This node
275
* @param {Node} oldParent The parent of this node
276
* @param {Node} newParent The new parent this node is moving to
277
* @param {Number} index The index it is being moved to
281
* @event beforeinsert
282
* Fires before a new child is inserted, return false to cancel the insert.
283
* @param {Tree} tree The owner tree
284
* @param {Node} this This node
285
* @param {Node} node The child node to be inserted
286
* @param {Node} refNode The child node the node is being inserted before
288
"beforeinsert" : true
290
this.listeners = this.attributes.listeners;
291
Ext.data.Node.superclass.constructor.call(this);
294
Ext.extend(Ext.data.Node, Ext.util.Observable, {
296
fireEvent : function(evtName){
297
// first do standard event for this node
298
if(Ext.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);
347
* Returns true if this node has one or more child nodes, else false.
350
hasChildNodes : function(){
351
return !this.isLeaf() && this.childNodes.length > 0;
355
* Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
356
* node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
359
isExpandable : function(){
360
return this.attributes.expandable || this.hasChildNodes();
364
* Insert node(s) as the last child node of this node.
365
* @param {Node/Array} node The node or Array of nodes to append
366
* @return {Node} The appended node if single append, or null if an array was passed
368
appendChild : function(node){
370
if(Ext.isArray(node)){
372
}else if(arguments.length > 1){
375
// if passed an array or multiple args do them one by one
377
for(var i = 0, len = multi.length; i < len; i++) {
378
this.appendChild(multi[i]);
381
if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
384
var index = this.childNodes.length;
385
var oldParent = node.parentNode;
386
// it's a move, make sure we move it cleanly
388
if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
391
oldParent.removeChild(node);
393
index = this.childNodes.length;
395
this.setFirstChild(node);
397
this.childNodes.push(node);
398
node.parentNode = this;
399
var ps = this.childNodes[index-1];
401
node.previousSibling = ps;
402
ps.nextSibling = node;
404
node.previousSibling = null;
406
node.nextSibling = null;
407
this.setLastChild(node);
408
node.setOwnerTree(this.getOwnerTree());
409
this.fireEvent("append", this.ownerTree, this, node, index);
411
node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
418
* Removes a child node from this node.
419
* @param {Node} node The node to remove
420
* @return {Node} The removed node
422
removeChild : function(node){
423
var index = this.childNodes.indexOf(node);
427
if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
431
// remove it from childNodes collection
432
this.childNodes.splice(index, 1);
435
if(node.previousSibling){
436
node.previousSibling.nextSibling = node.nextSibling;
438
if(node.nextSibling){
439
node.nextSibling.previousSibling = node.previousSibling;
443
if(this.firstChild == node){
444
this.setFirstChild(node.nextSibling);
446
if(this.lastChild == node){
447
this.setLastChild(node.previousSibling);
450
node.setOwnerTree(null);
451
// clear any references from the node
452
node.parentNode = null;
453
node.previousSibling = null;
454
node.nextSibling = null;
455
this.fireEvent("remove", this.ownerTree, this, node);
460
* Inserts the first node before the second node in this nodes childNodes collection.
461
* @param {Node} node The node to insert
462
* @param {Node} refNode The node to insert before (if null the node is appended)
463
* @return {Node} The inserted node
465
insertBefore : function(node, refNode){
466
if(!refNode){ // like standard Dom, refNode can be null for append
467
return this.appendChild(node);
474
if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
477
var index = this.childNodes.indexOf(refNode);
478
var oldParent = node.parentNode;
479
var refIndex = index;
481
// when moving internally, indexes will change after remove
482
if(oldParent == this && this.childNodes.indexOf(node) < index){
486
// it's a move, make sure we move it cleanly
488
if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
491
oldParent.removeChild(node);
494
this.setFirstChild(node);
496
this.childNodes.splice(refIndex, 0, node);
497
node.parentNode = this;
498
var ps = this.childNodes[refIndex-1];
500
node.previousSibling = ps;
501
ps.nextSibling = node;
503
node.previousSibling = null;
505
node.nextSibling = refNode;
506
refNode.previousSibling = node;
507
node.setOwnerTree(this.getOwnerTree());
508
this.fireEvent("insert", this.ownerTree, this, node, refNode);
510
node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
516
* Removes this node from its parent
517
* @return {Node} this
520
this.parentNode.removeChild(this);
525
* Returns the child node at the specified index.
526
* @param {Number} index
529
item : function(index){
530
return this.childNodes[index];
534
* Replaces one child node in this node with another.
535
* @param {Node} newChild The replacement node
536
* @param {Node} oldChild The node to replace
537
* @return {Node} The replaced node
539
replaceChild : function(newChild, oldChild){
540
var s = oldChild ? oldChild.nextSibling : null;
541
this.removeChild(oldChild);
542
this.insertBefore(newChild, s);
547
* Returns the index of a child node
549
* @return {Number} The index of the node or -1 if it was not found
551
indexOf : function(child){
552
return this.childNodes.indexOf(child);
556
* Returns the tree this node is in.
559
getOwnerTree : function(){
560
// if it doesn't have one, look for one
565
this.ownerTree = p.ownerTree;
571
return this.ownerTree;
575
* Returns depth of this node (the root node has a depth of 0)
578
getDepth : function(){
589
setOwnerTree : function(tree){
590
// if it is a move, we need to update everyone
591
if(tree != this.ownerTree){
593
this.ownerTree.unregisterNode(this);
595
this.ownerTree = tree;
596
var cs = this.childNodes;
597
for(var i = 0, len = cs.length; i < len; i++) {
598
cs[i].setOwnerTree(tree);
601
tree.registerNode(this);
607
* Changes the id of this node.
608
* @param {String} id The new id for the node.
612
var t = this.ownerTree;
614
t.unregisterNode(this);
618
t.registerNode(this);
625
onIdChange: Ext.emptyFn,
628
* Returns the path for this node. The path can be used to expand or select this node programmatically.
629
* @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
630
* @return {String} The path
632
getPath : function(attr){
634
var p = this.parentNode;
635
var b = [this.attributes[attr]];
637
b.unshift(p.attributes[attr]);
640
var sep = this.getOwnerTree().pathSeparator;
641
return sep + b.join(sep);
645
* Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
646
* function call will be the scope provided or the current node. The arguments to the function
647
* will be the args provided or the current node. If the function returns false at any point,
648
* the bubble is stopped.
649
* @param {Function} fn The function to call
650
* @param {Object} scope (optional) The scope of the function (defaults to current node)
651
* @param {Array} args (optional) The args to call the function with (default to passing the current node)
653
bubble : function(fn, scope, args){
656
if(fn.apply(scope || p, args || [p]) === false){
664
* Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
665
* function call will be the scope provided or the current node. The arguments to the function
666
* will be the args provided or the current node. If the function returns false at any point,
667
* the cascade is stopped on that branch.
668
* @param {Function} fn The function to call
669
* @param {Object} scope (optional) The scope of the function (defaults to current node)
670
* @param {Array} args (optional) The args to call the function with (default to passing the current node)
672
cascade : function(fn, scope, args){
673
if(fn.apply(scope || this, args || [this]) !== false){
674
var cs = this.childNodes;
675
for(var i = 0, len = cs.length; i < len; i++) {
676
cs[i].cascade(fn, scope, args);
682
* Interates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of
683
* function call will be the scope provided or the current node. The arguments to the function
684
* will be the args provided or the current node. If the function returns false at any point,
685
* the iteration stops.
686
* @param {Function} fn The function to call
687
* @param {Object} scope (optional) The scope of the function (defaults to current node)
688
* @param {Array} args (optional) The args to call the function with (default to passing the current node)
690
eachChild : function(fn, scope, args){
691
var cs = this.childNodes;
692
for(var i = 0, len = cs.length; i < len; i++) {
693
if(fn.apply(scope || this, args || [cs[i]]) === false){
700
* Finds the first child that has the attribute with the specified value.
701
* @param {String} attribute The attribute name
702
* @param {Mixed} value The value to search for
703
* @return {Node} The found child or null if none was found
705
findChild : function(attribute, value){
706
var cs = this.childNodes;
707
for(var i = 0, len = cs.length; i < len; i++) {
708
if(cs[i].attributes[attribute] == value){
716
* Finds the first child by a custom function. The child matches if the function passed
718
* @param {Function} fn
719
* @param {Object} scope (optional)
720
* @return {Node} The found child or null if none was found
722
findChildBy : function(fn, scope){
723
var cs = this.childNodes;
724
for(var i = 0, len = cs.length; i < len; i++) {
725
if(fn.call(scope||cs[i], cs[i]) === true){
733
* Sorts this nodes children using the supplied sort function
734
* @param {Function} fn
735
* @param {Object} scope (optional)
737
sort : function(fn, scope){
738
var cs = this.childNodes;
741
var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
743
for(var i = 0; i < len; i++){
745
n.previousSibling = cs[i-1];
746
n.nextSibling = cs[i+1];
748
this.setFirstChild(n);
751
this.setLastChild(n);
758
* Returns true if this node is an ancestor (at any point) of the passed node.
762
contains : function(node){
763
return node.isAncestor(this);
767
* Returns true if the passed node is an ancestor (at any point) of this node.
771
isAncestor : function(node){
772
var p = this.parentNode;
782
toString : function(){
783
return "[Node"+(this.id?" "+this.id:"")+"]";
b'\\ No newline at end of file'