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