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