X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/7a654f8d43fdb43d78b63d90528bed6e86b608cc..refs/heads/master:/docs/source/NodeInterface.html diff --git a/docs/source/NodeInterface.html b/docs/source/NodeInterface.html index 55f38f77..d650118d 100644 --- a/docs/source/NodeInterface.html +++ b/docs/source/NodeInterface.html @@ -1,19 +1,185 @@ -Sencha Documentation Project
/**
- * @class Ext.data.NodeInterface
- * This class is meant to be used as a set of methods that are applied to the prototype of a
- * Record to decorate it with a Node API. This means that models used in conjunction with a tree
+
+
+
+  
+  The source code
+  
+  
+  
+  
+
+
+  
/**
+ * This class is used as a set of methods that are applied to the prototype of a
+ * Model to decorate it with a Node API. This means that models used in conjunction with a tree
  * will have all of the tree related methods available on the model. In general this class will
- * not be used directly by the developer.
+ * not be used directly by the developer. This class also creates extra fields on the model if
+ * they do not exist, to help maintain the tree state and UI. These fields are documented as
+ * config options.
  */
 Ext.define('Ext.data.NodeInterface', {
     requires: ['Ext.data.Field'],
-    
+
+    /**
+     * @cfg {String} parentId
+     * ID of parent node.
+     */
+
+    /**
+     * @cfg {Number} index
+     * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
+     * index will be 2.
+     */
+
+    /**
+     * @cfg {Number} depth
+     * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
+     */
+
+    /**
+     * @cfg {Boolean} [expanded=false]
+     * True if the node is expanded.
+     */
+
+    /**
+     * @cfg {Boolean} [expandable=false]
+     * Set to true to allow for expanding/collapsing of this node.
+     */
+
+    /**
+     * @cfg {Boolean} [checked=null]
+     * Set to true or false to show a checkbox alongside this node.
+     */
+
+    /**
+     * @cfg {Boolean} [leaf=false]
+     * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
+     * rendered for this node.
+     */
+
+    /**
+     * @cfg {String} cls
+     * CSS class to apply for this node.
+     */
+
+    /**
+     * @cfg {String} iconCls
+     * CSS class to apply for this node's icon.
+     */
+
+    /**
+     * @cfg {String} icon
+     * URL for this node's icon.
+     */
+
+    /**
+     * @cfg {Boolean} root
+     * True if this is the root node.
+     */
+
+    /**
+     * @cfg {Boolean} isLast
+     * True if this is the last node.
+     */
+
+    /**
+     * @cfg {Boolean} isFirst
+     * True if this is the first node.
+     */
+
+    /**
+     * @cfg {Boolean} [allowDrop=true]
+     * Set to false to deny dropping on this node.
+     */
+
+    /**
+     * @cfg {Boolean} [allowDrag=true]
+     * Set to false to deny dragging of this node.
+     */
+
+    /**
+     * @cfg {Boolean} [loaded=false]
+     * True if the node has finished loading.
+     */
+
+    /**
+     * @cfg {Boolean} [loading=false]
+     * True if the node is currently loading.
+     */
+
+    /**
+     * @cfg {String} href
+     * An URL for a link that's created when this config is specified.
+     */
+
+    /**
+     * @cfg {String} hrefTarget
+     * Target for link. Only applicable when {@link #href} also specified.
+     */
+
+    /**
+     * @cfg {String} qtip
+     * Tooltip text to show on this node.
+     */
+
+    /**
+     * @cfg {String} qtitle
+     * Tooltip title.
+     */
+
+    /**
+     * @cfg {String} text
+     * The text for to show on node label.
+     */
+
+    /**
+     * @cfg {Ext.data.NodeInterface[]} children
+     * Array of child nodes.
+     */
+
+
+    /**
+     * @property nextSibling
+     * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
+     */
+
+    /**
+     * @property previousSibling
+     * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
+     */
+
+    /**
+     * @property parentNode
+     * A reference to this node's parent node. `null` if this node is the root node.
+     */
+
+    /**
+     * @property lastChild
+     * A reference to this node's last child node. `null` if this node has no children.
+     */
+
+    /**
+     * @property firstChild
+     * A reference to this node's first child node. `null` if this node has no children.
+     */
+
+    /**
+     * @property childNodes
+     * An array of this nodes children.  Array will be empty if this node has no chidren.
+     */
+
     statics: {
-        /**
+        /**
          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
          * This adds a set of methods, new events, new properties and new fields on every Record
          * with the same Model as the passed Record.
-         * @param {Ext.data.Record} record The Record you want to decorate the prototype of.
+         * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
          * @static
          */
         decorate: function(record) {
@@ -24,51 +190,46 @@ Ext.define('Ext.data.NodeInterface', {
                     modelName = record.modelName,
                     modelClass = mgr.getModel(modelName),
                     idName = modelClass.prototype.idProperty,
-                    instances = Ext.Array.filter(mgr.all.getArray(), function(item) {
-                        return item.modelName == modelName;
-                    }),
-                    iln = instances.length,
                     newFields = [],
-                    i, instance, jln, j, newField;
+                    i, newField, len;
 
                 // Start by adding the NodeInterface methods to the Model's prototype
                 modelClass.override(this.getPrototypeBody());
                 newFields = this.applyFields(modelClass, [
-                    {name: idName,      type: 'string',  defaultValue: null},
-                    {name: 'parentId',  type: 'string',  defaultValue: null},
-                    {name: 'index',     type: 'int',     defaultValue: null},
-                    {name: 'depth',     type: 'int',     defaultValue: 0}, 
-                    {name: 'expanded',  type: 'bool',    defaultValue: false, persist: false},
-                    {name: 'checked',   type: 'auto',    defaultValue: null},
-                    {name: 'leaf',      type: 'bool',    defaultValue: false, persist: false},
-                    {name: 'cls',       type: 'string',  defaultValue: null, persist: false},
-                    {name: 'iconCls',   type: 'string',  defaultValue: null, persist: false},
-                    {name: 'root',      type: 'boolean', defaultValue: false, persist: false},
-                    {name: 'isLast',    type: 'boolean', defaultValue: false, persist: false},
-                    {name: 'isFirst',   type: 'boolean', defaultValue: false, persist: false},
-                    {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
-                    {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
-                    {name: 'loaded',    type: 'boolean', defaultValue: false, persist: false},
-                    {name: 'loading',   type: 'boolean', defaultValue: false, persist: false},
-                    {name: 'href',      type: 'string',  defaultValue: null, persist: false},
-                    {name: 'hrefTarget',type: 'string',  defaultValue: null, persist: false},
-                    {name: 'qtip',      type: 'string',  defaultValue: null, persist: false},
-                    {name: 'qtitle',    type: 'string',  defaultValue: null, persist: false}
+                    {name: idName,       type: 'string',  defaultValue: null},
+                    {name: 'parentId',   type: 'string',  defaultValue: null},
+                    {name: 'index',      type: 'int',     defaultValue: null},
+                    {name: 'depth',      type: 'int',     defaultValue: 0},
+                    {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
+                    {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
+                    {name: 'checked',    type: 'auto',    defaultValue: null},
+                    {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
+                    {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
+                    {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
+                    {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
+                    {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
+                    {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
+                    {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
+                    {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
+                    {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
+                    {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
+                    {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
+                    {name: 'href',       type: 'string',  defaultValue: null, persist: false},
+                    {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
+                    {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
+                    {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
                 ]);
 
-                jln = newFields.length;
-                // Set default values to all instances already out there
-                for (i = 0; i < iln; i++) {
-                    instance = instances[i];
-                    for (j = 0; j < jln; j++) {
-                        newField = newFields[j];
-                        if (instance.get(newField.name) === undefined) {
-                            instance.data[newField.name] = newField.defaultValue;
-                        }
+                len = newFields.length;
+                // Set default values
+                for (i = 0; i < len; ++i) {
+                    newField = newFields[i];
+                    if (record.get(newField.name) === undefined) {
+                        record.data[newField.name] = newField.defaultValue;
                     }
                 }
             }
-            
+
             Ext.applyIf(record, {
                 firstChild: null,
                 lastChild: null,
@@ -79,118 +240,119 @@ Ext.define('Ext.data.NodeInterface', {
             });
             // Commit any fields so the record doesn't show as dirty initially
             record.commit(true);
-            
+
             record.enableBubble([
-                /**
+                /**
                  * @event append
                  * Fires when a new child node is appended
-                 * @param {Node} this This node
-                 * @param {Node} node The newly appended node
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} node The newly appended node
                  * @param {Number} index The index of the newly appended node
                  */
                 "append",
 
-                /**
+                /**
                  * @event remove
                  * Fires when a child node is removed
-                 * @param {Node} this This node
-                 * @param {Node} node The removed node
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} node The removed node
                  */
                 "remove",
 
-                /**
+                /**
                  * @event move
                  * Fires when this node is moved to a new location in the tree
-                 * @param {Node} this This node
-                 * @param {Node} oldParent The old parent of this node
-                 * @param {Node} newParent The new parent of this node
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} oldParent The old parent of this node
+                 * @param {Ext.data.NodeInterface} newParent The new parent of this node
                  * @param {Number} index The index it was moved to
                  */
                 "move",
 
-                /**
+                /**
                  * @event insert
                  * Fires when a new child node is inserted.
-                 * @param {Node} this This node
-                 * @param {Node} node The child node inserted
-                 * @param {Node} refNode The child node the node was inserted before
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} node The child node inserted
+                 * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
                  */
                 "insert",
 
-                /**
+                /**
                  * @event beforeappend
                  * Fires before a new child is appended, return false to cancel the append.
-                 * @param {Node} this This node
-                 * @param {Node} node The child node to be appended
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} node The child node to be appended
                  */
                 "beforeappend",
 
-                /**
+                /**
                  * @event beforeremove
                  * Fires before a child is removed, return false to cancel the remove.
-                 * @param {Node} this This node
-                 * @param {Node} node The child node to be removed
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} node The child node to be removed
                  */
                 "beforeremove",
 
-                /**
+                /**
                  * @event beforemove
                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
-                 * @param {Node} this This node
-                 * @param {Node} oldParent The parent of this node
-                 * @param {Node} newParent The new parent this node is moving to
+                 * @param {Ext.data.NodeInterface} this This node
+                 * @param {Ext.data.NodeInterface} oldParent The parent of this node
+                 * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
                  * @param {Number} index The index it is being moved to
                  */
                 "beforemove",
 
-                 /**
+                 /**
                   * @event beforeinsert
                   * Fires before a new child is inserted, return false to cancel the insert.
-                  * @param {Node} this This node
-                  * @param {Node} node The child node to be inserted
-                  * @param {Node} refNode The child node the node is being inserted before
+                  * @param {Ext.data.NodeInterface} this This node
+                  * @param {Ext.data.NodeInterface} node The child node to be inserted
+                  * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
                   */
                 "beforeinsert",
-                
-                /**
+
+                /**
                  * @event expand
                  * Fires when this node is expanded.
-                 * @param {Node} this The expanding node
+                 * @param {Ext.data.NodeInterface} this The expanding node
                  */
                 "expand",
-                
-                /**
+
+                /**
                  * @event collapse
                  * Fires when this node is collapsed.
-                 * @param {Node} this The collapsing node
+                 * @param {Ext.data.NodeInterface} this The collapsing node
                  */
                 "collapse",
-                
-                /**
+
+                /**
                  * @event beforeexpand
                  * Fires before this node is expanded.
-                 * @param {Node} this The expanding node
+                 * @param {Ext.data.NodeInterface} this The expanding node
                  */
                 "beforeexpand",
-                
-                /**
+
+                /**
                  * @event beforecollapse
                  * Fires before this node is collapsed.
-                 * @param {Node} this The collapsing node
+                 * @param {Ext.data.NodeInterface} this The collapsing node
                  */
                 "beforecollapse",
-                
-                /**
-                 * @event beforecollapse
-                 * Fires before this node is collapsed.
-                 * @param {Node} this The collapsing node
+
+                /**
+                 * @event sort
+                 * Fires when this node's childNodes are sorted.
+                 * @param {Ext.data.NodeInterface} this This node.
+                 * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
                  */
                 "sort"
             ]);
-            
+
             return record;
         },
-        
+
         applyFields: function(modelClass, addFields) {
             var modelPrototype = modelClass.prototype,
                 fields = modelPrototype.fields,
@@ -198,25 +360,25 @@ Ext.define('Ext.data.NodeInterface', {
                 ln = addFields.length,
                 addField, i, name,
                 newFields = [];
-                
+
             for (i = 0; i < ln; i++) {
                 addField = addFields[i];
                 if (!Ext.Array.contains(keys, addField.name)) {
                     addField = Ext.create('data.field', addField);
-                    
+
                     newFields.push(addField);
                     fields.add(addField);
                 }
             }
-            
+
             return newFields;
         },
-        
+
         getPrototypeBody: function() {
             return {
                 isNode: true,
 
-                /**
+                /**
                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
                  * @return {Boolean}
                  */
@@ -227,8 +389,8 @@ Ext.define('Ext.data.NodeInterface', {
                     // Make sure the node implements the node interface
                     return Ext.data.NodeInterface.decorate(node);
                 },
-                
-                /**
+
+                /**
                  * Returns true if this node is a leaf
                  * @return {Boolean}
                  */
@@ -236,7 +398,7 @@ Ext.define('Ext.data.NodeInterface', {
                     return this.get('leaf') === true;
                 },
 
-                /**
+                /**
                  * Sets the first child of this node
                  * @private
                  * @param {Ext.data.NodeInterface} node
@@ -245,7 +407,7 @@ Ext.define('Ext.data.NodeInterface', {
                     this.firstChild = node;
                 },
 
-                /**
+                /**
                  * Sets the last child of this node
                  * @private
                  * @param {Ext.data.NodeInterface} node
@@ -254,7 +416,7 @@ Ext.define('Ext.data.NodeInterface', {
                     this.lastChild = node;
                 },
 
-                /**
+                /**
                  * Updates general data of this node like isFirst, isLast, depth. This
                  * method is internally called after a node is moved. This shouldn't
                  * have to be called by the developer unless they are creating custom
@@ -276,8 +438,8 @@ Ext.define('Ext.data.NodeInterface', {
                     while (parent.parentNode) {
                         ++depth;
                         parent = parent.parentNode;
-                    }                                            
-                    
+                    }
+
                     me.beginEdit();
                     me.set({
                         isFirst: isFirst,
@@ -290,13 +452,13 @@ Ext.define('Ext.data.NodeInterface', {
                     if (silent) {
                         me.commit();
                     }
-                    
+
                     for (i = 0; i < len; i++) {
                         children[i].updateInfo(silent);
                     }
                 },
 
-                /**
+                /**
                  * Returns true if this node is the last child of its parent
                  * @return {Boolean}
                  */
@@ -304,7 +466,7 @@ Ext.define('Ext.data.NodeInterface', {
                    return this.get('isLast');
                 },
 
-                /**
+                /**
                  * Returns true if this node is the first child of its parent
                  * @return {Boolean}
                  */
@@ -312,7 +474,7 @@ Ext.define('Ext.data.NodeInterface', {
                    return this.get('isFirst');
                 },
 
-                /**
+                /**
                  * Returns true if this node has one or more child nodes, else false.
                  * @return {Boolean}
                  */
@@ -320,20 +482,27 @@ Ext.define('Ext.data.NodeInterface', {
                     return !this.isLeaf() && this.childNodes.length > 0;
                 },
 
-                /**
+                /**
                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
-                 * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
+                 * node attribute is explicitly specified as true, otherwise returns false.
                  * @return {Boolean}
                  */
                 isExpandable : function() {
-                    return this.get('expandable') || this.hasChildNodes();
+                    var me = this;
+
+                    if (me.get('expandable')) {
+                        return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
+                    }
+                    return false;
                 },
 
-                /**
-                 * <p>Insert node(s) as the last child node of this node.</p>
-                 * <p>If the node was previously a child node of another parent node, it will be removed from that node first.</p>
-                 * @param {Node/Array} node The node or Array of nodes to append
-                 * @return {Node} The appended node if single append, or null if an array was passed
+                /**
+                 * Inserts node(s) as the last child node of this node.
+                 *
+                 * If the node was previously a child node of another parent node, it will be removed from that node first.
+                 *
+                 * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
+                 * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
                  */
                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
                     var me = this,
@@ -350,9 +519,9 @@ Ext.define('Ext.data.NodeInterface', {
                     } else {
                         // Make sure it is a record
                         node = me.createNode(node);
-                        
+
                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
-                            return false;                         
+                            return false;
                         }
 
                         index = me.childNodes.length;
@@ -376,7 +545,7 @@ Ext.define('Ext.data.NodeInterface', {
                         node.nextSibling = null;
 
                         me.setLastChild(node);
-                                                
+
                         ps = me.childNodes[index - 1];
                         if (ps) {
                             node.previousSibling = ps;
@@ -387,29 +556,29 @@ Ext.define('Ext.data.NodeInterface', {
                         }
 
                         node.updateInfo(suppressNodeUpdate);
-                        
+
                         // As soon as we append a child to this node, we are loaded
                         if (!me.isLoaded()) {
-                            me.set('loaded', true);                            
+                            me.set('loaded', true);
                         }
                         // If this node didnt have any childnodes before, update myself
                         else if (me.childNodes.length === 1) {
                             me.set('loaded', me.isLoaded());
                         }
-                        
+
                         if (suppressEvents !== true) {
                             me.fireEvent("append", me, node, index);
 
                             if (oldParent) {
                                 node.fireEvent("move", node, oldParent, me, index);
-                            }                            
+                            }
                         }
 
                         return node;
                     }
                 },
-                
-                /**
+
+                /**
                  * Returns the bubble target for this node
                  * @private
                  * @return {Object} The bubble target
@@ -418,22 +587,22 @@ Ext.define('Ext.data.NodeInterface', {
                     return this.parentNode;
                 },
 
-                /**
+                /**
                  * Removes a child node from this node.
-                 * @param {Node} node The node to remove
-                 * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
-                 * @return {Node} The removed node
+                 * @param {Ext.data.NodeInterface} node The node to remove
+                 * @param {Boolean} [destroy=false] True to destroy the node upon removal.
+                 * @return {Ext.data.NodeInterface} The removed node
                  */
                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
                     var me = this,
                         index = me.indexOf(node);
-                    
+
                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
                         return false;
                     }
 
                     // remove it from childNodes collection
-                    me.childNodes.splice(index, 1);
+                    Ext.Array.erase(me.childNodes, index, 1);
 
                     // update child refs
                     if (me.firstChild == node) {
@@ -442,7 +611,7 @@ Ext.define('Ext.data.NodeInterface', {
                     if (me.lastChild == node) {
                         me.setLastChild(node.previousSibling);
                     }
-                    
+
                     // update siblings
                     if (node.previousSibling) {
                         node.previousSibling.nextSibling = node.nextSibling;
@@ -456,13 +625,13 @@ Ext.define('Ext.data.NodeInterface', {
                     if (suppressEvents !== true) {
                         me.fireEvent("remove", me, node);
                     }
-                    
-                    
+
+
                     // If this node suddenly doesnt have childnodes anymore, update myself
                     if (!me.childNodes.length) {
                         me.set('loaded', me.isLoaded());
                     }
-                    
+
                     if (destroy) {
                         node.destroy(true);
                     } else {
@@ -472,12 +641,12 @@ Ext.define('Ext.data.NodeInterface', {
                     return node;
                 },
 
-                /**
+                /**
                  * Creates a copy (clone) of this Node.
-                 * @param {String} id (optional) A new id, defaults to this Node's id. See <code>{@link #id}</code>.
-                 * @param {Boolean} deep (optional) <p>If passed as <code>true</code>, all child Nodes are recursively copied into the new Node.</p>
-                 * <p>If omitted or false, the copy will have no child Nodes.</p>
-                 * @return {Node} A copy of this Node.
+                 * @param {String} [id] A new id, defaults to this Node's id.
+                 * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
+                 * False to copy without child Nodes.
+                 * @return {Ext.data.NodeInterface} A copy of this Node.
                  */
                 copy: function(newId, deep) {
                     var me = this,
@@ -494,14 +663,14 @@ Ext.define('Ext.data.NodeInterface', {
                     return result;
                 },
 
-                /**
-                 * Clear the node.
+                /**
+                 * Clears the node.
                  * @private
-                 * @param {Boolean} destroy True to destroy the node.
+                 * @param {Boolean} [destroy=false] True to destroy the node.
                  */
                 clear : function(destroy) {
                     var me = this;
-                    
+
                     // clear any references from the node
                     me.parentNode = me.previousSibling = me.nextSibling = null;
                     if (destroy) {
@@ -509,7 +678,7 @@ Ext.define('Ext.data.NodeInterface', {
                     }
                 },
 
-                /**
+                /**
                  * Destroys the node.
                  */
                 destroy : function(silent) {
@@ -519,26 +688,29 @@ Ext.define('Ext.data.NodeInterface', {
                      * 2) When destroy on the tree is called
                      * 3) For destroying child nodes on a node
                      */
-                    var me = this;
-                    
+                    var me = this,
+                        options = me.destroyOptions;
+
                     if (silent === true) {
                         me.clear(true);
                         Ext.each(me.childNodes, function(n) {
                             n.destroy(true);
                         });
                         me.childNodes = null;
+                        delete me.destroyOptions;
+                        me.callOverridden([options]);
                     } else {
+                        me.destroyOptions = silent;
+                        // overridden method will be called, since remove will end up calling destroy(true);
                         me.remove(true);
                     }
-
-                    me.callOverridden();
                 },
 
-                /**
+                /**
                  * Inserts the first node before the second node in this nodes childNodes collection.
-                 * @param {Node} node The node to insert
-                 * @param {Node} refNode The node to insert before (if null the node is appended)
-                 * @return {Node} The inserted node
+                 * @param {Ext.data.NodeInterface} node The node to insert
+                 * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
+                 * @return {Ext.data.NodeInterface} The inserted node
                  */
                 insertBefore : function(node, refNode, suppressEvents) {
                     var me = this,
@@ -546,11 +718,11 @@ Ext.define('Ext.data.NodeInterface', {
                         oldParent = node.parentNode,
                         refIndex  = index,
                         ps;
-                    
+
                     if (!refNode) { // like standard Dom, refNode can be null for append
                         return me.appendChild(node);
                     }
-                    
+
                     // nothing to do
                     if (node == refNode) {
                         return false;
@@ -558,11 +730,11 @@ Ext.define('Ext.data.NodeInterface', {
 
                     // Make sure it is a record with the NodeInterface
                     node = me.createNode(node);
-                    
+
                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
                         return false;
                     }
-                    
+
                     // when moving internally, indexes will change after remove
                     if (oldParent == me && me.indexOf(node) < index) {
                         refIndex--;
@@ -580,12 +752,12 @@ Ext.define('Ext.data.NodeInterface', {
                         me.setFirstChild(node);
                     }
 
-                    me.childNodes.splice(refIndex, 0, node);
+                    Ext.Array.splice(me.childNodes, refIndex, 0, node);
                     node.parentNode = me;
-                    
+
                     node.nextSibling = refNode;
                     refNode.previousSibling = node;
-                    
+
                     ps = me.childNodes[refIndex - 1];
                     if (ps) {
                         node.previousSibling = ps;
@@ -594,12 +766,12 @@ Ext.define('Ext.data.NodeInterface', {
                     } else {
                         node.previousSibling = null;
                     }
-                    
+
                     node.updateInfo();
-                    
+
                     if (!me.isLoaded()) {
-                        me.set('loaded', true);                            
-                    }    
+                        me.set('loaded', true);
+                    }
                     // If this node didnt have any childnodes before, update myself
                     else if (me.childNodes.length === 1) {
                         me.set('loaded', me.isLoaded());
@@ -610,18 +782,18 @@ Ext.define('Ext.data.NodeInterface', {
 
                         if (oldParent) {
                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
-                        }                        
+                        }
                     }
 
                     return node;
                 },
-                
-                /**
+
+                /**
                  * Insert a node into this node
                  * @param {Number} index The zero-based index to insert the node at
                  * @param {Ext.data.Model} node The node to insert
-                 * @return {Ext.data.Record} The record you just inserted
-                 */    
+                 * @return {Ext.data.Model} The record you just inserted
+                 */
                 insertChild: function(index, node) {
                     var sibling = this.childNodes[index];
                     if (sibling) {
@@ -632,10 +804,10 @@ Ext.define('Ext.data.NodeInterface', {
                     }
                 },
 
-                /**
+                /**
                  * Removes this node from its parent
-                 * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
-                 * @return {Node} this
+                 * @param {Boolean} [destroy=false] True to destroy the node upon removal.
+                 * @return {Ext.data.NodeInterface} this
                  */
                 remove : function(destroy, suppressEvents) {
                     var parentNode = this.parentNode;
@@ -646,10 +818,10 @@ Ext.define('Ext.data.NodeInterface', {
                     return this;
                 },
 
-                /**
+                /**
                  * Removes all child nodes from this node.
-                 * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
-                 * @return {Node} this
+                 * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
+                 * @return {Ext.data.NodeInterface} this
                  */
                 removeAll : function(destroy, suppressEvents) {
                     var cn = this.childNodes,
@@ -661,39 +833,59 @@ Ext.define('Ext.data.NodeInterface', {
                     return this;
                 },
 
-                /**
+                /**
                  * Returns the child node at the specified index.
                  * @param {Number} index
-                 * @return {Node}
+                 * @return {Ext.data.NodeInterface}
                  */
                 getChildAt : function(index) {
                     return this.childNodes[index];
                 },
 
-                /**
+                /**
                  * Replaces one child node in this node with another.
-                 * @param {Node} newChild The replacement node
-                 * @param {Node} oldChild The node to replace
-                 * @return {Node} The replaced node
+                 * @param {Ext.data.NodeInterface} newChild The replacement node
+                 * @param {Ext.data.NodeInterface} oldChild The node to replace
+                 * @return {Ext.data.NodeInterface} The replaced node
                  */
                 replaceChild : function(newChild, oldChild, suppressEvents) {
                     var s = oldChild ? oldChild.nextSibling : null;
-                    
+
                     this.removeChild(oldChild, suppressEvents);
                     this.insertBefore(newChild, s, suppressEvents);
                     return oldChild;
                 },
 
-                /**
+                /**
                  * Returns the index of a child node
-                 * @param {Node} node
+                 * @param {Ext.data.NodeInterface} node
                  * @return {Number} The index of the node or -1 if it was not found
                  */
                 indexOf : function(child) {
                     return Ext.Array.indexOf(this.childNodes, child);
                 },
 
-                /**
+                /**
+                 * Gets the hierarchical path from the root of the current node.
+                 * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
+                 * @param {String} [separator="/"] A separator to use.
+                 * @return {String} The node path
+                 */
+                getPath: function(field, separator) {
+                    field = field || this.idProperty;
+                    separator = separator || '/';
+
+                    var path = [this.get(field)],
+                        parent = this.parentNode;
+
+                    while (parent) {
+                        path.unshift(parent.get(field));
+                        parent = parent.parentNode;
+                    }
+                    return separator + path.join(separator);
+                },
+
+                /**
                  * Returns depth of this node (the root node has a depth of 0)
                  * @return {Number}
                  */
@@ -701,13 +893,13 @@ Ext.define('Ext.data.NodeInterface', {
                     return this.get('depth');
                 },
 
-                /**
+                /**
                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
                  * will be the args provided or the current node. If the function returns false at any point,
                  * the bubble is stopped.
                  * @param {Function} fn The function to call
-                 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
-                 * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
+                 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
+                 * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
                  */
                 bubble : function(fn, scope, args) {
                     var p = this;
@@ -728,13 +920,13 @@ Ext.define('Ext.data.NodeInterface', {
                 },
                 //</deprecated>
 
-                /**
+                /**
                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
                  * will be the args provided or the current node. If the function returns false at any point,
                  * the cascade is stopped on that branch.
                  * @param {Function} fn The function to call
-                 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
-                 * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
+                 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
+                 * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
                  */
                 cascadeBy : function(fn, scope, args) {
                     if (fn.apply(scope || this, args || [this]) !== false) {
@@ -748,13 +940,13 @@ Ext.define('Ext.data.NodeInterface', {
                     }
                 },
 
-                /**
+                /**
                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
                  * will be the args provided or the current node. If the function returns false at any point,
                  * the iteration stops.
                  * @param {Function} fn The function to call
-                 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
-                 * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
+                 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
+                 * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
                  */
                 eachChild : function(fn, scope, args) {
                     var childNodes = this.childNodes,
@@ -768,12 +960,12 @@ Ext.define('Ext.data.NodeInterface', {
                     }
                 },
 
-                /**
+                /**
                  * Finds the first child that has the attribute with the specified value.
                  * @param {String} attribute The attribute name
-                 * @param {Mixed} value The value to search for
-                 * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
-                 * @return {Node} The found child or null if none was found
+                 * @param {Object} value The value to search for
+                 * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
+                 * @return {Ext.data.NodeInterface} The found child or null if none was found
                  */
                 findChild : function(attribute, value, deep) {
                     return this.findChildBy(function() {
@@ -781,12 +973,12 @@ Ext.define('Ext.data.NodeInterface', {
                     }, null, deep);
                 },
 
-                /**
-                 * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
-                 * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
-                 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
-                 * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
-                 * @return {Node} The found child or null if none was found
+                /**
+                 * Finds the first child by a custom function. The child matches if the function passed returns true.
+                 * @param {Function} fn A function which must return true if the passed Node is the required Node.
+                 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
+                 * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
+                 * @return {Ext.data.NodeInterface} The found child or null if none was found
                  */
                 findChildBy : function(fn, scope, deep) {
                     var cs = this.childNodes,
@@ -809,18 +1001,18 @@ Ext.define('Ext.data.NodeInterface', {
                     return null;
                 },
 
-                /**
+                /**
                  * Returns true if this node is an ancestor (at any point) of the passed node.
-                 * @param {Node} node
+                 * @param {Ext.data.NodeInterface} node
                  * @return {Boolean}
                  */
                 contains : function(node) {
                     return node.isAncestor(this);
                 },
 
-                /**
+                /**
                  * Returns true if the passed node is an ancestor (at any point) of this node.
-                 * @param {Node} node
+                 * @param {Ext.data.NodeInterface} node
                  * @return {Boolean}
                  */
                 isAncestor : function(node) {
@@ -834,24 +1026,24 @@ Ext.define('Ext.data.NodeInterface', {
                     return false;
                 },
 
-                /**
+                /**
                  * Sorts this nodes children using the supplied sort function.
                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
-                 * @param {Boolean} recursive Whether or not to apply this sort recursively
-                 * @param {Boolean} suppressEvent Set to true to not fire a sort event.
+                 * @param {Boolean} [recursive=false] True to apply this sort recursively
+                 * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
                  */
                 sort : function(sortFn, recursive, suppressEvent) {
                     var cs  = this.childNodes,
                         ln = cs.length,
                         i, n;
-                    
+
                     if (ln > 0) {
                         Ext.Array.sort(cs, sortFn);
                         for (i = 0; i < ln; i++) {
                             n = cs[i];
                             n.previousSibling = cs[i-1];
                             n.nextSibling = cs[i+1];
-                        
+
                             if (i === 0) {
                                 this.setFirstChild(n);
                                 n.updateInfo();
@@ -864,49 +1056,49 @@ Ext.define('Ext.data.NodeInterface', {
                                 n.sort(sortFn, true, true);
                             }
                         }
-                        
+
                         if (suppressEvent !== true) {
                             this.fireEvent('sort', this, cs);
                         }
                     }
                 },
-                        
-                /**
+
+                /**
                  * Returns true if this node is expaned
                  * @return {Boolean}
-                 */        
+                 */
                 isExpanded: function() {
                     return this.get('expanded');
                 },
-                
-                /**
+
+                /**
                  * Returns true if this node is loaded
                  * @return {Boolean}
-                 */ 
+                 */
                 isLoaded: function() {
                     return this.get('loaded');
                 },
 
-                /**
+                /**
                  * Returns true if this node is loading
                  * @return {Boolean}
-                 */ 
+                 */
                 isLoading: function() {
                     return this.get('loading');
                 },
-                                
-                /**
+
+                /**
                  * Returns true if this node is the root node
                  * @return {Boolean}
-                 */ 
+                 */
                 isRoot: function() {
                     return !this.parentNode;
                 },
-                
-                /**
+
+                /**
                  * Returns true if this node is visible
                  * @return {Boolean}
-                 */ 
+                 */
                 isVisible: function() {
                     var parent = this.parentNode;
                     while (parent) {
@@ -917,12 +1109,12 @@ Ext.define('Ext.data.NodeInterface', {
                     }
                     return true;
                 },
-                
-                /**
+
+                /**
                  * Expand this node.
-                 * @param {Function} recursive (Optional) True to recursively expand all the children
-                 * @param {Function} callback (Optional) The function to execute once the expand completes
-                 * @param {Object} scope (Optional) The scope to run the callback in
+                 * @param {Boolean} [recursive=false] True to recursively expand all the children
+                 * @param {Function} [callback] The function to execute once the expand completes
+                 * @param {Object} [scope] The scope to run the callback in
                  */
                 expand: function(recursive, callback, scope) {
                     var me = this;
@@ -932,48 +1124,47 @@ Ext.define('Ext.data.NodeInterface', {
 
                     // First we start by checking if this node is a parent
                     if (!me.isLeaf()) {
-                        // Now we check if this record is already expanding or expanded
-                        if (!me.isLoading() && !me.isExpanded()) {
-                            // The TreeStore actually listens for the beforeexpand method and checks
-                            // whether we have to asynchronously load the children from the server
-                            // first. Thats why we pass a callback function to the event that the
-                            // store can call once it has loaded and parsed all the children.
-                            me.fireEvent('beforeexpand', me, function(records) {
-                                me.set('expanded', true); 
-                                me.fireEvent('expand', me, me.childNodes, false);
-                                
-                                // Call the expandChildren method if recursive was set to true 
-                                if (recursive) {
-                                    me.expandChildren(true, callback, scope);
-                                }
-                                else {
-                                    Ext.callback(callback, scope || me, [me.childNodes]);                                
-                                }
-                            }, me);                            
-                        }
-                        // If it is is already expanded but we want to recursively expand then call expandChildren
-                        else if (recursive) {
-                            me.expandChildren(true, callback, scope);
-                        }
-                        else {
-                            Ext.callback(callback, scope || me, [me.childNodes]);
+                        // If it's loaded, wait until it loads before proceeding
+                        if (me.isLoading()) {
+                            me.on('expand', function(){
+                                me.expand(recursive, callback, scope);
+                            }, me, {single: true});
+                        } else {
+                            // Now we check if this record is already expanding or expanded
+                            if (!me.isExpanded()) {
+                                // The TreeStore actually listens for the beforeexpand method and checks
+                                // whether we have to asynchronously load the children from the server
+                                // first. Thats why we pass a callback function to the event that the
+                                // store can call once it has loaded and parsed all the children.
+                                me.fireEvent('beforeexpand', me, function(){
+                                    me.set('expanded', true);
+                                    me.fireEvent('expand', me, me.childNodes, false);
+
+                                    // Call the expandChildren method if recursive was set to true
+                                    if (recursive) {
+                                        me.expandChildren(true, callback, scope);
+                                    } else {
+                                        Ext.callback(callback, scope || me, [me.childNodes]);
+                                    }
+                                }, me);
+                            } else if (recursive) {
+                                // If it is is already expanded but we want to recursively expand then call expandChildren
+                                me.expandChildren(true, callback, scope);
+                            } else {
+                                Ext.callback(callback, scope || me, [me.childNodes]);
+                            }
                         }
-
-                        // TODO - if the node isLoading, we probably need to defer the
-                        // callback until it is loaded (e.g., selectPath would need us
-                        // to not make the callback until the childNodes exist).
-                    }
-                    // If it's not then we fire the callback right away
-                    else {
+                    } else {
+                        // If it's not then we fire the callback right away
                         Ext.callback(callback, scope || me); // leaf = no childNodes
                     }
                 },
-                
-                /**
+
+                /**
                  * Expand all the children of this node.
-                 * @param {Function} recursive (Optional) True to recursively expand all the children
-                 * @param {Function} callback (Optional) The function to execute once all the children are expanded
-                 * @param {Object} scope (Optional) The scope to run the callback in
+                 * @param {Boolean} [recursive=false] True to recursively expand all the children
+                 * @param {Function} [callback] The function to execute once all the children are expanded
+                 * @param {Object} [scope] The scope to run the callback in
                  */
                 expandChildren: function(recursive, callback, scope) {
                     var me = this,
@@ -990,22 +1181,21 @@ Ext.define('Ext.data.NodeInterface', {
                             nodes[i].expand(recursive, function () {
                                 expanding--;
                                 if (callback && !expanding) {
-                                    Ext.callback(callback, scope || me, me.childNodes); 
+                                    Ext.callback(callback, scope || me, [me.childNodes]);
                                 }
-                            });                            
+                            });
                         }
                     }
-                    
+
                     if (!expanding && callback) {
-                        Ext.callback(callback, scope || me, me.childNodes);
-                    }
+                        Ext.callback(callback, scope || me, [me.childNodes]);                    }
                 },
 
-                /**
+                /**
                  * Collapse this node.
-                 * @param {Function} recursive (Optional) True to recursively collapse all the children
-                 * @param {Function} callback (Optional) The function to execute once the collapse completes
-                 * @param {Object} scope (Optional) The scope to run the callback in
+                 * @param {Boolean} [recursive=false] True to recursively collapse all the children
+                 * @param {Function} [callback] The function to execute once the collapse completes
+                 * @param {Object} [scope] The scope to run the callback in
                  */
                 collapse: function(recursive, callback, scope) {
                     var me = this;
@@ -1014,18 +1204,18 @@ Ext.define('Ext.data.NodeInterface', {
                     if (!me.isLeaf()) {
                         // Now we check if this record is already collapsing or collapsed
                         if (!me.collapsing && me.isExpanded()) {
-                            me.fireEvent('beforecollapse', me, function(records) {
-                                me.set('expanded', false); 
+                            me.fireEvent('beforecollapse', me, function() {
+                                me.set('expanded', false);
                                 me.fireEvent('collapse', me, me.childNodes, false);
-                                
-                                // Call the collapseChildren method if recursive was set to true 
+
+                                // Call the collapseChildren method if recursive was set to true
                                 if (recursive) {
                                     me.collapseChildren(true, callback, scope);
                                 }
                                 else {
-                                    Ext.callback(callback, scope || me, [me.childNodes]);                                
+                                    Ext.callback(callback, scope || me, [me.childNodes]);
                                 }
-                            }, me);                            
+                            }, me);
                         }
                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
                         else if (recursive) {
@@ -1034,15 +1224,15 @@ Ext.define('Ext.data.NodeInterface', {
                     }
                     // If it's not then we fire the callback right away
                     else {
-                        Ext.callback(callback, scope || me, me.childNodes); 
+                        Ext.callback(callback, scope || me, [me.childNodes]);
                     }
                 },
-                
-                /**
+
+                /**
                  * Collapse all the children of this node.
-                 * @param {Function} recursive (Optional) True to recursively collapse all the children
-                 * @param {Function} callback (Optional) The function to execute once all the children are collapsed
-                 * @param {Object} scope (Optional) The scope to run the callback in
+                 * @param {Function} [recursive=false] True to recursively collapse all the children
+                 * @param {Function} [callback] The function to execute once all the children are collapsed
+                 * @param {Object} [scope] The scope to run the callback in
                  */
                 collapseChildren: function(recursive, callback, scope) {
                     var me = this,
@@ -1059,17 +1249,19 @@ Ext.define('Ext.data.NodeInterface', {
                             nodes[i].collapse(recursive, function () {
                                 collapsing--;
                                 if (callback && !collapsing) {
-                                    Ext.callback(callback, scope || me, me.childNodes); 
+                                    Ext.callback(callback, scope || me, [me.childNodes]);
                                 }
-                            });                            
+                            });
                         }
                     }
-                    
+
                     if (!collapsing && callback) {
-                        Ext.callback(callback, scope || me, me.childNodes);
+                        Ext.callback(callback, scope || me, [me.childNodes]);
                     }
                 }
             };
         }
     }
-});
\ No newline at end of file +});
+ +