Upgrade to ExtJS 3.1.1 - Released 02/08/2010
[extjs.git] / src / widgets / tree / TreeLoader.js
1 /*!
2  * Ext JS Library 3.1.1
3  * Copyright(c) 2006-2010 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.tree.TreeLoader\r
9  * @extends Ext.util.Observable\r
10  * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child\r
11  * nodes from a specified URL. The response must be a JavaScript Array definition\r
12  * whose elements are node definition objects. e.g.:\r
13  * <pre><code>\r
14     [{\r
15         id: 1,\r
16         text: 'A leaf Node',\r
17         leaf: true\r
18     },{\r
19         id: 2,\r
20         text: 'A folder Node',\r
21         children: [{\r
22             id: 3,\r
23             text: 'A child Node',\r
24             leaf: true\r
25         }]\r
26    }]\r
27 </code></pre>\r
28  * <br><br>\r
29  * A server request is sent, and child nodes are loaded only when a node is expanded.\r
30  * The loading node's id is passed to the server under the parameter name "node" to\r
31  * enable the server to produce the correct child nodes.\r
32  * <br><br>\r
33  * To pass extra parameters, an event handler may be attached to the "beforeload"\r
34  * event, and the parameters specified in the TreeLoader's baseParams property:\r
35  * <pre><code>\r
36     myTreeLoader.on("beforeload", function(treeLoader, node) {\r
37         this.baseParams.category = node.attributes.category;\r
38     }, this);\r
39 </code></pre>\r
40  * This would pass an HTTP parameter called "category" to the server containing\r
41  * the value of the Node's "category" attribute.\r
42  * @constructor\r
43  * Creates a new Treeloader.\r
44  * @param {Object} config A config object containing config properties.\r
45  */\r
46 Ext.tree.TreeLoader = function(config){\r
47     this.baseParams = {};\r
48     Ext.apply(this, config);\r
49 \r
50     this.addEvents(\r
51         /**\r
52          * @event beforeload\r
53          * Fires before a network request is made to retrieve the Json text which specifies a node's children.\r
54          * @param {Object} This TreeLoader object.\r
55          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
56          * @param {Object} callback The callback function specified in the {@link #load} call.\r
57          */\r
58         "beforeload",\r
59         /**\r
60          * @event load\r
61          * Fires when the node has been successfuly loaded.\r
62          * @param {Object} This TreeLoader object.\r
63          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
64          * @param {Object} response The response object containing the data from the server.\r
65          */\r
66         "load",\r
67         /**\r
68          * @event loadexception\r
69          * Fires if the network request failed.\r
70          * @param {Object} This TreeLoader object.\r
71          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
72          * @param {Object} response The response object containing the data from the server.\r
73          */\r
74         "loadexception"\r
75     );\r
76     Ext.tree.TreeLoader.superclass.constructor.call(this);\r
77     if(Ext.isString(this.paramOrder)){\r
78         this.paramOrder = this.paramOrder.split(/[\s,|]/);\r
79     }\r
80 };\r
81 \r
82 Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {\r
83     /**\r
84     * @cfg {String} dataUrl The URL from which to request a Json string which\r
85     * specifies an array of node definition objects representing the child nodes\r
86     * to be loaded.\r
87     */\r
88     /**\r
89      * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).\r
90      */\r
91     /**\r
92      * @cfg {String} url Equivalent to {@link #dataUrl}.\r
93      */\r
94     /**\r
95      * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.\r
96      */\r
97     /**\r
98     * @cfg {Object} baseParams (optional) An object containing properties which\r
99     * specify HTTP parameters to be passed to each request for child nodes.\r
100     */\r
101     /**\r
102     * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes\r
103     * created by this loader. If the attributes sent by the server have an attribute in this object,\r
104     * they take priority.\r
105     */\r
106     /**\r
107     * @cfg {Object} uiProviders (optional) An object containing properties which\r
108     * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional\r
109     * <i>uiProvider</i> attribute of a returned child node is a string rather\r
110     * than a reference to a TreeNodeUI implementation, then that string value\r
111     * is used as a property name in the uiProviders object.\r
112     */\r
113     uiProviders : {},\r
114 \r
115     /**\r
116     * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing\r
117     * child nodes before loading.\r
118     */\r
119     clearOnLoad : true,\r
120 \r
121     /**\r
122      * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.\r
123      * Specifies the params in the order in which they must be passed to the server-side Direct method\r
124      * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,\r
125      * comma, or pipe. For example,\r
126      * any of the following would be acceptable:<pre><code>\r
127 nodeParameter: 'node',\r
128 paramOrder: ['param1','param2','param3']\r
129 paramOrder: 'node param1 param2 param3'\r
130 paramOrder: 'param1,node,param2,param3'\r
131 paramOrder: 'param1|param2|param|node'\r
132      </code></pre>\r
133      */\r
134     paramOrder: undefined,\r
135 \r
136     /**\r
137      * @cfg {Boolean} paramsAsHash Only used when using directFn.\r
138      * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a\r
139      * <tt>{@link #paramOrder}</tt> nullifies this configuration.\r
140      */\r
141     paramsAsHash: false,\r
142 \r
143     /**\r
144      * @cfg {String} nodeParameter The name of the parameter sent to the server which contains\r
145      * the identifier of the node. Defaults to <tt>'node'</tt>.\r
146      */\r
147     nodeParameter: 'node',\r
148 \r
149     /**\r
150      * @cfg {Function} directFn\r
151      * Function to call when executing a request.\r
152      */\r
153     directFn : undefined,\r
154 \r
155     /**\r
156      * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.\r
157      * This is called automatically when a node is expanded, but may be used to reload\r
158      * a node (or append new children if the {@link #clearOnLoad} option is false.)\r
159      * @param {Ext.tree.TreeNode} node\r
160      * @param {Function} callback Function to call after the node has been loaded. The\r
161      * function is passed the TreeNode which was requested to be loaded.\r
162      * @param (Object) scope The cope (<code>this</code> reference) in which the callback is executed.\r
163      * defaults to the loaded TreeNode.\r
164      */\r
165     load : function(node, callback, scope){\r
166         if(this.clearOnLoad){\r
167             while(node.firstChild){\r
168                 node.removeChild(node.firstChild);\r
169             }\r
170         }\r
171         if(this.doPreload(node)){ // preloaded json children\r
172             this.runCallback(callback, scope || node, [node]);\r
173         }else if(this.directFn || this.dataUrl || this.url){\r
174             this.requestData(node, callback, scope || node);\r
175         }\r
176     },\r
177 \r
178     doPreload : function(node){\r
179         if(node.attributes.children){\r
180             if(node.childNodes.length < 1){ // preloaded?\r
181                 var cs = node.attributes.children;\r
182                 node.beginUpdate();\r
183                 for(var i = 0, len = cs.length; i < len; i++){\r
184                     var cn = node.appendChild(this.createNode(cs[i]));\r
185                     if(this.preloadChildren){\r
186                         this.doPreload(cn);\r
187                     }\r
188                 }\r
189                 node.endUpdate();\r
190             }\r
191             return true;\r
192         }\r
193         return false;\r
194     },\r
195 \r
196     getParams: function(node){\r
197         var bp = Ext.apply({}, this.baseParams),\r
198             np = this.nodeParameter,\r
199             po = this.paramOrder;\r
200 \r
201         np && (bp[ np ] = node.id);\r
202 \r
203         if(this.directFn){\r
204             var buf = [node.id];\r
205             if(po){\r
206                 // reset 'buf' if the nodeParameter was included in paramOrder\r
207                 if(np && po.indexOf(np) > -1){\r
208                     buf = [];\r
209                 }\r
210 \r
211                 for(var i = 0, len = po.length; i < len; i++){\r
212                     buf.push(bp[ po[i] ]);\r
213                 }\r
214             }else if(this.paramsAsHash){\r
215                 buf = [bp];\r
216             }\r
217             return buf;\r
218         }else{\r
219             return bp;\r
220         }\r
221     },\r
222 \r
223     requestData : function(node, callback, scope){\r
224         if(this.fireEvent("beforeload", this, node, callback) !== false){\r
225             if(this.directFn){\r
226                 var args = this.getParams(node);\r
227                 args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));\r
228                 this.directFn.apply(window, args);\r
229             }else{\r
230                 this.transId = Ext.Ajax.request({\r
231                     method:this.requestMethod,\r
232                     url: this.dataUrl||this.url,\r
233                     success: this.handleResponse,\r
234                     failure: this.handleFailure,\r
235                     scope: this,\r
236                     argument: {callback: callback, node: node, scope: scope},\r
237                     params: this.getParams(node)\r
238                 });\r
239             }\r
240         }else{\r
241             // if the load is cancelled, make sure we notify\r
242             // the node that we are done\r
243             this.runCallback(callback, scope || node, []);\r
244         }\r
245     },\r
246 \r
247     processDirectResponse: function(result, response, args){\r
248         if(response.status){\r
249             this.handleResponse({\r
250                 responseData: Ext.isArray(result) ? result : null,\r
251                 responseText: result,\r
252                 argument: args\r
253             });\r
254         }else{\r
255             this.handleFailure({\r
256                 argument: args\r
257             });\r
258         }\r
259     },\r
260 \r
261     // private\r
262     runCallback: function(cb, scope, args){\r
263         if(Ext.isFunction(cb)){\r
264             cb.apply(scope, args);\r
265         }\r
266     },\r
267 \r
268     isLoading : function(){\r
269         return !!this.transId;\r
270     },\r
271 \r
272     abort : function(){\r
273         if(this.isLoading()){\r
274             Ext.Ajax.abort(this.transId);\r
275         }\r
276     },\r
277 \r
278     /**\r
279     * <p>Override this function for custom TreeNode node implementation, or to\r
280     * modify the attributes at creation time.</p>\r
281     * Example:<pre><code>\r
282 new Ext.tree.TreePanel({\r
283     ...\r
284     loader: new Ext.tree.TreeLoader({\r
285         url: 'dataUrl',\r
286         createNode: function(attr) {\r
287 //          Allow consolidation consignments to have\r
288 //          consignments dropped into them.\r
289             if (attr.isConsolidation) {\r
290                 attr.iconCls = 'x-consol',\r
291                 attr.allowDrop = true;\r
292             }\r
293             return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);\r
294         }\r
295     }),\r
296     ...\r
297 });\r
298 </code></pre>\r
299     * @param attr {Object} The attributes from which to create the new node.\r
300     */\r
301     createNode : function(attr){\r
302         // apply baseAttrs, nice idea Corey!\r
303         if(this.baseAttrs){\r
304             Ext.applyIf(attr, this.baseAttrs);\r
305         }\r
306         if(this.applyLoader !== false && !attr.loader){\r
307             attr.loader = this;\r
308         }\r
309         if(Ext.isString(attr.uiProvider)){\r
310            attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);\r
311         }\r
312         if(attr.nodeType){\r
313             return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);\r
314         }else{\r
315             return attr.leaf ?\r
316                         new Ext.tree.TreeNode(attr) :\r
317                         new Ext.tree.AsyncTreeNode(attr);\r
318         }\r
319     },\r
320 \r
321     processResponse : function(response, node, callback, scope){\r
322         var json = response.responseText;\r
323         try {\r
324             var o = response.responseData || Ext.decode(json);\r
325             node.beginUpdate();\r
326             for(var i = 0, len = o.length; i < len; i++){\r
327                 var n = this.createNode(o[i]);\r
328                 if(n){\r
329                     node.appendChild(n);\r
330                 }\r
331             }\r
332             node.endUpdate();\r
333             this.runCallback(callback, scope || node, [node]);\r
334         }catch(e){\r
335             this.handleFailure(response);\r
336         }\r
337     },\r
338 \r
339     handleResponse : function(response){\r
340         this.transId = false;\r
341         var a = response.argument;\r
342         this.processResponse(response, a.node, a.callback, a.scope);\r
343         this.fireEvent("load", this, a.node, response);\r
344     },\r
345 \r
346     handleFailure : function(response){\r
347         this.transId = false;\r
348         var a = response.argument;\r
349         this.fireEvent("loadexception", this, a.node, response);\r
350         this.runCallback(a.callback, a.scope || a.node, [a.node]);\r
351     },\r
352 \r
353     destroy : function(){\r
354         this.abort();\r
355         this.purgeListeners();\r
356     }\r
357 });