Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / tree / Panel.js
index 0c8cef5..5d5c934 100644 (file)
@@ -1,46 +1,50 @@
+/*
+
+This file is part of Ext JS 4
+
+Copyright (c) 2011 Sencha Inc
+
+Contact:  http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
+
+*/
 /**
- * @class Ext.tree.Panel
- * @extends Ext.panel.Table
- * 
  * The TreePanel provides tree-structured UI representation of tree-structured data.
  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
- * multiple columns through the {@link columns} configuration. 
- * 
- * Simple TreePanel using inline data.
+ * multiple columns through the {@link #columns} configuration.
  *
- * {@img Ext.tree.Panel/Ext.tree.Panel1.png Ext.tree.Panel component}
- * 
- * ## Simple Tree Panel (no columns)
+ * Simple TreePanel using inline data:
  *
+ *     @example
  *     var store = Ext.create('Ext.data.TreeStore', {
  *         root: {
- *             expanded: true, 
- *             text:"",
- *             user:"",
- *             status:"", 
+ *             expanded: true,
  *             children: [
- *                 { text:"detention", leaf: true },
- *                 { text:"homework", expanded: true, 
- *                     children: [
- *                         { text:"book report", leaf: true },
- *                         { text:"alegrbra", leaf: true}
- *                     ]
- *                 },
- *                 { text: "buy lottery tickets", leaf:true }
+ *                 { text: "detention", leaf: true },
+ *                 { text: "homework", expanded: true, children: [
+ *                     { text: "book report", leaf: true },
+ *                     { text: "alegrbra", leaf: true}
+ *                 ] },
+ *                 { text: "buy lottery tickets", leaf: true }
  *             ]
  *         }
- *     });     
- *             
+ *     });
+ *
  *     Ext.create('Ext.tree.Panel', {
  *         title: 'Simple Tree',
  *         width: 200,
  *         height: 150,
  *         store: store,
- *         rootVisible: false,        
+ *         rootVisible: false,
  *         renderTo: Ext.getBody()
  *     });
  *
- * @xtype treepanel
+ * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
+ * {@link Ext.data.NodeInterface NodeInterface} config options.
  */
 Ext.define('Ext.tree.Panel', {
     extend: 'Ext.panel.Table',
@@ -49,64 +53,79 @@ Ext.define('Ext.tree.Panel', {
     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
     viewType: 'treeview',
     selType: 'treemodel',
-    
+
     treeCls: Ext.baseCSSPrefix + 'tree-panel',
-    
+
+    deferRowRender: false,
+
     /**
-     * @cfg {Boolean} lines false to disable tree lines (defaults to true)
+     * @cfg {Boolean} lines False to disable tree lines.
      */
     lines: true,
-    
+
     /**
-     * @cfg {Boolean} useArrows true to use Vista-style arrows in the tree (defaults to false)
+     * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
      */
     useArrows: false,
-    
+
     /**
-     * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
+     * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
      */
     singleExpand: false,
-    
+
     ddConfig: {
         enableDrag: true,
         enableDrop: true
     },
-    
-    /** 
-     * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
+
+    /**
+     * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
      */
-            
-    /** 
-     * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
+
+    /**
+     * @cfg {Boolean} rootVisible False to hide the root node.
      */
     rootVisible: true,
-    
-    /** 
-     * @cfg {Boolean} displayField The field inside the model that will be used as the node's text. (defaults to <tt>text</tt>)
-     */    
+
+    /**
+     * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
+     */
     displayField: 'text',
 
-    /** 
-     * @cfg {Boolean} root Allows you to not specify a store on this TreePanel. This is useful for creating a simple
-     * tree with preloaded data without having to specify a TreeStore and Model. A store and model will be created and
-     * root will be passed to that store.
+    /**
+     * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
+     * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
+     * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
+     * to that store. For example:
+     *
+     *     Ext.create('Ext.tree.Panel', {
+     *         title: 'Simple Tree',
+     *         root: {
+     *             text: "Root node",
+     *             expanded: true,
+     *             children: [
+     *                 { text: "Child 1", leaf: true },
+     *                 { text: "Child 2", leaf: true }
+     *             ]
+     *         },
+     *         renderTo: Ext.getBody()
+     *     });
      */
     root: null,
-    
+
     // Required for the Lockable Mixin. These are the configurations which will be copied to the
     // normal and locked sub tablepanels
     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
 
     /**
-     * @cfg {Boolean} hideHeaders
-     * Specify as <code>true</code> to hide the headers.
+     * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
      */
-    
+
     /**
-     * @cfg {Boolean} folderSort Set to true to automatically prepend a leaf sorter to the store (defaults to <tt>undefined</tt>)
-     */ 
-    
+     * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
+     */
+
     constructor: function(config) {
         config = config || {};
         if (config.animate === undefined) {
@@ -114,10 +133,10 @@ Ext.define('Ext.tree.Panel', {
         }
         this.enableAnimations = config.animate;
         delete config.animate;
-        
+
         this.callParent([config]);
     },
-    
+
     initComponent: function() {
         var me = this,
             cls = [me.treeCls];
@@ -126,35 +145,36 @@ Ext.define('Ext.tree.Panel', {
             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
             me.lines = false;
         }
-        
+
         if (me.lines) {
             cls.push(Ext.baseCSSPrefix + 'tree-lines');
         } else if (!me.useArrows) {
             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
         }
 
-        if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
+        if (Ext.isString(me.store)) {
+            me.store = Ext.StoreMgr.lookup(me.store);
+        } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
                 root: me.root,
                 fields: me.fields,
                 model: me.model,
                 folderSort: me.folderSort
             }));
-        }
-        else if (me.root) {
+        } else if (me.root) {
             me.store = Ext.data.StoreManager.lookup(me.store);
             me.store.setRootNode(me.root);
             if (me.folderSort !== undefined) {
                 me.store.folderSort = me.folderSort;
                 me.store.sort();
-            }            
+            }
         }
-        
+
         // I'm not sure if we want to this. It might be confusing
         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
         //     me.rootVisible = false;
         // }
-        
+
         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
             rootVisible: me.rootVisible,
             animate: me.enableAnimations,
@@ -162,141 +182,101 @@ Ext.define('Ext.tree.Panel', {
             node: me.store.getRootNode(),
             hideHeaders: me.hideHeaders
         });
-        
+
         me.mon(me.store, {
             scope: me,
             rootchange: me.onRootChange,
             clear: me.onClear
         });
-    
+
         me.relayEvents(me.store, [
             /**
              * @event beforeload
-             * Event description
-             * @param {Ext.data.Store} store This Store
-             * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
+             * @alias Ext.data.Store#beforeload
              */
             'beforeload',
 
             /**
              * @event load
-             * Fires whenever the store reads data from a remote data source.
-             * @param {Ext.data.store} this
-             * @param {Array} records An array of records
-             * @param {Boolean} successful True if the operation was successful.
+             * @alias Ext.data.Store#load
              */
-            'load'   
+            'load'
         ]);
-        
+
         me.store.on({
             /**
              * @event itemappend
-             * Fires when a new child node is appended to a node in the tree.
-             * @param {Tree} tree The owner tree
-             * @param {Node} parent The parent node
-             * @param {Node} node The newly appended node
-             * @param {Number} index The index of the newly appended node
+             * @alias Ext.data.TreeStore#append
              */
             append: me.createRelayer('itemappend'),
-            
+
             /**
              * @event itemremove
-             * Fires when a child node is removed from a node in the tree
-             * @param {Tree} tree The owner tree
-             * @param {Node} parent The parent node
-             * @param {Node} node The child node removed
+             * @alias Ext.data.TreeStore#remove
              */
             remove: me.createRelayer('itemremove'),
-            
+
             /**
              * @event itemmove
-             * Fires when a node is moved to a new location in the tree
-             * @param {Tree} tree The owner tree
-             * @param {Node} node The node moved
-             * @param {Node} oldParent The old parent of this node
-             * @param {Node} newParent The new parent of this node
-             * @param {Number} index The index it was moved to
+             * @alias Ext.data.TreeStore#move
              */
             move: me.createRelayer('itemmove'),
-            
+
             /**
              * @event iteminsert
-             * Fires when a new child node is inserted in a node in tree
-             * @param {Tree} tree The owner tree
-             * @param {Node} parent The parent node
-             * @param {Node} node The child node inserted
-             * @param {Node} refNode The child node the node was inserted before
+             * @alias Ext.data.TreeStore#insert
              */
             insert: me.createRelayer('iteminsert'),
-            
+
             /**
              * @event beforeitemappend
-             * Fires before a new child is appended to a node in this tree, return false to cancel the append.
-             * @param {Tree} tree The owner tree
-             * @param {Node} parent The parent node
-             * @param {Node} node The child node to be appended
+             * @alias Ext.data.TreeStore#beforeappend
              */
             beforeappend: me.createRelayer('beforeitemappend'),
-            
+
             /**
              * @event beforeitemremove
-             * Fires before a child is removed from a node in this tree, return false to cancel the remove.
-             * @param {Tree} tree The owner tree
-             * @param {Node} parent The parent node
-             * @param {Node} node The child node to be removed
+             * @alias Ext.data.TreeStore#beforeremove
              */
             beforeremove: me.createRelayer('beforeitemremove'),
-            
+
             /**
              * @event beforeitemmove
-             * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
-             * @param {Tree} tree The owner tree
-             * @param {Node} node The node being moved
-             * @param {Node} oldParent The parent of the node
-             * @param {Node} newParent The new parent the node is moving to
-             * @param {Number} index The index it is being moved to
+             * @alias Ext.data.TreeStore#beforemove
              */
             beforemove: me.createRelayer('beforeitemmove'),
-            
+
             /**
              * @event beforeiteminsert
-             * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
-             * @param {Tree} tree The owner tree
-             * @param {Node} parent The parent node
-             * @param {Node} node The child node to be inserted
-             * @param {Node} refNode The child node the node is being inserted before
+             * @alias Ext.data.TreeStore#beforeinsert
              */
             beforeinsert: me.createRelayer('beforeiteminsert'),
-             
+
             /**
              * @event itemexpand
-             * Fires when a node is expanded.
-             * @param {Node} this The expanding node
+             * @alias Ext.data.TreeStore#expand
              */
             expand: me.createRelayer('itemexpand'),
-             
+
             /**
              * @event itemcollapse
-             * Fires when a node is collapsed.
-             * @param {Node} this The collapsing node
+             * @alias Ext.data.TreeStore#collapse
              */
             collapse: me.createRelayer('itemcollapse'),
-             
+
             /**
              * @event beforeitemexpand
-             * Fires before a node is expanded.
-             * @param {Node} this The expanding node
+             * @alias Ext.data.TreeStore#beforeexpand
              */
             beforeexpand: me.createRelayer('beforeitemexpand'),
-             
+
             /**
              * @event beforeitemcollapse
-             * Fires before a node is collapsed.
-             * @param {Node} this The collapsing node
+             * @alias Ext.data.TreeStore#beforecollapse
              */
             beforecollapse: me.createRelayer('beforeitemcollapse')
         });
-        
+
         // If the user specifies the headers collection manually then dont inject our own
         if (!me.columns) {
             if (me.initialConfig.hideHeaders === undefined) {
@@ -306,16 +286,16 @@ Ext.define('Ext.tree.Panel', {
                 xtype    : 'treecolumn',
                 text     : 'Name',
                 flex     : 1,
-                dataIndex: me.displayField         
+                dataIndex: me.displayField
             }];
         }
-        
+
         if (me.cls) {
             cls.push(me.cls);
         }
         me.cls = cls.join(' ');
         me.callParent();
-        
+
         me.relayEvents(me.getView(), [
             /**
              * @event checkchange
@@ -325,7 +305,7 @@ Ext.define('Ext.tree.Panel', {
              */
             'checkchange'
         ]);
-            
+
         // If the root is not visible and there is no rootnode defined, then just lets load the store
         if (!me.getView().rootVisible && !me.getRootNode()) {
             me.setRootNode({
@@ -333,44 +313,61 @@ Ext.define('Ext.tree.Panel', {
             });
         }
     },
-    
+
     onClear: function(){
         this.view.onClear();
     },
-    
+
+    /**
+     * Sets root node of this tree.
+     * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
+     * @return {Ext.data.NodeInterface} The new root
+     */
     setRootNode: function() {
         return this.store.setRootNode.apply(this.store, arguments);
     },
-    
+
+    /**
+     * Returns the root node for this tree.
+     * @return {Ext.data.NodeInterface}
+     */
     getRootNode: function() {
         return this.store.getRootNode();
     },
-    
+
     onRootChange: function(root) {
         this.view.setRootNode(root);
     },
 
     /**
      * Retrieve an array of checked records.
-     * @return {Array} An array containing the checked records
+     * @return {Ext.data.Model[]} An array containing the checked records
      */
     getChecked: function() {
         return this.getView().getChecked();
     },
-    
+
     isItemChecked: function(rec) {
         return rec.get('checked');
     },
-        
+
     /**
      * Expand all nodes
      * @param {Function} callback (optional) A function to execute when the expand finishes.
      * @param {Object} scope (optional) The scope of the callback function
      */
     expandAll : function(callback, scope) {
-        var root = this.getRootNode();
+        var root = this.getRootNode(),
+            animate = this.enableAnimations,
+            view = this.getView();
         if (root) {
+            if (!animate) {
+                view.beginBulkUpdate();
+            }
             root.expand(true, callback, scope);
+            if (!animate) {
+                view.endBulkUpdate();
+            }
         }
     },
 
@@ -380,22 +377,30 @@ Ext.define('Ext.tree.Panel', {
      * @param {Object} scope (optional) The scope of the callback function
      */
     collapseAll : function(callback, scope) {
-        var root = this.getRootNode();
+        var root = this.getRootNode(),
+            animate = this.enableAnimations,
+            view = this.getView();
+
         if (root) {
-            if (this.getView().rootVisible) {
-                root.collapse(true, callback, scope);
+            if (!animate) {
+                view.beginBulkUpdate();
             }
-            else {
+            if (view.rootVisible) {
+                root.collapse(true, callback, scope);
+            } else {
                 root.collapseChildren(true, callback, scope);
             }
+            if (!animate) {
+                view.endBulkUpdate();
+            }
         }
     },
 
     /**
      * Expand the tree to the path of a particular node.
-     * @param {String} path The path to expand
+     * @param {String} path The path to expand. The path should include a leading separator.
      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
-     * @param {String} separator (optional) A separator to use. Defaults to <tt>'/'</tt>.
+     * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
      * @param {Object} scope (optional) The scope of the callback function
@@ -407,22 +412,22 @@ Ext.define('Ext.tree.Panel', {
             view = me.getView(),
             keys,
             expander;
-        
+
         field = field || me.getRootNode().idProperty;
         separator = separator || '/';
-        
+
         if (Ext.isEmpty(path)) {
             Ext.callback(callback, scope || me, [false, null]);
             return;
         }
-        
+
         keys = path.split(separator);
         if (current.get(field) != keys[1]) {
             // invalid root
             Ext.callback(callback, scope || me, [false, current]);
             return;
         }
-        
+
         expander = function(){
             if (++index === keys.length) {
                 Ext.callback(callback, scope || me, [true, current]);
@@ -438,12 +443,12 @@ Ext.define('Ext.tree.Panel', {
         };
         current.expand(false, expander);
     },
-    
+
     /**
-     * Expand the tree to the path of a particular node, then selectt.
-     * @param {String} path The path to select
+     * Expand the tree to the path of a particular node, then select it.
+     * @param {String} path The path to select. The path should include a leading separator.
      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
-     * @param {String} separator (optional) A separator to use. Defaults to <tt>'/'</tt>.
+     * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
      * @param {Object} scope (optional) The scope of the callback function
@@ -452,14 +457,14 @@ Ext.define('Ext.tree.Panel', {
         var me = this,
             keys,
             last;
-        
+
         field = field || me.getRootNode().idProperty;
         separator = separator || '/';
-        
+
         keys = path.split(separator);
         last = keys.pop();
-        
-        me.expandPath(keys.join('/'), field, separator, function(success, node){
+
+        me.expandPath(keys.join(separator), field, separator, function(success, node){
             var doSuccess = false;
             if (success && node) {
                 node = node.findChild(field, last);
@@ -474,4 +479,5 @@ Ext.define('Ext.tree.Panel', {
             Ext.callback(callback, scope || me, [doSuccess, node]);
         }, me);
     }
-});
\ No newline at end of file
+});
+