Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / NodeInterface.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-data-NodeInterface'>/**
19 </span> * @class Ext.data.NodeInterface
20  * This class is meant to be used as a set of methods that are applied to the prototype of a
21  * Record to decorate it with a Node API. This means that models used in conjunction with a tree
22  * will have all of the tree related methods available on the model. In general this class will
23  * not be used directly by the developer.
24  */
25 Ext.define('Ext.data.NodeInterface', {
26     requires: ['Ext.data.Field'],
27     
28     statics: {
29 <span id='Ext-data-NodeInterface-method-decorate'>        /**
30 </span>         * This method allows you to decorate a Record's prototype to implement the NodeInterface.
31          * This adds a set of methods, new events, new properties and new fields on every Record
32          * with the same Model as the passed Record.
33          * @param {Ext.data.Record} record The Record you want to decorate the prototype of.
34          * @static
35          */
36         decorate: function(record) {
37             if (!record.isNode) {
38                 // Apply the methods and fields to the prototype
39                 // @TODO: clean this up to use proper class system stuff
40                 var mgr = Ext.ModelManager,
41                     modelName = record.modelName,
42                     modelClass = mgr.getModel(modelName),
43                     idName = modelClass.prototype.idProperty,
44                     newFields = [],
45                     i, newField, len;
46
47                 // Start by adding the NodeInterface methods to the Model's prototype
48                 modelClass.override(this.getPrototypeBody());
49                 newFields = this.applyFields(modelClass, [
50                     {name: idName,       type: 'string',  defaultValue: null},
51                     {name: 'parentId',   type: 'string',  defaultValue: null},
52                     {name: 'index',      type: 'int',     defaultValue: null},
53                     {name: 'depth',      type: 'int',     defaultValue: 0}, 
54                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
55                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
56                     {name: 'checked',    type: 'auto',    defaultValue: null},
57                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
58                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
59                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
60                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
61                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
62                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
63                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
64                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
65                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
66                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
67                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
68                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
69                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
70                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
71                 ]);
72
73                 len = newFields.length;
74                 // Set default values
75                 for (i = 0; i &lt; len; ++i) {
76                     newField = newFields[i];
77                     if (record.get(newField.name) === undefined) {
78                         record.data[newField.name] = newField.defaultValue;
79                     }
80                 }
81             }
82             
83             Ext.applyIf(record, {
84                 firstChild: null,
85                 lastChild: null,
86                 parentNode: null,
87                 previousSibling: null,
88                 nextSibling: null,
89                 childNodes: []
90             });
91             // Commit any fields so the record doesn't show as dirty initially
92             record.commit(true);
93             
94             record.enableBubble([
95 <span id='Ext-data-NodeInterface-event-append'>                /**
96 </span>                 * @event append
97                  * Fires when a new child node is appended
98                  * @param {Node} this This node
99                  * @param {Node} node The newly appended node
100                  * @param {Number} index The index of the newly appended node
101                  */
102                 &quot;append&quot;,
103
104 <span id='Ext-data-NodeInterface-event-remove'>                /**
105 </span>                 * @event remove
106                  * Fires when a child node is removed
107                  * @param {Node} this This node
108                  * @param {Node} node The removed node
109                  */
110                 &quot;remove&quot;,
111
112 <span id='Ext-data-NodeInterface-event-move'>                /**
113 </span>                 * @event move
114                  * Fires when this node is moved to a new location in the tree
115                  * @param {Node} this This node
116                  * @param {Node} oldParent The old parent of this node
117                  * @param {Node} newParent The new parent of this node
118                  * @param {Number} index The index it was moved to
119                  */
120                 &quot;move&quot;,
121
122 <span id='Ext-data-NodeInterface-event-insert'>                /**
123 </span>                 * @event insert
124                  * Fires when a new child node is inserted.
125                  * @param {Node} this This node
126                  * @param {Node} node The child node inserted
127                  * @param {Node} refNode The child node the node was inserted before
128                  */
129                 &quot;insert&quot;,
130
131 <span id='Ext-data-NodeInterface-event-beforeappend'>                /**
132 </span>                 * @event beforeappend
133                  * Fires before a new child is appended, return false to cancel the append.
134                  * @param {Node} this This node
135                  * @param {Node} node The child node to be appended
136                  */
137                 &quot;beforeappend&quot;,
138
139 <span id='Ext-data-NodeInterface-event-beforeremove'>                /**
140 </span>                 * @event beforeremove
141                  * Fires before a child is removed, return false to cancel the remove.
142                  * @param {Node} this This node
143                  * @param {Node} node The child node to be removed
144                  */
145                 &quot;beforeremove&quot;,
146
147 <span id='Ext-data-NodeInterface-event-beforemove'>                /**
148 </span>                 * @event beforemove
149                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
150                  * @param {Node} this This node
151                  * @param {Node} oldParent The parent of this node
152                  * @param {Node} newParent The new parent this node is moving to
153                  * @param {Number} index The index it is being moved to
154                  */
155                 &quot;beforemove&quot;,
156
157 <span id='Ext-data-NodeInterface-event-beforeinsert'>                 /**
158 </span>                  * @event beforeinsert
159                   * Fires before a new child is inserted, return false to cancel the insert.
160                   * @param {Node} this This node
161                   * @param {Node} node The child node to be inserted
162                   * @param {Node} refNode The child node the node is being inserted before
163                   */
164                 &quot;beforeinsert&quot;,
165                 
166 <span id='Ext-data-NodeInterface-event-expand'>                /**
167 </span>                 * @event expand
168                  * Fires when this node is expanded.
169                  * @param {Node} this The expanding node
170                  */
171                 &quot;expand&quot;,
172                 
173 <span id='Ext-data-NodeInterface-event-collapse'>                /**
174 </span>                 * @event collapse
175                  * Fires when this node is collapsed.
176                  * @param {Node} this The collapsing node
177                  */
178                 &quot;collapse&quot;,
179                 
180 <span id='Ext-data-NodeInterface-event-beforeexpand'>                /**
181 </span>                 * @event beforeexpand
182                  * Fires before this node is expanded.
183                  * @param {Node} this The expanding node
184                  */
185                 &quot;beforeexpand&quot;,
186                 
187 <span id='Ext-data-NodeInterface-event-beforecollapse'>                /**
188 </span>                 * @event beforecollapse
189                  * Fires before this node is collapsed.
190                  * @param {Node} this The collapsing node
191                  */
192                 &quot;beforecollapse&quot;,
193                 
194 <span id='Ext-data-NodeInterface-event-sort'>                /**
195 </span>                 * @event sort
196                  * Fires when this node's childNodes are sorted.
197                  * @param {Node} this This node.
198                  * @param {Array} The childNodes of this node.
199                  */
200                 &quot;sort&quot;
201             ]);
202             
203             return record;
204         },
205         
206         applyFields: function(modelClass, addFields) {
207             var modelPrototype = modelClass.prototype,
208                 fields = modelPrototype.fields,
209                 keys = fields.keys,
210                 ln = addFields.length,
211                 addField, i, name,
212                 newFields = [];
213                 
214             for (i = 0; i &lt; ln; i++) {
215                 addField = addFields[i];
216                 if (!Ext.Array.contains(keys, addField.name)) {
217                     addField = Ext.create('data.field', addField);
218                     
219                     newFields.push(addField);
220                     fields.add(addField);
221                 }
222             }
223             
224             return newFields;
225         },
226         
227         getPrototypeBody: function() {
228             return {
229                 isNode: true,
230
231 <span id='Ext-data-NodeInterface-method-createNode'>                /**
232 </span>                 * Ensures that the passed object is an instance of a Record with the NodeInterface applied
233                  * @return {Boolean}
234                  */
235                 createNode: function(node) {
236                     if (Ext.isObject(node) &amp;&amp; !node.isModel) {
237                         node = Ext.ModelManager.create(node, this.modelName);
238                     }
239                     // Make sure the node implements the node interface
240                     return Ext.data.NodeInterface.decorate(node);
241                 },
242                 
243 <span id='Ext-data-NodeInterface-method-isLeaf'>                /**
244 </span>                 * Returns true if this node is a leaf
245                  * @return {Boolean}
246                  */
247                 isLeaf : function() {
248                     return this.get('leaf') === true;
249                 },
250
251 <span id='Ext-data-NodeInterface-method-setFirstChild'>                /**
252 </span>                 * Sets the first child of this node
253                  * @private
254                  * @param {Ext.data.NodeInterface} node
255                  */
256                 setFirstChild : function(node) {
257                     this.firstChild = node;
258                 },
259
260 <span id='Ext-data-NodeInterface-method-setLastChild'>                /**
261 </span>                 * Sets the last child of this node
262                  * @private
263                  * @param {Ext.data.NodeInterface} node
264                  */
265                 setLastChild : function(node) {
266                     this.lastChild = node;
267                 },
268
269 <span id='Ext-data-NodeInterface-method-updateInfo'>                /**
270 </span>                 * Updates general data of this node like isFirst, isLast, depth. This
271                  * method is internally called after a node is moved. This shouldn't
272                  * have to be called by the developer unless they are creating custom
273                  * Tree plugins.
274                  * @return {Boolean}
275                  */
276                 updateInfo: function(silent) {
277                     var me = this,
278                         isRoot = me.isRoot(),
279                         parentNode = me.parentNode,
280                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
281                         isLast = (!parentNode ? true : parentNode.lastChild == me),
282                         depth = 0,
283                         parent = me,
284                         children = me.childNodes,
285                         len = children.length,
286                         i = 0;
287
288                     while (parent.parentNode) {
289                         ++depth;
290                         parent = parent.parentNode;
291                     }                                            
292                     
293                     me.beginEdit();
294                     me.set({
295                         isFirst: isFirst,
296                         isLast: isLast,
297                         depth: depth,
298                         index: parentNode ? parentNode.indexOf(me) : 0,
299                         parentId: parentNode ? parentNode.getId() : null
300                     });
301                     me.endEdit(silent);
302                     if (silent) {
303                         me.commit();
304                     }
305                     
306                     for (i = 0; i &lt; len; i++) {
307                         children[i].updateInfo(silent);
308                     }
309                 },
310
311 <span id='Ext-data-NodeInterface-method-isLast'>                /**
312 </span>                 * Returns true if this node is the last child of its parent
313                  * @return {Boolean}
314                  */
315                 isLast : function() {
316                    return this.get('isLast');
317                 },
318
319 <span id='Ext-data-NodeInterface-method-isFirst'>                /**
320 </span>                 * Returns true if this node is the first child of its parent
321                  * @return {Boolean}
322                  */
323                 isFirst : function() {
324                    return this.get('isFirst');
325                 },
326
327 <span id='Ext-data-NodeInterface-method-hasChildNodes'>                /**
328 </span>                 * Returns true if this node has one or more child nodes, else false.
329                  * @return {Boolean}
330                  */
331                 hasChildNodes : function() {
332                     return !this.isLeaf() &amp;&amp; this.childNodes.length &gt; 0;
333                 },
334
335 <span id='Ext-data-NodeInterface-method-isExpandable'>                /**
336 </span>                 * Returns true if this node has one or more child nodes, or if the &lt;tt&gt;expandable&lt;/tt&gt;
337                  * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
338                  * @return {Boolean}
339                  */
340                 isExpandable : function() {
341                     var me = this;
342                     
343                     if (me.get('expandable')) {
344                         return !(me.isLeaf() || (me.isLoaded() &amp;&amp; !me.hasChildNodes()));
345                     }
346                     return false;
347                 },
348
349 <span id='Ext-data-NodeInterface-method-appendChild'>                /**
350 </span>                 * &lt;p&gt;Insert node(s) as the last child node of this node.&lt;/p&gt;
351                  * &lt;p&gt;If the node was previously a child node of another parent node, it will be removed from that node first.&lt;/p&gt;
352                  * @param {Node/Array} node The node or Array of nodes to append
353                  * @return {Node} The appended node if single append, or null if an array was passed
354                  */
355                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
356                     var me = this,
357                         i, ln,
358                         index,
359                         oldParent,
360                         ps;
361
362                     // if passed an array or multiple args do them one by one
363                     if (Ext.isArray(node)) {
364                         for (i = 0, ln = node.length; i &lt; ln; i++) {
365                             me.appendChild(node[i]);
366                         }
367                     } else {
368                         // Make sure it is a record
369                         node = me.createNode(node);
370                         
371                         if (suppressEvents !== true &amp;&amp; me.fireEvent(&quot;beforeappend&quot;, me, node) === false) {
372                             return false;                         
373                         }
374
375                         index = me.childNodes.length;
376                         oldParent = node.parentNode;
377
378                         // it's a move, make sure we move it cleanly
379                         if (oldParent) {
380                             if (suppressEvents !== true &amp;&amp; node.fireEvent(&quot;beforemove&quot;, node, oldParent, me, index) === false) {
381                                 return false;
382                             }
383                             oldParent.removeChild(node, null, false, true);
384                         }
385
386                         index = me.childNodes.length;
387                         if (index === 0) {
388                             me.setFirstChild(node);
389                         }
390
391                         me.childNodes.push(node);
392                         node.parentNode = me;
393                         node.nextSibling = null;
394
395                         me.setLastChild(node);
396                                                 
397                         ps = me.childNodes[index - 1];
398                         if (ps) {
399                             node.previousSibling = ps;
400                             ps.nextSibling = node;
401                             ps.updateInfo(suppressNodeUpdate);
402                         } else {
403                             node.previousSibling = null;
404                         }
405
406                         node.updateInfo(suppressNodeUpdate);
407                         
408                         // As soon as we append a child to this node, we are loaded
409                         if (!me.isLoaded()) {
410                             me.set('loaded', true);                            
411                         }
412                         // If this node didnt have any childnodes before, update myself
413                         else if (me.childNodes.length === 1) {
414                             me.set('loaded', me.isLoaded());
415                         }
416                         
417                         if (suppressEvents !== true) {
418                             me.fireEvent(&quot;append&quot;, me, node, index);
419
420                             if (oldParent) {
421                                 node.fireEvent(&quot;move&quot;, node, oldParent, me, index);
422                             }                            
423                         }
424
425                         return node;
426                     }
427                 },
428                 
429 <span id='Ext-data-NodeInterface-method-getBubbleTarget'>                /**
430 </span>                 * Returns the bubble target for this node
431                  * @private
432                  * @return {Object} The bubble target
433                  */
434                 getBubbleTarget: function() {
435                     return this.parentNode;
436                 },
437
438 <span id='Ext-data-NodeInterface-method-removeChild'>                /**
439 </span>                 * Removes a child node from this node.
440                  * @param {Node} node The node to remove
441                  * @param {Boolean} destroy &lt;tt&gt;true&lt;/tt&gt; to destroy the node upon removal. Defaults to &lt;tt&gt;false&lt;/tt&gt;.
442                  * @return {Node} The removed node
443                  */
444                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
445                     var me = this,
446                         index = me.indexOf(node);
447                     
448                     if (index == -1 || (suppressEvents !== true &amp;&amp; me.fireEvent(&quot;beforeremove&quot;, me, node) === false)) {
449                         return false;
450                     }
451
452                     // remove it from childNodes collection
453                     Ext.Array.erase(me.childNodes, index, 1);
454
455                     // update child refs
456                     if (me.firstChild == node) {
457                         me.setFirstChild(node.nextSibling);
458                     }
459                     if (me.lastChild == node) {
460                         me.setLastChild(node.previousSibling);
461                     }
462                     
463                     // update siblings
464                     if (node.previousSibling) {
465                         node.previousSibling.nextSibling = node.nextSibling;
466                         node.previousSibling.updateInfo(suppressNodeUpdate);
467                     }
468                     if (node.nextSibling) {
469                         node.nextSibling.previousSibling = node.previousSibling;
470                         node.nextSibling.updateInfo(suppressNodeUpdate);
471                     }
472
473                     if (suppressEvents !== true) {
474                         me.fireEvent(&quot;remove&quot;, me, node);
475                     }
476                     
477                     
478                     // If this node suddenly doesnt have childnodes anymore, update myself
479                     if (!me.childNodes.length) {
480                         me.set('loaded', me.isLoaded());
481                     }
482                     
483                     if (destroy) {
484                         node.destroy(true);
485                     } else {
486                         node.clear();
487                     }
488
489                     return node;
490                 },
491
492 <span id='Ext-data-NodeInterface-method-copy'>                /**
493 </span>                 * Creates a copy (clone) of this Node.
494                  * @param {String} id (optional) A new id, defaults to this Node's id. See &lt;code&gt;{@link #id}&lt;/code&gt;.
495                  * @param {Boolean} deep (optional) &lt;p&gt;If passed as &lt;code&gt;true&lt;/code&gt;, all child Nodes are recursively copied into the new Node.&lt;/p&gt;
496                  * &lt;p&gt;If omitted or false, the copy will have no child Nodes.&lt;/p&gt;
497                  * @return {Node} A copy of this Node.
498                  */
499                 copy: function(newId, deep) {
500                     var me = this,
501                         result = me.callOverridden(arguments),
502                         len = me.childNodes ? me.childNodes.length : 0,
503                         i;
504
505                     // Move child nodes across to the copy if required
506                     if (deep) {
507                         for (i = 0; i &lt; len; i++) {
508                             result.appendChild(me.childNodes[i].copy(true));
509                         }
510                     }
511                     return result;
512                 },
513
514 <span id='Ext-data-NodeInterface-method-clear'>                /**
515 </span>                 * Clear the node.
516                  * @private
517                  * @param {Boolean} destroy True to destroy the node.
518                  */
519                 clear : function(destroy) {
520                     var me = this;
521                     
522                     // clear any references from the node
523                     me.parentNode = me.previousSibling = me.nextSibling = null;
524                     if (destroy) {
525                         me.firstChild = me.lastChild = null;
526                     }
527                 },
528
529 <span id='Ext-data-NodeInterface-method-destroy'>                /**
530 </span>                 * Destroys the node.
531                  */
532                 destroy : function(silent) {
533                     /*
534                      * Silent is to be used in a number of cases
535                      * 1) When setRoot is called.
536                      * 2) When destroy on the tree is called
537                      * 3) For destroying child nodes on a node
538                      */
539                     var me = this,
540                         options = me.destroyOptions;
541                     
542                     if (silent === true) {
543                         me.clear(true);
544                         Ext.each(me.childNodes, function(n) {
545                             n.destroy(true);
546                         });
547                         me.childNodes = null;
548                         delete me.destroyOptions;
549                         me.callOverridden([options]);
550                     } else {
551                         me.destroyOptions = silent;
552                         // overridden method will be called, since remove will end up calling destroy(true);
553                         me.remove(true);
554                     }
555                 },
556
557 <span id='Ext-data-NodeInterface-method-insertBefore'>                /**
558 </span>                 * Inserts the first node before the second node in this nodes childNodes collection.
559                  * @param {Node} node The node to insert
560                  * @param {Node} refNode The node to insert before (if null the node is appended)
561                  * @return {Node} The inserted node
562                  */
563                 insertBefore : function(node, refNode, suppressEvents) {
564                     var me = this,
565                         index     = me.indexOf(refNode),
566                         oldParent = node.parentNode,
567                         refIndex  = index,
568                         ps;
569                     
570                     if (!refNode) { // like standard Dom, refNode can be null for append
571                         return me.appendChild(node);
572                     }
573                     
574                     // nothing to do
575                     if (node == refNode) {
576                         return false;
577                     }
578
579                     // Make sure it is a record with the NodeInterface
580                     node = me.createNode(node);
581                     
582                     if (suppressEvents !== true &amp;&amp; me.fireEvent(&quot;beforeinsert&quot;, me, node, refNode) === false) {
583                         return false;
584                     }
585                     
586                     // when moving internally, indexes will change after remove
587                     if (oldParent == me &amp;&amp; me.indexOf(node) &lt; index) {
588                         refIndex--;
589                     }
590
591                     // it's a move, make sure we move it cleanly
592                     if (oldParent) {
593                         if (suppressEvents !== true &amp;&amp; node.fireEvent(&quot;beforemove&quot;, node, oldParent, me, index, refNode) === false) {
594                             return false;
595                         }
596                         oldParent.removeChild(node);
597                     }
598
599                     if (refIndex === 0) {
600                         me.setFirstChild(node);
601                     }
602
603                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
604                     node.parentNode = me;
605                     
606                     node.nextSibling = refNode;
607                     refNode.previousSibling = node;
608                     
609                     ps = me.childNodes[refIndex - 1];
610                     if (ps) {
611                         node.previousSibling = ps;
612                         ps.nextSibling = node;
613                         ps.updateInfo();
614                     } else {
615                         node.previousSibling = null;
616                     }
617                     
618                     node.updateInfo();
619                     
620                     if (!me.isLoaded()) {
621                         me.set('loaded', true);                            
622                     }    
623                     // If this node didnt have any childnodes before, update myself
624                     else if (me.childNodes.length === 1) {
625                         me.set('loaded', me.isLoaded());
626                     }
627
628                     if (suppressEvents !== true) {
629                         me.fireEvent(&quot;insert&quot;, me, node, refNode);
630
631                         if (oldParent) {
632                             node.fireEvent(&quot;move&quot;, node, oldParent, me, refIndex, refNode);
633                         }                        
634                     }
635
636                     return node;
637                 },
638                 
639 <span id='Ext-data-NodeInterface-method-insertChild'>                /**
640 </span>                 * Insert a node into this node
641                  * @param {Number} index The zero-based index to insert the node at
642                  * @param {Ext.data.Model} node The node to insert
643                  * @return {Ext.data.Record} The record you just inserted
644                  */    
645                 insertChild: function(index, node) {
646                     var sibling = this.childNodes[index];
647                     if (sibling) {
648                         return this.insertBefore(node, sibling);
649                     }
650                     else {
651                         return this.appendChild(node);
652                     }
653                 },
654
655 <span id='Ext-data-NodeInterface-method-remove'>                /**
656 </span>                 * Removes this node from its parent
657                  * @param {Boolean} destroy &lt;tt&gt;true&lt;/tt&gt; to destroy the node upon removal. Defaults to &lt;tt&gt;false&lt;/tt&gt;.
658                  * @return {Node} this
659                  */
660                 remove : function(destroy, suppressEvents) {
661                     var parentNode = this.parentNode;
662
663                     if (parentNode) {
664                         parentNode.removeChild(this, destroy, suppressEvents, true);
665                     }
666                     return this;
667                 },
668
669 <span id='Ext-data-NodeInterface-method-removeAll'>                /**
670 </span>                 * Removes all child nodes from this node.
671                  * @param {Boolean} destroy &lt;tt&gt;true&lt;/tt&gt; to destroy the node upon removal. Defaults to &lt;tt&gt;false&lt;/tt&gt;.
672                  * @return {Node} this
673                  */
674                 removeAll : function(destroy, suppressEvents) {
675                     var cn = this.childNodes,
676                         n;
677
678                     while ((n = cn[0])) {
679                         this.removeChild(n, destroy, suppressEvents);
680                     }
681                     return this;
682                 },
683
684 <span id='Ext-data-NodeInterface-method-getChildAt'>                /**
685 </span>                 * Returns the child node at the specified index.
686                  * @param {Number} index
687                  * @return {Node}
688                  */
689                 getChildAt : function(index) {
690                     return this.childNodes[index];
691                 },
692
693 <span id='Ext-data-NodeInterface-method-replaceChild'>                /**
694 </span>                 * Replaces one child node in this node with another.
695                  * @param {Node} newChild The replacement node
696                  * @param {Node} oldChild The node to replace
697                  * @return {Node} The replaced node
698                  */
699                 replaceChild : function(newChild, oldChild, suppressEvents) {
700                     var s = oldChild ? oldChild.nextSibling : null;
701                     
702                     this.removeChild(oldChild, suppressEvents);
703                     this.insertBefore(newChild, s, suppressEvents);
704                     return oldChild;
705                 },
706
707 <span id='Ext-data-NodeInterface-method-indexOf'>                /**
708 </span>                 * Returns the index of a child node
709                  * @param {Node} node
710                  * @return {Number} The index of the node or -1 if it was not found
711                  */
712                 indexOf : function(child) {
713                     return Ext.Array.indexOf(this.childNodes, child);
714                 },
715
716 <span id='Ext-data-NodeInterface-method-getDepth'>                /**
717 </span>                 * Returns depth of this node (the root node has a depth of 0)
718                  * @return {Number}
719                  */
720                 getDepth : function() {
721                     return this.get('depth');
722                 },
723
724 <span id='Ext-data-NodeInterface-method-bubble'>                /**
725 </span>                 * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
726                  * will be the args provided or the current node. If the function returns false at any point,
727                  * the bubble is stopped.
728                  * @param {Function} fn The function to call
729                  * @param {Object} scope (optional) The scope (&lt;code&gt;this&lt;/code&gt; reference) in which the function is executed. Defaults to the current Node.
730                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
731                  */
732                 bubble : function(fn, scope, args) {
733                     var p = this;
734                     while (p) {
735                         if (fn.apply(scope || p, args || [p]) === false) {
736                             break;
737                         }
738                         p = p.parentNode;
739                     }
740                 },
741
742                 //&lt;deprecated since=0.99&gt;
743                 cascade: function() {
744                     if (Ext.isDefined(Ext.global.console)) {
745                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
746                     }
747                     return this.cascadeBy.apply(this, arguments);
748                 },
749                 //&lt;/deprecated&gt;
750
751 <span id='Ext-data-NodeInterface-method-cascadeBy'>                /**
752 </span>                 * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
753                  * will be the args provided or the current node. If the function returns false at any point,
754                  * the cascade is stopped on that branch.
755                  * @param {Function} fn The function to call
756                  * @param {Object} scope (optional) The scope (&lt;code&gt;this&lt;/code&gt; reference) in which the function is executed. Defaults to the current Node.
757                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
758                  */
759                 cascadeBy : function(fn, scope, args) {
760                     if (fn.apply(scope || this, args || [this]) !== false) {
761                         var childNodes = this.childNodes,
762                             length     = childNodes.length,
763                             i;
764
765                         for (i = 0; i &lt; length; i++) {
766                             childNodes[i].cascadeBy(fn, scope, args);
767                         }
768                     }
769                 },
770
771 <span id='Ext-data-NodeInterface-method-eachChild'>                /**
772 </span>                 * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
773                  * will be the args provided or the current node. If the function returns false at any point,
774                  * the iteration stops.
775                  * @param {Function} fn The function to call
776                  * @param {Object} scope (optional) The scope (&lt;code&gt;this&lt;/code&gt; reference) in which the function is executed. Defaults to the current Node in the iteration.
777                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
778                  */
779                 eachChild : function(fn, scope, args) {
780                     var childNodes = this.childNodes,
781                         length     = childNodes.length,
782                         i;
783
784                     for (i = 0; i &lt; length; i++) {
785                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
786                             break;
787                         }
788                     }
789                 },
790
791 <span id='Ext-data-NodeInterface-method-findChild'>                /**
792 </span>                 * Finds the first child that has the attribute with the specified value.
793                  * @param {String} attribute The attribute name
794                  * @param {Mixed} value The value to search for
795                  * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
796                  * @return {Node} The found child or null if none was found
797                  */
798                 findChild : function(attribute, value, deep) {
799                     return this.findChildBy(function() {
800                         return this.get(attribute) == value;
801                     }, null, deep);
802                 },
803
804 <span id='Ext-data-NodeInterface-method-findChildBy'>                /**
805 </span>                 * Finds the first child by a custom function. The child matches if the function passed returns &lt;code&gt;true&lt;/code&gt;.
806                  * @param {Function} fn A function which must return &lt;code&gt;true&lt;/code&gt; if the passed Node is the required Node.
807                  * @param {Object} scope (optional) The scope (&lt;code&gt;this&lt;/code&gt; reference) in which the function is executed. Defaults to the Node being tested.
808                  * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
809                  * @return {Node} The found child or null if none was found
810                  */
811                 findChildBy : function(fn, scope, deep) {
812                     var cs = this.childNodes,
813                         len = cs.length,
814                         i = 0, n, res;
815
816                     for (; i &lt; len; i++) {
817                         n = cs[i];
818                         if (fn.call(scope || n, n) === true) {
819                             return n;
820                         }
821                         else if (deep) {
822                             res = n.findChildBy(fn, scope, deep);
823                             if (res !== null) {
824                                 return res;
825                             }
826                         }
827                     }
828
829                     return null;
830                 },
831
832 <span id='Ext-data-NodeInterface-method-contains'>                /**
833 </span>                 * Returns true if this node is an ancestor (at any point) of the passed node.
834                  * @param {Node} node
835                  * @return {Boolean}
836                  */
837                 contains : function(node) {
838                     return node.isAncestor(this);
839                 },
840
841 <span id='Ext-data-NodeInterface-method-isAncestor'>                /**
842 </span>                 * Returns true if the passed node is an ancestor (at any point) of this node.
843                  * @param {Node} node
844                  * @return {Boolean}
845                  */
846                 isAncestor : function(node) {
847                     var p = this.parentNode;
848                     while (p) {
849                         if (p == node) {
850                             return true;
851                         }
852                         p = p.parentNode;
853                     }
854                     return false;
855                 },
856
857 <span id='Ext-data-NodeInterface-method-sort'>                /**
858 </span>                 * Sorts this nodes children using the supplied sort function.
859                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
860                  * @param {Boolean} recursive Whether or not to apply this sort recursively
861                  * @param {Boolean} suppressEvent Set to true to not fire a sort event.
862                  */
863                 sort : function(sortFn, recursive, suppressEvent) {
864                     var cs  = this.childNodes,
865                         ln = cs.length,
866                         i, n;
867                     
868                     if (ln &gt; 0) {
869                         Ext.Array.sort(cs, sortFn);
870                         for (i = 0; i &lt; ln; i++) {
871                             n = cs[i];
872                             n.previousSibling = cs[i-1];
873                             n.nextSibling = cs[i+1];
874                         
875                             if (i === 0) {
876                                 this.setFirstChild(n);
877                                 n.updateInfo();
878                             }
879                             if (i == ln - 1) {
880                                 this.setLastChild(n);
881                                 n.updateInfo();
882                             }
883                             if (recursive &amp;&amp; !n.isLeaf()) {
884                                 n.sort(sortFn, true, true);
885                             }
886                         }
887                         
888                         if (suppressEvent !== true) {
889                             this.fireEvent('sort', this, cs);
890                         }
891                     }
892                 },
893                         
894 <span id='Ext-data-NodeInterface-method-isExpanded'>                /**
895 </span>                 * Returns true if this node is expaned
896                  * @return {Boolean}
897                  */        
898                 isExpanded: function() {
899                     return this.get('expanded');
900                 },
901                 
902 <span id='Ext-data-NodeInterface-method-isLoaded'>                /**
903 </span>                 * Returns true if this node is loaded
904                  * @return {Boolean}
905                  */ 
906                 isLoaded: function() {
907                     return this.get('loaded');
908                 },
909
910 <span id='Ext-data-NodeInterface-method-isLoading'>                /**
911 </span>                 * Returns true if this node is loading
912                  * @return {Boolean}
913                  */ 
914                 isLoading: function() {
915                     return this.get('loading');
916                 },
917                                 
918 <span id='Ext-data-NodeInterface-method-isRoot'>                /**
919 </span>                 * Returns true if this node is the root node
920                  * @return {Boolean}
921                  */ 
922                 isRoot: function() {
923                     return !this.parentNode;
924                 },
925                 
926 <span id='Ext-data-NodeInterface-method-isVisible'>                /**
927 </span>                 * Returns true if this node is visible
928                  * @return {Boolean}
929                  */ 
930                 isVisible: function() {
931                     var parent = this.parentNode;
932                     while (parent) {
933                         if (!parent.isExpanded()) {
934                             return false;
935                         }
936                         parent = parent.parentNode;
937                     }
938                     return true;
939                 },
940                 
941 <span id='Ext-data-NodeInterface-method-expand'>                /**
942 </span>                 * Expand this node.
943                  * @param {Function} recursive (Optional) True to recursively expand all the children
944                  * @param {Function} callback (Optional) The function to execute once the expand completes
945                  * @param {Object} scope (Optional) The scope to run the callback in
946                  */
947                 expand: function(recursive, callback, scope) {
948                     var me = this;
949
950                     // all paths must call the callback (eventually) or things like
951                     // selectPath fail
952
953                     // First we start by checking if this node is a parent
954                     if (!me.isLeaf()) {
955                         // Now we check if this record is already expanding or expanded
956                         if (!me.isLoading() &amp;&amp; !me.isExpanded()) {
957                             // The TreeStore actually listens for the beforeexpand method and checks
958                             // whether we have to asynchronously load the children from the server
959                             // first. Thats why we pass a callback function to the event that the
960                             // store can call once it has loaded and parsed all the children.
961                             me.fireEvent('beforeexpand', me, function() {
962                                 me.set('expanded', true); 
963                                 me.fireEvent('expand', me, me.childNodes, false);
964                                 
965                                 // Call the expandChildren method if recursive was set to true 
966                                 if (recursive) {
967                                     me.expandChildren(true, callback, scope);
968                                 }
969                                 else {
970                                     Ext.callback(callback, scope || me, [me.childNodes]);                                
971                                 }
972                             }, me);                            
973                         }
974                         // If it is is already expanded but we want to recursively expand then call expandChildren
975                         else if (recursive) {
976                             me.expandChildren(true, callback, scope);
977                         }
978                         else {
979                             Ext.callback(callback, scope || me, [me.childNodes]);
980                         }
981
982                         // TODO - if the node isLoading, we probably need to defer the
983                         // callback until it is loaded (e.g., selectPath would need us
984                         // to not make the callback until the childNodes exist).
985                     }
986                     // If it's not then we fire the callback right away
987                     else {
988                         Ext.callback(callback, scope || me); // leaf = no childNodes
989                     }
990                 },
991                 
992 <span id='Ext-data-NodeInterface-method-expandChildren'>                /**
993 </span>                 * Expand all the children of this node.
994                  * @param {Function} recursive (Optional) True to recursively expand all the children
995                  * @param {Function} callback (Optional) The function to execute once all the children are expanded
996                  * @param {Object} scope (Optional) The scope to run the callback in
997                  */
998                 expandChildren: function(recursive, callback, scope) {
999                     var me = this,
1000                         i = 0,
1001                         nodes = me.childNodes,
1002                         ln = nodes.length,
1003                         node,
1004                         expanding = 0;
1005
1006                     for (; i &lt; ln; ++i) {
1007                         node = nodes[i];
1008                         if (!node.isLeaf() &amp;&amp; !node.isExpanded()) {
1009                             expanding++;
1010                             nodes[i].expand(recursive, function () {
1011                                 expanding--;
1012                                 if (callback &amp;&amp; !expanding) {
1013                                     Ext.callback(callback, scope || me, [me.childNodes]); 
1014                                 }
1015                             });                            
1016                         }
1017                     }
1018                     
1019                     if (!expanding &amp;&amp; callback) {
1020                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
1021                 },
1022
1023 <span id='Ext-data-NodeInterface-method-collapse'>                /**
1024 </span>                 * Collapse this node.
1025                  * @param {Function} recursive (Optional) True to recursively collapse all the children
1026                  * @param {Function} callback (Optional) The function to execute once the collapse completes
1027                  * @param {Object} scope (Optional) The scope to run the callback in
1028                  */
1029                 collapse: function(recursive, callback, scope) {
1030                     var me = this;
1031
1032                     // First we start by checking if this node is a parent
1033                     if (!me.isLeaf()) {
1034                         // Now we check if this record is already collapsing or collapsed
1035                         if (!me.collapsing &amp;&amp; me.isExpanded()) {
1036                             me.fireEvent('beforecollapse', me, function() {
1037                                 me.set('expanded', false); 
1038                                 me.fireEvent('collapse', me, me.childNodes, false);
1039                                 
1040                                 // Call the collapseChildren method if recursive was set to true 
1041                                 if (recursive) {
1042                                     me.collapseChildren(true, callback, scope);
1043                                 }
1044                                 else {
1045                                     Ext.callback(callback, scope || me, [me.childNodes]);                                
1046                                 }
1047                             }, me);                            
1048                         }
1049                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
1050                         else if (recursive) {
1051                             me.collapseChildren(true, callback, scope);
1052                         }
1053                     }
1054                     // If it's not then we fire the callback right away
1055                     else {
1056                         Ext.callback(callback, scope || me, [me.childNodes]); 
1057                     }
1058                 },
1059                 
1060 <span id='Ext-data-NodeInterface-method-collapseChildren'>                /**
1061 </span>                 * Collapse all the children of this node.
1062                  * @param {Function} recursive (Optional) True to recursively collapse all the children
1063                  * @param {Function} callback (Optional) The function to execute once all the children are collapsed
1064                  * @param {Object} scope (Optional) The scope to run the callback in
1065                  */
1066                 collapseChildren: function(recursive, callback, scope) {
1067                     var me = this,
1068                         i = 0,
1069                         nodes = me.childNodes,
1070                         ln = nodes.length,
1071                         node,
1072                         collapsing = 0;
1073
1074                     for (; i &lt; ln; ++i) {
1075                         node = nodes[i];
1076                         if (!node.isLeaf() &amp;&amp; node.isExpanded()) {
1077                             collapsing++;
1078                             nodes[i].collapse(recursive, function () {
1079                                 collapsing--;
1080                                 if (callback &amp;&amp; !collapsing) {
1081                                     Ext.callback(callback, scope || me, [me.childNodes]); 
1082                                 }
1083                             });                            
1084                         }
1085                     }
1086                     
1087                     if (!collapsing &amp;&amp; callback) {
1088                         Ext.callback(callback, scope || me, [me.childNodes]);
1089                     }
1090                 }
1091             };
1092         }
1093     }
1094 });</pre>
1095 </body>
1096 </html>