Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / widgets / tree / TreeLoader.js
index 0fe32c8..d5a0776 100644 (file)
 /*!
- * Ext JS Library 3.0.0
- * Copyright(c) 2006-2009 Ext JS, LLC
- * licensing@extjs.com
- * http://www.extjs.com/license
+ * Ext JS Library 3.3.1
+ * Copyright(c) 2006-2010 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
  */
-/**\r
- * @class Ext.tree.TreeLoader\r
- * @extends Ext.util.Observable\r
- * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child\r
- * nodes from a specified URL. The response must be a JavaScript Array definition\r
- * whose elements are node definition objects. e.g.:\r
- * <pre><code>\r
-    [{\r
-        id: 1,\r
-        text: 'A leaf Node',\r
-        leaf: true\r
-    },{\r
-        id: 2,\r
-        text: 'A folder Node',\r
-        children: [{\r
-            id: 3,\r
-            text: 'A child Node',\r
-            leaf: true\r
-        }]\r
-   }]\r
-</code></pre>\r
- * <br><br>\r
- * A server request is sent, and child nodes are loaded only when a node is expanded.\r
- * The loading node's id is passed to the server under the parameter name "node" to\r
- * enable the server to produce the correct child nodes.\r
- * <br><br>\r
- * To pass extra parameters, an event handler may be attached to the "beforeload"\r
- * event, and the parameters specified in the TreeLoader's baseParams property:\r
- * <pre><code>\r
-    myTreeLoader.on("beforeload", function(treeLoader, node) {\r
-        this.baseParams.category = node.attributes.category;\r
-    }, this);\r
-</code></pre>\r
- * This would pass an HTTP parameter called "category" to the server containing\r
- * the value of the Node's "category" attribute.\r
- * @constructor\r
- * Creates a new Treeloader.\r
- * @param {Object} config A config object containing config properties.\r
- */\r
-Ext.tree.TreeLoader = function(config){\r
-    this.baseParams = {};\r
-    Ext.apply(this, config);\r
-\r
-    this.addEvents(\r
-        /**\r
-         * @event beforeload\r
-         * Fires before a network request is made to retrieve the Json text which specifies a node's children.\r
-         * @param {Object} This TreeLoader object.\r
-         * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
-         * @param {Object} callback The callback function specified in the {@link #load} call.\r
-         */\r
-        "beforeload",\r
-        /**\r
-         * @event load\r
-         * Fires when the node has been successfuly loaded.\r
-         * @param {Object} This TreeLoader object.\r
-         * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
-         * @param {Object} response The response object containing the data from the server.\r
-         */\r
-        "load",\r
-        /**\r
-         * @event loadexception\r
-         * Fires if the network request failed.\r
-         * @param {Object} This TreeLoader object.\r
-         * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
-         * @param {Object} response The response object containing the data from the server.\r
-         */\r
-        "loadexception"\r
-    );\r
-    Ext.tree.TreeLoader.superclass.constructor.call(this);\r
-    if(typeof this.paramOrder == 'string'){\r
-        this.paramOrder = this.paramOrder.split(/[\s,|]/);\r
-    }\r
-};\r
-\r
-Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {\r
-    /**\r
-    * @cfg {String} dataUrl The URL from which to request a Json string which\r
-    * specifies an array of node definition objects representing the child nodes\r
-    * to be loaded.\r
-    */\r
-    /**\r
-     * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).\r
-     */\r
-    /**\r
-     * @cfg {String} url Equivalent to {@link #dataUrl}.\r
-     */\r
-    /**\r
-     * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.\r
-     */\r
-    /**\r
-    * @cfg {Object} baseParams (optional) An object containing properties which\r
-    * specify HTTP parameters to be passed to each request for child nodes.\r
-    */\r
-    /**\r
-    * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes\r
-    * created by this loader. If the attributes sent by the server have an attribute in this object,\r
-    * they take priority.\r
-    */\r
-    /**\r
-    * @cfg {Object} uiProviders (optional) An object containing properties which\r
-    * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional\r
-    * <i>uiProvider</i> attribute of a returned child node is a string rather\r
-    * than a reference to a TreeNodeUI implementation, then that string value\r
-    * is used as a property name in the uiProviders object.\r
-    */\r
-    uiProviders : {},\r
-\r
-    /**\r
-    * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing\r
-    * child nodes before loading.\r
-    */\r
-    clearOnLoad : true,\r
-\r
-    /**\r
-     * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.\r
-     * A list of params to be executed\r
-     * server side.  Specify the params in the order in which they must be executed on the server-side\r
-     * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,\r
-     * comma, or pipe. For example,\r
-     * any of the following would be acceptable:<pre><code>\r
-paramOrder: ['param1','param2','param3']\r
-paramOrder: 'param1 param2 param3'\r
-paramOrder: 'param1,param2,param3'\r
-paramOrder: 'param1|param2|param'\r
-     </code></pre>\r
-     */\r
-    paramOrder: undefined,\r
-\r
-    /**\r
-     * @cfg {Boolean} paramsAsHash Only used when using directFn.\r
-     * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a\r
-     * <tt>{@link #paramOrder}</tt> nullifies this configuration.\r
-     */\r
-    paramsAsHash: false,\r
-\r
-    /**\r
-     * @cfg {Function} directFn\r
-     * Function to call when executing a request.\r
-     */\r
-    directFn : undefined,\r
-\r
-    /**\r
-     * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.\r
-     * This is called automatically when a node is expanded, but may be used to reload\r
-     * a node (or append new children if the {@link #clearOnLoad} option is false.)\r
-     * @param {Ext.tree.TreeNode} node\r
-     * @param {Function} callback\r
-     * @param (Object) scope\r
-     */\r
-    load : function(node, callback, scope){\r
-        if(this.clearOnLoad){\r
-            while(node.firstChild){\r
-                node.removeChild(node.firstChild);\r
-            }\r
-        }\r
-        if(this.doPreload(node)){ // preloaded json children\r
-            this.runCallback(callback, scope || node, []);\r
-        }else if(this.directFn || this.dataUrl || this.url){\r
-            this.requestData(node, callback, scope || node);\r
-        }\r
-    },\r
-\r
-    doPreload : function(node){\r
-        if(node.attributes.children){\r
-            if(node.childNodes.length < 1){ // preloaded?\r
-                var cs = node.attributes.children;\r
-                node.beginUpdate();\r
-                for(var i = 0, len = cs.length; i < len; i++){\r
-                    var cn = node.appendChild(this.createNode(cs[i]));\r
-                    if(this.preloadChildren){\r
-                        this.doPreload(cn);\r
-                    }\r
-                }\r
-                node.endUpdate();\r
-            }\r
-            return true;\r
-        }\r
-        return false;\r
-    },\r
-\r
-    getParams: function(node){\r
-        var buf = [], bp = this.baseParams;\r
-        if(this.directFn){\r
-            buf.push(node.id);\r
-            if(bp){\r
-                if(this.paramOrder){\r
-                    for(var i = 0, len = this.paramOrder.length; i < len; i++){\r
-                        buf.push(bp[this.paramOrder[i]]);\r
-                    }\r
-                }else if(this.paramsAsHash){\r
-                    buf.push(bp);\r
-                }\r
-            }\r
-            return buf;\r
-        }else{\r
-            for(var key in bp){\r
-                if(!Ext.isFunction(bp[key])){\r
-                    buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");\r
-                }\r
-            }\r
-            buf.push("node=", encodeURIComponent(node.id));\r
-            return buf.join("");\r
-        }\r
-    },\r
-\r
-    requestData : function(node, callback, scope){\r
-        if(this.fireEvent("beforeload", this, node, callback) !== false){\r
-            if(this.directFn){\r
-                var args = this.getParams(node);\r
-                args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));\r
-                this.directFn.apply(window, args);\r
-            }else{\r
-                this.transId = Ext.Ajax.request({\r
-                    method:this.requestMethod,\r
-                    url: this.dataUrl||this.url,\r
-                    success: this.handleResponse,\r
-                    failure: this.handleFailure,\r
-                    scope: this,\r
-                    argument: {callback: callback, node: node, scope: scope},\r
-                    params: this.getParams(node)\r
-                });\r
-            }\r
-        }else{\r
-            // if the load is cancelled, make sure we notify\r
-            // the node that we are done\r
-            this.runCallback(callback, scope || node, []);\r
-        }\r
-    },\r
-\r
-    processDirectResponse: function(result, response, args){\r
-        if(response.status){\r
-            this.handleResponse({\r
-                responseData: Ext.isArray(result) ? result : null,\r
-                responseText: result,\r
-                argument: args\r
-            });\r
-        }else{\r
-            this.handleFailure({\r
-                argument: args\r
-            });\r
-        }\r
-    },\r
-\r
-    // private\r
-    runCallback: function(cb, scope, args){\r
-        if(Ext.isFunction(cb)){\r
-            cb.apply(scope, args);\r
-        }\r
-    },\r
-\r
-    isLoading : function(){\r
-        return !!this.transId;\r
-    },\r
-\r
-    abort : function(){\r
-        if(this.isLoading()){\r
-            Ext.Ajax.abort(this.transId);\r
-        }\r
-    },\r
-\r
-    /**\r
-    * <p>Override this function for custom TreeNode node implementation, or to\r
-    * modify the attributes at creation time.</p>\r
-    * Example:<pre><code>\r
-new Ext.tree.TreePanel({\r
-    ...\r
-    new Ext.tree.TreeLoader({\r
-        url: 'dataUrl',\r
-        createNode: function(attr) {\r
-//          Allow consolidation consignments to have\r
-//          consignments dropped into them.\r
-            if (attr.isConsolidation) {\r
-                attr.iconCls = 'x-consol',\r
-                attr.allowDrop = true;\r
-            }\r
-            return Ext.tree.TreeLoader.prototype.call(this, attr);\r
-        }\r
-    }),\r
-    ...\r
-});\r
-</code></pre>\r
-    * @param attr {Object} The attributes from which to create the new node.\r
-    */\r
-    createNode : function(attr){\r
-        // apply baseAttrs, nice idea Corey!\r
-        if(this.baseAttrs){\r
-            Ext.applyIf(attr, this.baseAttrs);\r
-        }\r
-        if(this.applyLoader !== false){\r
-            attr.loader = this;\r
-        }\r
-        if(typeof attr.uiProvider == 'string'){\r
-           attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);\r
-        }\r
-        if(attr.nodeType){\r
-            return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);\r
-        }else{\r
-            return attr.leaf ?\r
-                        new Ext.tree.TreeNode(attr) :\r
-                        new Ext.tree.AsyncTreeNode(attr);\r
-        }\r
-    },\r
-\r
-    processResponse : function(response, node, callback, scope){\r
-        var json = response.responseText;\r
-        try {\r
-            var o = response.responseData || Ext.decode(json);\r
-            node.beginUpdate();\r
-            for(var i = 0, len = o.length; i < len; i++){\r
-                var n = this.createNode(o[i]);\r
-                if(n){\r
-                    node.appendChild(n);\r
-                }\r
-            }\r
-            node.endUpdate();\r
-            this.runCallback(callback, scope || node, [node]);\r
-        }catch(e){\r
-            this.handleFailure(response);\r
-        }\r
-    },\r
-\r
-    handleResponse : function(response){\r
-        this.transId = false;\r
-        var a = response.argument;\r
-        this.processResponse(response, a.node, a.callback, a.scope);\r
-        this.fireEvent("load", this, a.node, response);\r
-    },\r
-\r
-    handleFailure : function(response){\r
-        this.transId = false;\r
-        var a = response.argument;\r
-        this.fireEvent("loadexception", this, a.node, response);\r
-        this.runCallback(a.callback, a.scope || a.node, [a.node]);\r
-    }\r
+/**
+ * @class Ext.tree.TreeLoader
+ * @extends Ext.util.Observable
+ * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child
+ * nodes from a specified URL. The response must be a JavaScript Array definition
+ * whose elements are node definition objects. e.g.:
+ * <pre><code>
+    [{
+        id: 1,
+        text: 'A leaf Node',
+        leaf: true
+    },{
+        id: 2,
+        text: 'A folder Node',
+        children: [{
+            id: 3,
+            text: 'A child Node',
+            leaf: true
+        }]
+   }]
+</code></pre>
+ * <br><br>
+ * A server request is sent, and child nodes are loaded only when a node is expanded.
+ * The loading node's id is passed to the server under the parameter name "node" to
+ * enable the server to produce the correct child nodes.
+ * <br><br>
+ * To pass extra parameters, an event handler may be attached to the "beforeload"
+ * event, and the parameters specified in the TreeLoader's baseParams property:
+ * <pre><code>
+    myTreeLoader.on("beforeload", function(treeLoader, node) {
+        this.baseParams.category = node.attributes.category;
+    }, this);
+</code></pre>
+ * This would pass an HTTP parameter called "category" to the server containing
+ * the value of the Node's "category" attribute.
+ * @constructor
+ * Creates a new Treeloader.
+ * @param {Object} config A config object containing config properties.
+ */
+Ext.tree.TreeLoader = function(config){
+    this.baseParams = {};
+    Ext.apply(this, config);
+
+    this.addEvents(
+        /**
+         * @event beforeload
+         * Fires before a network request is made to retrieve the Json text which specifies a node's children.
+         * @param {Object} This TreeLoader object.
+         * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
+         * @param {Object} callback The callback function specified in the {@link #load} call.
+         */
+        "beforeload",
+        /**
+         * @event load
+         * Fires when the node has been successfuly loaded.
+         * @param {Object} This TreeLoader object.
+         * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
+         * @param {Object} response The response object containing the data from the server.
+         */
+        "load",
+        /**
+         * @event loadexception
+         * Fires if the network request failed.
+         * @param {Object} This TreeLoader object.
+         * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
+         * @param {Object} response The response object containing the data from the server.
+         */
+        "loadexception"
+    );
+    Ext.tree.TreeLoader.superclass.constructor.call(this);
+    if(Ext.isString(this.paramOrder)){
+        this.paramOrder = this.paramOrder.split(/[\s,|]/);
+    }
+};
+
+Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
+    /**
+    * @cfg {String} dataUrl The URL from which to request a Json string which
+    * specifies an array of node definition objects representing the child nodes
+    * to be loaded.
+    */
+    /**
+     * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
+     */
+    /**
+     * @cfg {String} url Equivalent to {@link #dataUrl}.
+     */
+    /**
+     * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
+     */
+    /**
+    * @cfg {Object} baseParams (optional) An object containing properties which
+    * specify HTTP parameters to be passed to each request for child nodes.
+    */
+    /**
+    * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
+    * created by this loader. If the attributes sent by the server have an attribute in this object,
+    * they take priority.
+    */
+    /**
+    * @cfg {Object} uiProviders (optional) An object containing properties which
+    * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
+    * <i>uiProvider</i> attribute of a returned child node is a string rather
+    * than a reference to a TreeNodeUI implementation, then that string value
+    * is used as a property name in the uiProviders object.
+    */
+    uiProviders : {},
+
+    /**
+    * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
+    * child nodes before loading.
+    */
+    clearOnLoad : true,
+
+    /**
+     * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.
+     * Specifies the params in the order in which they must be passed to the server-side Direct method
+     * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
+     * comma, or pipe. For example,
+     * any of the following would be acceptable:<pre><code>
+nodeParameter: 'node',
+paramOrder: ['param1','param2','param3']
+paramOrder: 'node param1 param2 param3'
+paramOrder: 'param1,node,param2,param3'
+paramOrder: 'param1|param2|param|node'
+     </code></pre>
+     */
+    paramOrder: undefined,
+
+    /**
+     * @cfg {Boolean} paramsAsHash Only used when using directFn.
+     * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a
+     * <tt>{@link #paramOrder}</tt> nullifies this configuration.
+     */
+    paramsAsHash: false,
+
+    /**
+     * @cfg {String} nodeParameter The name of the parameter sent to the server which contains
+     * the identifier of the node. Defaults to <tt>'node'</tt>.
+     */
+    nodeParameter: 'node',
+
+    /**
+     * @cfg {Function} directFn
+     * Function to call when executing a request.
+     */
+    directFn : undefined,
+
+    /**
+     * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
+     * This is called automatically when a node is expanded, but may be used to reload
+     * a node (or append new children if the {@link #clearOnLoad} option is false.)
+     * @param {Ext.tree.TreeNode} node
+     * @param {Function} callback Function to call after the node has been loaded. The
+     * function is passed the TreeNode which was requested to be loaded.
+     * @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed.
+     * defaults to the loaded TreeNode.
+     */
+    load : function(node, callback, scope){
+        if(this.clearOnLoad){
+            while(node.firstChild){
+                node.removeChild(node.firstChild);
+            }
+        }
+        if(this.doPreload(node)){ // preloaded json children
+            this.runCallback(callback, scope || node, [node]);
+        }else if(this.directFn || this.dataUrl || this.url){
+            this.requestData(node, callback, scope || node);
+        }
+    },
+
+    doPreload : function(node){
+        if(node.attributes.children){
+            if(node.childNodes.length < 1){ // preloaded?
+                var cs = node.attributes.children;
+                node.beginUpdate();
+                for(var i = 0, len = cs.length; i < len; i++){
+                    var cn = node.appendChild(this.createNode(cs[i]));
+                    if(this.preloadChildren){
+                        this.doPreload(cn);
+                    }
+                }
+                node.endUpdate();
+            }
+            return true;
+        }
+        return false;
+    },
+
+    getParams: function(node){
+        var bp = Ext.apply({}, this.baseParams),
+            np = this.nodeParameter,
+            po = this.paramOrder;
+
+        np && (bp[ np ] = node.id);
+
+        if(this.directFn){
+            var buf = [node.id];
+            if(po){
+                // reset 'buf' if the nodeParameter was included in paramOrder
+                if(np && po.indexOf(np) > -1){
+                    buf = [];
+                }
+
+                for(var i = 0, len = po.length; i < len; i++){
+                    buf.push(bp[ po[i] ]);
+                }
+            }else if(this.paramsAsHash){
+                buf = [bp];
+            }
+            return buf;
+        }else{
+            return bp;
+        }
+    },
+
+    requestData : function(node, callback, scope){
+        if(this.fireEvent("beforeload", this, node, callback) !== false){
+            if(this.directFn){
+                var args = this.getParams(node);
+                args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
+                this.directFn.apply(window, args);
+            }else{
+                this.transId = Ext.Ajax.request({
+                    method:this.requestMethod,
+                    url: this.dataUrl||this.url,
+                    success: this.handleResponse,
+                    failure: this.handleFailure,
+                    scope: this,
+                    argument: {callback: callback, node: node, scope: scope},
+                    params: this.getParams(node)
+                });
+            }
+        }else{
+            // if the load is cancelled, make sure we notify
+            // the node that we are done
+            this.runCallback(callback, scope || node, []);
+        }
+    },
+
+    processDirectResponse: function(result, response, args){
+        if(response.status){
+            this.handleResponse({
+                responseData: Ext.isArray(result) ? result : null,
+                responseText: result,
+                argument: args
+            });
+        }else{
+            this.handleFailure({
+                argument: args
+            });
+        }
+    },
+
+    // private
+    runCallback: function(cb, scope, args){
+        if(Ext.isFunction(cb)){
+            cb.apply(scope, args);
+        }
+    },
+
+    isLoading : function(){
+        return !!this.transId;
+    },
+
+    abort : function(){
+        if(this.isLoading()){
+            Ext.Ajax.abort(this.transId);
+        }
+    },
+
+    /**
+    * <p>Override this function for custom TreeNode node implementation, or to
+    * modify the attributes at creation time.</p>
+    * Example:<pre><code>
+new Ext.tree.TreePanel({
+    ...
+    loader: new Ext.tree.TreeLoader({
+        url: 'dataUrl',
+        createNode: function(attr) {
+//          Allow consolidation consignments to have
+//          consignments dropped into them.
+            if (attr.isConsolidation) {
+                attr.iconCls = 'x-consol',
+                attr.allowDrop = true;
+            }
+            return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
+        }
+    }),
+    ...
+});
+</code></pre>
+    * @param attr {Object} The attributes from which to create the new node.
+    */
+    createNode : function(attr){
+        // apply baseAttrs, nice idea Corey!
+        if(this.baseAttrs){
+            Ext.applyIf(attr, this.baseAttrs);
+        }
+        if(this.applyLoader !== false && !attr.loader){
+            attr.loader = this;
+        }
+        if(Ext.isString(attr.uiProvider)){
+           attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
+        }
+        if(attr.nodeType){
+            return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
+        }else{
+            return attr.leaf ?
+                        new Ext.tree.TreeNode(attr) :
+                        new Ext.tree.AsyncTreeNode(attr);
+        }
+    },
+
+    processResponse : function(response, node, callback, scope){
+        var json = response.responseText;
+        try {
+            var o = response.responseData || Ext.decode(json);
+            node.beginUpdate();
+            for(var i = 0, len = o.length; i < len; i++){
+                var n = this.createNode(o[i]);
+                if(n){
+                    node.appendChild(n);
+                }
+            }
+            node.endUpdate();
+            this.runCallback(callback, scope || node, [node]);
+        }catch(e){
+            this.handleFailure(response);
+        }
+    },
+
+    handleResponse : function(response){
+        this.transId = false;
+        var a = response.argument;
+        this.processResponse(response, a.node, a.callback, a.scope);
+        this.fireEvent("load", this, a.node, response);
+    },
+
+    handleFailure : function(response){
+        this.transId = false;
+        var a = response.argument;
+        this.fireEvent("loadexception", this, a.node, response);
+        this.runCallback(a.callback, a.scope || a.node, [a.node]);
+    },
+
+    destroy : function(){
+        this.abort();
+        this.purgeListeners();
+    }
 });
\ No newline at end of file