Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / direct / RemotingProvider.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.direct.RemotingProvider\r
9  * @extends Ext.direct.JsonProvider\r
10  * \r
11  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to\r
12  * server side methods on the client (a remote procedure call (RPC) type of\r
13  * connection where the client can initiate a procedure on the server).</p>\r
14  * \r
15  * <p>This allows for code to be organized in a fashion that is maintainable,\r
16  * while providing a clear path between client and server, something that is\r
17  * not always apparent when using URLs.</p>\r
18  * \r
19  * <p>To accomplish this the server-side needs to describe what classes and methods\r
20  * are available on the client-side. This configuration will typically be\r
21  * outputted by the server-side Ext.Direct stack when the API description is built.</p>\r
22  */\r
23 Ext.direct.RemotingProvider = Ext.extend(Ext.direct.JsonProvider, {       \r
24     /**\r
25      * @cfg {Object} actions\r
26      * Object literal defining the server side actions and methods. For example, if\r
27      * the Provider is configured with:\r
28      * <pre><code>\r
29 "actions":{ // each property within the 'actions' object represents a server side Class \r
30     "TestAction":[ // array of methods within each server side Class to be   \r
31     {              // stubbed out on client\r
32         "name":"doEcho", \r
33         "len":1            \r
34     },{\r
35         "name":"multiply",// name of method\r
36         "len":2           // The number of parameters that will be used to create an\r
37                           // array of data to send to the server side function.\r
38                           // Ensure the server sends back a Number, not a String. \r
39     },{\r
40         "name":"doForm",\r
41         "formHandler":true, // direct the client to use specialized form handling method \r
42         "len":1\r
43     }]\r
44 }\r
45      * </code></pre>\r
46      * <p>Note that a Store is not required, a server method can be called at any time.\r
47      * In the following example a <b>client side</b> handler is used to call the\r
48      * server side method "multiply" in the server-side "TestAction" Class:</p>\r
49      * <pre><code>\r
50 TestAction.multiply(\r
51     2, 4, // pass two arguments to server, so specify len=2\r
52     // callback function after the server is called\r
53     // result: the result returned by the server\r
54     //      e: Ext.Direct.RemotingEvent object\r
55     function(result, e){\r
56         var t = e.getTransaction();\r
57         var action = t.action; // server side Class called\r
58         var method = t.method; // server side method called\r
59         if(e.status){\r
60             var answer = Ext.encode(result); // 8\r
61     \r
62         }else{\r
63             var msg = e.message; // failure message\r
64         }\r
65     }\r
66 );\r
67      * </code></pre>\r
68      * In the example above, the server side "multiply" function will be passed two\r
69      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be\r
70      * available as the <tt>result</tt> in the example above. \r
71      */\r
72     \r
73     /**\r
74      * @cfg {String/Object} namespace\r
75      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).\r
76      * Explicitly specify the namespace Object, or specify a String to have a\r
77      * {@link Ext#namespace namespace created} implicitly.\r
78      */\r
79     \r
80     /**\r
81      * @cfg {String} url\r
82      * <b>Required<b>. The url to connect to the {@link Ext.Direct} server-side router. \r
83      */\r
84     \r
85     /**\r
86      * @cfg {String} enableUrlEncode\r
87      * Specify which param will hold the arguments for the method.\r
88      * Defaults to <tt>'data'</tt>.\r
89      */\r
90     \r
91     /**\r
92      * @cfg {Number/Boolean} enableBuffer\r
93      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method\r
94      * calls. If a number is specified this is the amount of time in milliseconds\r
95      * to wait before sending a batched request (defaults to <tt>10</tt>).</p>\r
96      * <br><p>Calls which are received within the specified timeframe will be\r
97      * concatenated together and sent in a single request, optimizing the\r
98      * application by reducing the amount of round trips that have to be made\r
99      * to the server.</p>\r
100      */\r
101     enableBuffer: 10,\r
102     \r
103     /**\r
104      * @cfg {Number} maxRetries\r
105      * Number of times to re-attempt delivery on failure of a call. Defaults to <tt>1</tt>.\r
106      */\r
107     maxRetries: 1,\r
108     \r
109     /**\r
110      * @cfg {Number} timeout\r
111      * The timeout to use for each request. Defaults to <tt>undefined</tt>.\r
112      */\r
113     timeout: undefined,\r
114 \r
115     constructor : function(config){\r
116         Ext.direct.RemotingProvider.superclass.constructor.call(this, config);\r
117         this.addEvents(\r
118             /**\r
119              * @event beforecall\r
120              * Fires immediately before the client-side sends off the RPC call.\r
121              * By returning false from an event handler you can prevent the call from\r
122              * executing.\r
123              * @param {Ext.direct.RemotingProvider} provider\r
124              * @param {Ext.Direct.Transaction} transaction\r
125              */            \r
126             'beforecall',            \r
127             /**\r
128              * @event call\r
129              * Fires immediately after the request to the server-side is sent. This does\r
130              * NOT fire after the response has come back from the call.\r
131              * @param {Ext.direct.RemotingProvider} provider\r
132              * @param {Ext.Direct.Transaction} transaction\r
133              */            \r
134             'call'\r
135         );\r
136         this.namespace = (Ext.isString(this.namespace)) ? Ext.ns(this.namespace) : this.namespace || window;\r
137         this.transactions = {};\r
138         this.callBuffer = [];\r
139     },\r
140 \r
141     // private\r
142     initAPI : function(){\r
143         var o = this.actions;\r
144         for(var c in o){\r
145             var cls = this.namespace[c] || (this.namespace[c] = {}),\r
146                 ms = o[c];\r
147             for(var i = 0, len = ms.length; i < len; i++){\r
148                 var m = ms[i];\r
149                 cls[m.name] = this.createMethod(c, m);\r
150             }\r
151         }\r
152     },\r
153 \r
154     // inherited\r
155     isConnected: function(){\r
156         return !!this.connected;\r
157     },\r
158 \r
159     connect: function(){\r
160         if(this.url){\r
161             this.initAPI();\r
162             this.connected = true;\r
163             this.fireEvent('connect', this);\r
164         }else if(!this.url){\r
165             throw 'Error initializing RemotingProvider, no url configured.';\r
166         }\r
167     },\r
168 \r
169     disconnect: function(){\r
170         if(this.connected){\r
171             this.connected = false;\r
172             this.fireEvent('disconnect', this);\r
173         }\r
174     },\r
175 \r
176     onData: function(opt, success, xhr){\r
177         if(success){\r
178             var events = this.getEvents(xhr);\r
179             for(var i = 0, len = events.length; i < len; i++){\r
180                 var e = events[i],\r
181                     t = this.getTransaction(e);\r
182                 this.fireEvent('data', this, e);\r
183                 if(t){\r
184                     this.doCallback(t, e, true);\r
185                     Ext.Direct.removeTransaction(t);\r
186                 }\r
187             }\r
188         }else{\r
189             var ts = [].concat(opt.ts);\r
190             for(var i = 0, len = ts.length; i < len; i++){\r
191                 var t = this.getTransaction(ts[i]);\r
192                 if(t && t.retryCount < this.maxRetries){\r
193                     t.retry();\r
194                 }else{\r
195                     var e = new Ext.Direct.ExceptionEvent({\r
196                         data: e,\r
197                         transaction: t,\r
198                         code: Ext.Direct.exceptions.TRANSPORT,\r
199                         message: 'Unable to connect to the server.',\r
200                         xhr: xhr\r
201                     });\r
202                     this.fireEvent('data', this, e);\r
203                     if(t){\r
204                         this.doCallback(t, e, false);\r
205                         Ext.Direct.removeTransaction(t);\r
206                     }\r
207                 }\r
208             }\r
209         }\r
210     },\r
211 \r
212     getCallData: function(t){\r
213         return {\r
214             action: t.action,\r
215             method: t.method,\r
216             data: t.data,\r
217             type: 'rpc',\r
218             tid: t.tid\r
219         };\r
220     },\r
221 \r
222     doSend : function(data){\r
223         var o = {\r
224             url: this.url,\r
225             callback: this.onData,\r
226             scope: this,\r
227             ts: data,\r
228             timeout: this.timeout\r
229         }, callData;\r
230 \r
231         if(Ext.isArray(data)){\r
232             callData = [];\r
233             for(var i = 0, len = data.length; i < len; i++){\r
234                 callData.push(this.getCallData(data[i]));\r
235             }\r
236         }else{\r
237             callData = this.getCallData(data);\r
238         }\r
239 \r
240         if(this.enableUrlEncode){\r
241             var params = {};\r
242             params[Ext.isString(this.enableUrlEncode) ? this.enableUrlEncode : 'data'] = Ext.encode(callData);\r
243             o.params = params;\r
244         }else{\r
245             o.jsonData = callData;\r
246         }\r
247         Ext.Ajax.request(o);\r
248     },\r
249 \r
250     combineAndSend : function(){\r
251         var len = this.callBuffer.length;\r
252         if(len > 0){\r
253             this.doSend(len == 1 ? this.callBuffer[0] : this.callBuffer);\r
254             this.callBuffer = [];\r
255         }\r
256     },\r
257 \r
258     queueTransaction: function(t){\r
259         if(t.form){\r
260             this.processForm(t);\r
261             return;\r
262         }\r
263         this.callBuffer.push(t);\r
264         if(this.enableBuffer){\r
265             if(!this.callTask){\r
266                 this.callTask = new Ext.util.DelayedTask(this.combineAndSend, this);\r
267             }\r
268             this.callTask.delay(Ext.isNumber(this.enableBuffer) ? this.enableBuffer : 10);\r
269         }else{\r
270             this.combineAndSend();\r
271         }\r
272     },\r
273 \r
274     doCall : function(c, m, args){\r
275         var data = null, hs = args[m.len], scope = args[m.len+1];\r
276 \r
277         if(m.len !== 0){\r
278             data = args.slice(0, m.len);\r
279         }\r
280 \r
281         var t = new Ext.Direct.Transaction({\r
282             provider: this,\r
283             args: args,\r
284             action: c,\r
285             method: m.name,\r
286             data: data,\r
287             cb: scope && Ext.isFunction(hs) ? hs.createDelegate(scope) : hs\r
288         });\r
289 \r
290         if(this.fireEvent('beforecall', this, t) !== false){\r
291             Ext.Direct.addTransaction(t);\r
292             this.queueTransaction(t);\r
293             this.fireEvent('call', this, t);\r
294         }\r
295     },\r
296 \r
297     doForm : function(c, m, form, callback, scope){\r
298         var t = new Ext.Direct.Transaction({\r
299             provider: this,\r
300             action: c,\r
301             method: m.name,\r
302             args:[form, callback, scope],\r
303             cb: scope && Ext.isFunction(callback) ? callback.createDelegate(scope) : callback,\r
304             isForm: true\r
305         });\r
306 \r
307         if(this.fireEvent('beforecall', this, t) !== false){\r
308             Ext.Direct.addTransaction(t);\r
309             var isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data',\r
310                 params = {\r
311                     extTID: t.tid,\r
312                     extAction: c,\r
313                     extMethod: m.name,\r
314                     extType: 'rpc',\r
315                     extUpload: String(isUpload)\r
316                 };\r
317             \r
318             // change made from typeof callback check to callback.params\r
319             // to support addl param passing in DirectSubmit EAC 6/2\r
320             Ext.apply(t, {\r
321                 form: Ext.getDom(form),\r
322                 isUpload: isUpload,\r
323                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params\r
324             });\r
325             this.fireEvent('call', this, t);\r
326             this.processForm(t);\r
327         }\r
328     },\r
329     \r
330     processForm: function(t){\r
331         Ext.Ajax.request({\r
332             url: this.url,\r
333             params: t.params,\r
334             callback: this.onData,\r
335             scope: this,\r
336             form: t.form,\r
337             isUpload: t.isUpload,\r
338             ts: t\r
339         });\r
340     },\r
341 \r
342     createMethod : function(c, m){\r
343         var f;\r
344         if(!m.formHandler){\r
345             f = function(){\r
346                 this.doCall(c, m, Array.prototype.slice.call(arguments, 0));\r
347             }.createDelegate(this);\r
348         }else{\r
349             f = function(form, callback, scope){\r
350                 this.doForm(c, m, form, callback, scope);\r
351             }.createDelegate(this);\r
352         }\r
353         f.directCfg = {\r
354             action: c,\r
355             method: m\r
356         };\r
357         return f;\r
358     },\r
359 \r
360     getTransaction: function(opt){\r
361         return opt && opt.tid ? Ext.Direct.getTransaction(opt.tid) : null;\r
362     },\r
363 \r
364     doCallback: function(t, e){\r
365         var fn = e.status ? 'success' : 'failure';\r
366         if(t && t.cb){\r
367             var hs = t.cb,\r
368                 result = Ext.isDefined(e.result) ? e.result : e.data;\r
369             if(Ext.isFunction(hs)){\r
370                 hs(result, e);\r
371             } else{\r
372                 Ext.callback(hs[fn], hs.scope, [result, e]);\r
373                 Ext.callback(hs.callback, hs.scope, [result, e]);\r
374             }\r
375         }\r
376     }\r
377 });\r
378 Ext.Direct.PROVIDERS['remoting'] = Ext.direct.RemotingProvider;