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