Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[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      * Returns the child node at the specified index.\r
551      * @param {Number} index\r
552      * @return {Node}\r
553      */\r
554     item : function(index){\r
555         return this.childNodes[index];\r
556     },\r
557 \r
558     /**\r
559      * Replaces one child node in this node with another.\r
560      * @param {Node} newChild The replacement node\r
561      * @param {Node} oldChild The node to replace\r
562      * @return {Node} The replaced node\r
563      */\r
564     replaceChild : function(newChild, oldChild){\r
565         var s = oldChild ? oldChild.nextSibling : null;\r
566         this.removeChild(oldChild);\r
567         this.insertBefore(newChild, s);\r
568         return oldChild;\r
569     },\r
570 \r
571     /**\r
572      * Returns the index of a child node\r
573      * @param {Node} node\r
574      * @return {Number} The index of the node or -1 if it was not found\r
575      */\r
576     indexOf : function(child){\r
577         return this.childNodes.indexOf(child);\r
578     },\r
579 \r
580     /**\r
581      * Returns the tree this node is in.\r
582      * @return {Tree}\r
583      */\r
584     getOwnerTree : function(){\r
585         // if it doesn't have one, look for one\r
586         if(!this.ownerTree){\r
587             var p = this;\r
588             while(p){\r
589                 if(p.ownerTree){\r
590                     this.ownerTree = p.ownerTree;\r
591                     break;\r
592                 }\r
593                 p = p.parentNode;\r
594             }\r
595         }\r
596         return this.ownerTree;\r
597     },\r
598 \r
599     /**\r
600      * Returns depth of this node (the root node has a depth of 0)\r
601      * @return {Number}\r
602      */\r
603     getDepth : function(){\r
604         var depth = 0;\r
605         var p = this;\r
606         while(p.parentNode){\r
607             ++depth;\r
608             p = p.parentNode;\r
609         }\r
610         return depth;\r
611     },\r
612 \r
613     // private\r
614     setOwnerTree : function(tree, destroy){\r
615         // if it is a move, we need to update everyone\r
616         if(tree != this.ownerTree){\r
617             if(this.ownerTree){\r
618                 this.ownerTree.unregisterNode(this);\r
619             }\r
620             this.ownerTree = tree;\r
621             // If we're destroying, we don't need to recurse since it will be called on each child node\r
622             if(destroy !== true){\r
623                 Ext.each(this.childNodes, function(n){\r
624                     n.setOwnerTree(tree);\r
625                 });\r
626             }\r
627             if(tree){\r
628                 tree.registerNode(this);\r
629             }\r
630         }\r
631     },\r
632     \r
633     /**\r
634      * Changes the id of this node.\r
635      * @param {String} id The new id for the node.\r
636      */\r
637     setId: function(id){\r
638         if(id !== this.id){\r
639             var t = this.ownerTree;\r
640             if(t){\r
641                 t.unregisterNode(this);\r
642             }\r
643             this.id = this.attributes.id = id;\r
644             if(t){\r
645                 t.registerNode(this);\r
646             }\r
647             this.onIdChange(id);\r
648         }\r
649     },\r
650     \r
651     // private\r
652     onIdChange: Ext.emptyFn,\r
653 \r
654     /**\r
655      * Returns the path for this node. The path can be used to expand or select this node programmatically.\r
656      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)\r
657      * @return {String} The path\r
658      */\r
659     getPath : function(attr){\r
660         attr = attr || "id";\r
661         var p = this.parentNode;\r
662         var b = [this.attributes[attr]];\r
663         while(p){\r
664             b.unshift(p.attributes[attr]);\r
665             p = p.parentNode;\r
666         }\r
667         var sep = this.getOwnerTree().pathSeparator;\r
668         return sep + b.join(sep);\r
669     },\r
670 \r
671     /**\r
672      * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function\r
673      * will be the args provided or the current node. If the function returns false at any point,\r
674      * the bubble is stopped.\r
675      * @param {Function} fn The function to call\r
676      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.\r
677      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)\r
678      */\r
679     bubble : function(fn, scope, args){\r
680         var p = this;\r
681         while(p){\r
682             if(fn.apply(scope || p, args || [p]) === false){\r
683                 break;\r
684             }\r
685             p = p.parentNode;\r
686         }\r
687     },\r
688 \r
689     /**\r
690      * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function\r
691      * will be the args provided or the current node. If the function returns false at any point,\r
692      * the cascade is stopped on that branch.\r
693      * @param {Function} fn The function to call\r
694      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.\r
695      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)\r
696      */\r
697     cascade : function(fn, scope, args){\r
698         if(fn.apply(scope || this, args || [this]) !== false){\r
699             var cs = this.childNodes;\r
700             for(var i = 0, len = cs.length; i < len; i++) {\r
701                 cs[i].cascade(fn, scope, args);\r
702             }\r
703         }\r
704     },\r
705 \r
706     /**\r
707      * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function\r
708      * will be the args provided or the current node. If the function returns false at any point,\r
709      * the iteration stops.\r
710      * @param {Function} fn The function to call\r
711      * @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
712      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)\r
713      */\r
714     eachChild : function(fn, scope, args){\r
715         var cs = this.childNodes;\r
716         for(var i = 0, len = cs.length; i < len; i++) {\r
717                 if(fn.apply(scope || this, args || [cs[i]]) === false){\r
718                     break;\r
719                 }\r
720         }\r
721     },\r
722 \r
723     /**\r
724      * Finds the first child that has the attribute with the specified value.\r
725      * @param {String} attribute The attribute name\r
726      * @param {Mixed} value The value to search for\r
727      * @return {Node} The found child or null if none was found\r
728      */\r
729     findChild : function(attribute, value){\r
730         var cs = this.childNodes;\r
731         for(var i = 0, len = cs.length; i < len; i++) {\r
732                 if(cs[i].attributes[attribute] == value){\r
733                     return cs[i];\r
734                 }\r
735         }\r
736         return null;\r
737     },\r
738 \r
739     /**\r
740      * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.\r
741      * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.\r
742      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.\r
743      * @return {Node} The found child or null if none was found\r
744      */\r
745     findChildBy : function(fn, scope){\r
746         var cs = this.childNodes;\r
747         for(var i = 0, len = cs.length; i < len; i++) {\r
748                 if(fn.call(scope||cs[i], cs[i]) === true){\r
749                     return cs[i];\r
750                 }\r
751         }\r
752         return null;\r
753     },\r
754 \r
755     /**\r
756      * Sorts this nodes children using the supplied sort function.\r
757      * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.\r
758      * @param {Object} scope (optional)The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.\r
759      */\r
760     sort : function(fn, scope){\r
761         var cs = this.childNodes;\r
762         var len = cs.length;\r
763         if(len > 0){\r
764             var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;\r
765             cs.sort(sortFn);\r
766             for(var i = 0; i < len; i++){\r
767                 var n = cs[i];\r
768                 n.previousSibling = cs[i-1];\r
769                 n.nextSibling = cs[i+1];\r
770                 if(i === 0){\r
771                     this.setFirstChild(n);\r
772                 }\r
773                 if(i == len-1){\r
774                     this.setLastChild(n);\r
775                 }\r
776             }\r
777         }\r
778     },\r
779 \r
780     /**\r
781      * Returns true if this node is an ancestor (at any point) of the passed node.\r
782      * @param {Node} node\r
783      * @return {Boolean}\r
784      */\r
785     contains : function(node){\r
786         return node.isAncestor(this);\r
787     },\r
788 \r
789     /**\r
790      * Returns true if the passed node is an ancestor (at any point) of this node.\r
791      * @param {Node} node\r
792      * @return {Boolean}\r
793      */\r
794     isAncestor : function(node){\r
795         var p = this.parentNode;\r
796         while(p){\r
797             if(p == node){\r
798                 return true;\r
799             }\r
800             p = p.parentNode;\r
801         }\r
802         return false;\r
803     },\r
804 \r
805     toString : function(){\r
806         return "[Node"+(this.id?" "+this.id:"")+"]";\r
807     }\r
808 });</pre>    \r
809 </body>\r
810 </html>