Upgrade to ExtJS 3.3.0 - Released 10/06/2010
[extjs.git] / docs / source / Tree.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.0
11  * Copyright(c) 2006-2010 Ext JS, Inc.
12  * licensing@extjs.com
13  * http://www.extjs.com/license
14  */
15 <div id="cls-Ext.data.Tree"></div>/**
16  * @class Ext.data.Tree
17  * @extends Ext.util.Observable
18  * Represents a tree data structure and bubbles all the events for its nodes. The nodes
19  * in the tree have most standard DOM functionality.
20  * @constructor
21  * @param {Node} root (optional) The root node
22  */
23 Ext.data.Tree = Ext.extend(Ext.util.Observable, {
24     
25     constructor: function(root){
26         this.nodeHash = {};
27         <div id="prop-Ext.data.Tree-root"></div>/**
28          * The root node for this tree
29          * @type Node
30          */
31         this.root = null;
32         if(root){
33             this.setRootNode(root);
34         }
35         this.addEvents(
36             <div id="event-Ext.data.Tree-append"></div>/**
37              * @event append
38              * Fires when a new child node is appended to a node in this tree.
39              * @param {Tree} tree The owner tree
40              * @param {Node} parent The parent node
41              * @param {Node} node The newly appended node
42              * @param {Number} index The index of the newly appended node
43              */
44             "append",
45             <div id="event-Ext.data.Tree-remove"></div>/**
46              * @event remove
47              * Fires when a child node is removed from a node in this tree.
48              * @param {Tree} tree The owner tree
49              * @param {Node} parent The parent node
50              * @param {Node} node The child node removed
51              */
52             "remove",
53             <div id="event-Ext.data.Tree-move"></div>/**
54              * @event move
55              * Fires when a node is moved to a new location in the tree
56              * @param {Tree} tree The owner tree
57              * @param {Node} node The node moved
58              * @param {Node} oldParent The old parent of this node
59              * @param {Node} newParent The new parent of this node
60              * @param {Number} index The index it was moved to
61              */
62             "move",
63             <div id="event-Ext.data.Tree-insert"></div>/**
64              * @event insert
65              * Fires when a new child node is inserted in a node in this tree.
66              * @param {Tree} tree The owner tree
67              * @param {Node} parent The parent node
68              * @param {Node} node The child node inserted
69              * @param {Node} refNode The child node the node was inserted before
70              */
71             "insert",
72             <div id="event-Ext.data.Tree-beforeappend"></div>/**
73              * @event beforeappend
74              * Fires before a new child is appended to a node in this tree, return false to cancel the append.
75              * @param {Tree} tree The owner tree
76              * @param {Node} parent The parent node
77              * @param {Node} node The child node to be appended
78              */
79             "beforeappend",
80             <div id="event-Ext.data.Tree-beforeremove"></div>/**
81              * @event beforeremove
82              * Fires before a child is removed from a node in this tree, return false to cancel the remove.
83              * @param {Tree} tree The owner tree
84              * @param {Node} parent The parent node
85              * @param {Node} node The child node to be removed
86              */
87             "beforeremove",
88             <div id="event-Ext.data.Tree-beforemove"></div>/**
89              * @event beforemove
90              * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
91              * @param {Tree} tree The owner tree
92              * @param {Node} node The node being moved
93              * @param {Node} oldParent The parent of the node
94              * @param {Node} newParent The new parent the node is moving to
95              * @param {Number} index The index it is being moved to
96              */
97             "beforemove",
98             <div id="event-Ext.data.Tree-beforeinsert"></div>/**
99              * @event beforeinsert
100              * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
101              * @param {Tree} tree The owner tree
102              * @param {Node} parent The parent node
103              * @param {Node} node The child node to be inserted
104              * @param {Node} refNode The child node the node is being inserted before
105              */
106             "beforeinsert"
107         );
108         Ext.data.Tree.superclass.constructor.call(this);        
109     },
110     
111     <div id="cfg-Ext.data.Tree-pathSeparator"></div>/**
112      * @cfg {String} pathSeparator
113      * The token used to separate paths in node ids (defaults to '/').
114      */
115     pathSeparator: "/",
116
117     // private
118     proxyNodeEvent : function(){
119         return this.fireEvent.apply(this, arguments);
120     },
121
122     <div id="method-Ext.data.Tree-getRootNode"></div>/**
123      * Returns the root node for this tree.
124      * @return {Node}
125      */
126     getRootNode : function(){
127         return this.root;
128     },
129
130     <div id="method-Ext.data.Tree-setRootNode"></div>/**
131      * Sets the root node for this tree.
132      * @param {Node} node
133      * @return {Node}
134      */
135     setRootNode : function(node){
136         this.root = node;
137         node.ownerTree = this;
138         node.isRoot = true;
139         this.registerNode(node);
140         return node;
141     },
142
143     <div id="method-Ext.data.Tree-getNodeById"></div>/**
144      * Gets a node in this tree by its id.
145      * @param {String} id
146      * @return {Node}
147      */
148     getNodeById : function(id){
149         return this.nodeHash[id];
150     },
151
152     // private
153     registerNode : function(node){
154         this.nodeHash[node.id] = node;
155     },
156
157     // private
158     unregisterNode : function(node){
159         delete this.nodeHash[node.id];
160     },
161
162     toString : function(){
163         return "[Tree"+(this.id?" "+this.id:"")+"]";
164     }
165 });
166
167 <div id="cls-Ext.data.Node"></div>/**
168  * @class Ext.data.Node
169  * @extends Ext.util.Observable
170  * @cfg {Boolean} leaf true if this node is a leaf and does not have children
171  * @cfg {String} id The id for this node. If one is not specified, one is generated.
172  * @constructor
173  * @param {Object} attributes The attributes/config for the node
174  */
175 Ext.data.Node = Ext.extend(Ext.util.Observable, {
176     
177     constructor: function(attributes){
178         /**
179          * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
180          * @type {Object}
181          */
182         this.attributes = attributes || {};
183         this.leaf = this.attributes.leaf;
184         /**
185          * The node id. @type String
186          */
187         this.id = this.attributes.id;
188         if(!this.id){
189             this.id = Ext.id(null, "xnode-");
190             this.attributes.id = this.id;
191         }
192         /**
193          * All child nodes of this node. @type Array
194          */
195         this.childNodes = [];
196         /**
197          * The parent node for this node. @type Node
198          */
199         this.parentNode = null;
200         /**
201          * The first direct child node of this node, or null if this node has no child nodes. @type Node
202          */
203         this.firstChild = null;
204         /**
205          * The last direct child node of this node, or null if this node has no child nodes. @type Node
206          */
207         this.lastChild = null;
208         /**
209          * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
210          */
211         this.previousSibling = null;
212         /**
213          * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
214          */
215         this.nextSibling = null;
216
217         this.addEvents({
218             /**
219              * @event append
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
225              */
226             "append" : true,
227             /**
228              * @event remove
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
233              */
234             "remove" : true,
235             /**
236              * @event move
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
243              */
244             "move" : true,
245             /**
246              * @event insert
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
252              */
253             "insert" : true,
254             /**
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
260              */
261             "beforeappend" : true,
262             /**
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
268              */
269             "beforeremove" : true,
270             /**
271              * @event beforemove
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
278              */
279             "beforemove" : true,
280              /**
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
287               */
288             "beforeinsert" : true
289         });
290         this.listeners = this.attributes.listeners;
291         Ext.data.Node.superclass.constructor.call(this);    
292     },
293     
294     // private
295     fireEvent : function(evtName){
296         // first do standard event for this node
297         if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
298             return false;
299         }
300         // then bubble it up to the tree if the event wasn't cancelled
301         var ot = this.getOwnerTree();
302         if(ot){
303             if(ot.proxyNodeEvent.apply(ot, arguments) === false){
304                 return false;
305             }
306         }
307         return true;
308     },
309
310     /**
311      * Returns true if this node is a leaf
312      * @return {Boolean}
313      */
314     isLeaf : function(){
315         return this.leaf === true;
316     },
317
318     // private
319     setFirstChild : function(node){
320         this.firstChild = node;
321     },
322
323     //private
324     setLastChild : function(node){
325         this.lastChild = node;
326     },
327
328
329     /**
330      * Returns true if this node is the last child of its parent
331      * @return {Boolean}
332      */
333     isLast : function(){
334        return (!this.parentNode ? true : this.parentNode.lastChild == this);
335     },
336
337     /**
338      * Returns true if this node is the first child of its parent
339      * @return {Boolean}
340      */
341     isFirst : function(){
342        return (!this.parentNode ? true : this.parentNode.firstChild == this);
343     },
344
345     /**
346      * Returns true if this node has one or more child nodes, else false.
347      * @return {Boolean}
348      */
349     hasChildNodes : function(){
350         return !this.isLeaf() && this.childNodes.length > 0;
351     },
352
353     /**
354      * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
355      * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
356      * @return {Boolean}
357      */
358     isExpandable : function(){
359         return this.attributes.expandable || this.hasChildNodes();
360     },
361
362     /**
363      * Insert node(s) as the last child node of this node.
364      * @param {Node/Array} node The node or Array of nodes to append
365      * @return {Node} The appended node if single append, or null if an array was passed
366      */
367     appendChild : function(node){
368         var multi = false;
369         if(Ext.isArray(node)){
370             multi = node;
371         }else if(arguments.length > 1){
372             multi = arguments;
373         }
374         // if passed an array or multiple args do them one by one
375         if(multi){
376             for(var i = 0, len = multi.length; i < len; i++) {
377                 this.appendChild(multi[i]);
378             }
379         }else{
380             if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
381                 return false;
382             }
383             var index = this.childNodes.length;
384             var oldParent = node.parentNode;
385             // it's a move, make sure we move it cleanly
386             if(oldParent){
387                 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
388                     return false;
389                 }
390                 oldParent.removeChild(node);
391             }
392             index = this.childNodes.length;
393             if(index === 0){
394                 this.setFirstChild(node);
395             }
396             this.childNodes.push(node);
397             node.parentNode = this;
398             var ps = this.childNodes[index-1];
399             if(ps){
400                 node.previousSibling = ps;
401                 ps.nextSibling = node;
402             }else{
403                 node.previousSibling = null;
404             }
405             node.nextSibling = null;
406             this.setLastChild(node);
407             node.setOwnerTree(this.getOwnerTree());
408             this.fireEvent("append", this.ownerTree, this, node, index);
409             if(oldParent){
410                 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
411             }
412             return node;
413         }
414     },
415
416     /**
417      * Removes a child node from this node.
418      * @param {Node} node The node to remove
419      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
420      * @return {Node} The removed node
421      */
422     removeChild : function(node, destroy){
423         var index = this.childNodes.indexOf(node);
424         if(index == -1){
425             return false;
426         }
427         if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
428             return false;
429         }
430
431         // remove it from childNodes collection
432         this.childNodes.splice(index, 1);
433
434         // update siblings
435         if(node.previousSibling){
436             node.previousSibling.nextSibling = node.nextSibling;
437         }
438         if(node.nextSibling){
439             node.nextSibling.previousSibling = node.previousSibling;
440         }
441
442         // update child refs
443         if(this.firstChild == node){
444             this.setFirstChild(node.nextSibling);
445         }
446         if(this.lastChild == node){
447             this.setLastChild(node.previousSibling);
448         }
449
450         this.fireEvent("remove", this.ownerTree, this, node);
451         if(destroy){
452             node.destroy(true);
453         }else{
454             node.clear();
455         }
456         return node;
457     },
458
459     // private
460     clear : function(destroy){
461         // clear any references from the node
462         this.setOwnerTree(null, destroy);
463         this.parentNode = this.previousSibling = this.nextSibling = null;
464         if(destroy){
465             this.firstChild = this.lastChild = null;
466         }
467     },
468
469     /**
470      * Destroys the node.
471      */
472     destroy : function(/* private */ silent){
473         /*
474          * Silent is to be used in a number of cases
475          * 1) When setRootNode is called.
476          * 2) When destroy on the tree is called
477          * 3) For destroying child nodes on a node
478          */
479         if(silent === true){
480             this.purgeListeners();
481             this.clear(true);
482             Ext.each(this.childNodes, function(n){
483                 n.destroy(true);
484             });
485             this.childNodes = null;
486         }else{
487             this.remove(true);
488         }
489     },
490
491     /**
492      * Inserts the first node before the second node in this nodes childNodes collection.
493      * @param {Node} node The node to insert
494      * @param {Node} refNode The node to insert before (if null the node is appended)
495      * @return {Node} The inserted node
496      */
497     insertBefore : function(node, refNode){
498         if(!refNode){ // like standard Dom, refNode can be null for append
499             return this.appendChild(node);
500         }
501         // nothing to do
502         if(node == refNode){
503             return false;
504         }
505
506         if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
507             return false;
508         }
509         var index = this.childNodes.indexOf(refNode);
510         var oldParent = node.parentNode;
511         var refIndex = index;
512
513         // when moving internally, indexes will change after remove
514         if(oldParent == this && this.childNodes.indexOf(node) < index){
515             refIndex--;
516         }
517
518         // it's a move, make sure we move it cleanly
519         if(oldParent){
520             if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
521                 return false;
522             }
523             oldParent.removeChild(node);
524         }
525         if(refIndex === 0){
526             this.setFirstChild(node);
527         }
528         this.childNodes.splice(refIndex, 0, node);
529         node.parentNode = this;
530         var ps = this.childNodes[refIndex-1];
531         if(ps){
532             node.previousSibling = ps;
533             ps.nextSibling = node;
534         }else{
535             node.previousSibling = null;
536         }
537         node.nextSibling = refNode;
538         refNode.previousSibling = node;
539         node.setOwnerTree(this.getOwnerTree());
540         this.fireEvent("insert", this.ownerTree, this, node, refNode);
541         if(oldParent){
542             node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
543         }
544         return node;
545     },
546
547     /**
548      * Removes this node from its parent
549      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
550      * @return {Node} this
551      */
552     remove : function(destroy){
553         if (this.parentNode) {
554             this.parentNode.removeChild(this, destroy);
555         }
556         return this;
557     },
558
559     /**
560      * Removes all child nodes from this node.
561      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
562      * @return {Node} this
563      */
564     removeAll : function(destroy){
565         var cn = this.childNodes,
566             n;
567         while((n = cn[0])){
568             this.removeChild(n, destroy);
569         }
570         return this;
571     },
572
573     /**
574      * Returns the child node at the specified index.
575      * @param {Number} index
576      * @return {Node}
577      */
578     item : function(index){
579         return this.childNodes[index];
580     },
581
582     /**
583      * Replaces one child node in this node with another.
584      * @param {Node} newChild The replacement node
585      * @param {Node} oldChild The node to replace
586      * @return {Node} The replaced node
587      */
588     replaceChild : function(newChild, oldChild){
589         var s = oldChild ? oldChild.nextSibling : null;
590         this.removeChild(oldChild);
591         this.insertBefore(newChild, s);
592         return oldChild;
593     },
594
595     /**
596      * Returns the index of a child node
597      * @param {Node} node
598      * @return {Number} The index of the node or -1 if it was not found
599      */
600     indexOf : function(child){
601         return this.childNodes.indexOf(child);
602     },
603
604     /**
605      * Returns the tree this node is in.
606      * @return {Tree}
607      */
608     getOwnerTree : function(){
609         // if it doesn't have one, look for one
610         if(!this.ownerTree){
611             var p = this;
612             while(p){
613                 if(p.ownerTree){
614                     this.ownerTree = p.ownerTree;
615                     break;
616                 }
617                 p = p.parentNode;
618             }
619         }
620         return this.ownerTree;
621     },
622
623     /**
624      * Returns depth of this node (the root node has a depth of 0)
625      * @return {Number}
626      */
627     getDepth : function(){
628         var depth = 0;
629         var p = this;
630         while(p.parentNode){
631             ++depth;
632             p = p.parentNode;
633         }
634         return depth;
635     },
636
637     // private
638     setOwnerTree : function(tree, destroy){
639         // if it is a move, we need to update everyone
640         if(tree != this.ownerTree){
641             if(this.ownerTree){
642                 this.ownerTree.unregisterNode(this);
643             }
644             this.ownerTree = tree;
645             // If we're destroying, we don't need to recurse since it will be called on each child node
646             if(destroy !== true){
647                 Ext.each(this.childNodes, function(n){
648                     n.setOwnerTree(tree);
649                 });
650             }
651             if(tree){
652                 tree.registerNode(this);
653             }
654         }
655     },
656
657     /**
658      * Changes the id of this node.
659      * @param {String} id The new id for the node.
660      */
661     setId: function(id){
662         if(id !== this.id){
663             var t = this.ownerTree;
664             if(t){
665                 t.unregisterNode(this);
666             }
667             this.id = this.attributes.id = id;
668             if(t){
669                 t.registerNode(this);
670             }
671             this.onIdChange(id);
672         }
673     },
674
675     // private
676     onIdChange: Ext.emptyFn,
677
678     /**
679      * Returns the path for this node. The path can be used to expand or select this node programmatically.
680      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
681      * @return {String} The path
682      */
683     getPath : function(attr){
684         attr = attr || "id";
685         var p = this.parentNode;
686         var b = [this.attributes[attr]];
687         while(p){
688             b.unshift(p.attributes[attr]);
689             p = p.parentNode;
690         }
691         var sep = this.getOwnerTree().pathSeparator;
692         return sep + b.join(sep);
693     },
694
695     /**
696      * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
697      * will be the args provided or the current node. If the function returns false at any point,
698      * the bubble is stopped.
699      * @param {Function} fn The function to call
700      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
701      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
702      */
703     bubble : function(fn, scope, args){
704         var p = this;
705         while(p){
706             if(fn.apply(scope || p, args || [p]) === false){
707                 break;
708             }
709             p = p.parentNode;
710         }
711     },
712
713     /**
714      * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
715      * will be the args provided or the current node. If the function returns false at any point,
716      * the cascade is stopped on that branch.
717      * @param {Function} fn The function to call
718      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
719      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
720      */
721     cascade : function(fn, scope, args){
722         if(fn.apply(scope || this, args || [this]) !== false){
723             var cs = this.childNodes;
724             for(var i = 0, len = cs.length; i < len; i++) {
725                 cs[i].cascade(fn, scope, args);
726             }
727         }
728     },
729
730     /**
731      * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
732      * will be the args provided or the current node. If the function returns false at any point,
733      * the iteration stops.
734      * @param {Function} fn The function to call
735      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
736      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
737      */
738     eachChild : function(fn, scope, args){
739         var cs = this.childNodes;
740         for(var i = 0, len = cs.length; i < len; i++) {
741             if(fn.apply(scope || cs[i], args || [cs[i]]) === false){
742                 break;
743             }
744         }
745     },
746
747     /**
748      * Finds the first child that has the attribute with the specified value.
749      * @param {String} attribute The attribute name
750      * @param {Mixed} value The value to search for
751      * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
752      * @return {Node} The found child or null if none was found
753      */
754     findChild : function(attribute, value, deep){
755         return this.findChildBy(function(){
756             return this.attributes[attribute] == value;
757         }, null, deep);
758     },
759
760     /**
761      * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
762      * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
763      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
764      * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
765      * @return {Node} The found child or null if none was found
766      */
767     findChildBy : function(fn, scope, deep){
768         var cs = this.childNodes,
769             len = cs.length,
770             i = 0,
771             n,
772             res;
773         for(; i < len; i++){
774             n = cs[i];
775             if(fn.call(scope || n, n) === true){
776                 return n;
777             }else if (deep){
778                 res = n.findChildBy(fn, scope, deep);
779                 if(res != null){
780                     return res;
781                 }
782             }
783             
784         }
785         return null;
786     },
787
788     /**
789      * Sorts this nodes children using the supplied sort function.
790      * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
791      * @param {Object} scope (optional)The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
792      */
793     sort : function(fn, scope){
794         var cs = this.childNodes;
795         var len = cs.length;
796         if(len > 0){
797             var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
798             cs.sort(sortFn);
799             for(var i = 0; i < len; i++){
800                 var n = cs[i];
801                 n.previousSibling = cs[i-1];
802                 n.nextSibling = cs[i+1];
803                 if(i === 0){
804                     this.setFirstChild(n);
805                 }
806                 if(i == len-1){
807                     this.setLastChild(n);
808                 }
809             }
810         }
811     },
812
813     /**
814      * Returns true if this node is an ancestor (at any point) of the passed node.
815      * @param {Node} node
816      * @return {Boolean}
817      */
818     contains : function(node){
819         return node.isAncestor(this);
820     },
821
822     /**
823      * Returns true if the passed node is an ancestor (at any point) of this node.
824      * @param {Node} node
825      * @return {Boolean}
826      */
827     isAncestor : function(node){
828         var p = this.parentNode;
829         while(p){
830             if(p == node){
831                 return true;
832             }
833             p = p.parentNode;
834         }
835         return false;
836     },
837
838     toString : function(){
839         return "[Node"+(this.id?" "+this.id:"")+"]";
840     }
841 });</pre>    
842 </body>
843 </html>