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