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