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