Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / src / ext-core / src / data / Connection.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 (function(){
8     var BEFOREREQUEST = "beforerequest",
9         REQUESTCOMPLETE = "requestcomplete",
10         REQUESTEXCEPTION = "requestexception",
11         UNDEFINED = undefined,
12         LOAD = 'load',
13         POST = 'POST',
14         GET = 'GET',
15         WINDOW = window;
16
17     /**
18      * @class Ext.data.Connection
19      * @extends Ext.util.Observable
20      * <p>The class encapsulates a connection to the page's originating domain, allowing requests to be made
21      * either to a configured URL, or to a URL specified at request time.</p>
22      * <p>Requests made by this class are asynchronous, and will return immediately. No data from
23      * the server will be available to the statement immediately following the {@link #request} call.
24      * To process returned data, use a
25      * <a href="#request-option-success" ext:member="request-option-success" ext:cls="Ext.data.Connection">success callback</a>
26      * in the request options object,
27      * or an {@link #requestcomplete event listener}.</p>
28      * <p><h3>File Uploads</h3><a href="#request-option-isUpload" ext:member="request-option-isUpload" ext:cls="Ext.data.Connection">File uploads</a> are not performed using normal "Ajax" techniques, that
29      * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
30      * manner with the DOM <tt>&lt;form></tt> element temporarily modified to have its
31      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
32      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
33      * but removed after the return data has been gathered.</p>
34      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
35      * server is using JSON to send the return object, then the
36      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
37      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
38      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
39      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
40      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
41      * is created containing a <tt>responseText</tt> property in order to conform to the
42      * requirements of event handlers and callbacks.</p>
43      * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
44      * and some server technologies (notably JEE) may require some custom processing in order to
45      * retrieve parameter names and parameter values from the packet content.</p>
46      * @constructor
47      * @param {Object} config a configuration object.
48      */
49     Ext.data.Connection = function(config){
50         Ext.apply(this, config);
51         this.addEvents(
52             /**
53              * @event beforerequest
54              * Fires before a network request is made to retrieve a data object.
55              * @param {Connection} conn This Connection object.
56              * @param {Object} options The options config object passed to the {@link #request} method.
57              */
58             BEFOREREQUEST,
59             /**
60              * @event requestcomplete
61              * Fires if the request was successfully completed.
62              * @param {Connection} conn This Connection object.
63              * @param {Object} response The XHR object containing the response data.
64              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
65              * for details.
66              * @param {Object} options The options config object passed to the {@link #request} method.
67              */
68             REQUESTCOMPLETE,
69             /**
70              * @event requestexception
71              * Fires if an error HTTP status was returned from the server.
72              * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
73              * for details of HTTP status codes.
74              * @param {Connection} conn This Connection object.
75              * @param {Object} response The XHR object containing the response data.
76              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
77              * for details.
78              * @param {Object} options The options config object passed to the {@link #request} method.
79              */
80             REQUESTEXCEPTION
81         );
82         Ext.data.Connection.superclass.constructor.call(this);
83     };
84
85     Ext.extend(Ext.data.Connection, Ext.util.Observable, {
86         /**
87          * @cfg {String} url (Optional) <p>The default URL to be used for requests to the server. Defaults to undefined.</p>
88          * <p>The <code>url</code> config may be a function which <i>returns</i> the URL to use for the Ajax request. The scope
89          * (<code><b>this</b></code> reference) of the function is the <code>scope</code> option passed to the {@link #request} method.</p>
90          */
91         /**
92          * @cfg {Object} extraParams (Optional) An object containing properties which are used as
93          * extra parameters to each request made by this object. (defaults to undefined)
94          */
95         /**
96          * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
97          *  to each request made by this object. (defaults to undefined)
98          */
99         /**
100          * @cfg {String} method (Optional) The default HTTP method to be used for requests.
101          * (defaults to undefined; if not set, but {@link #request} params are present, POST will be used;
102          * otherwise, GET will be used.)
103          */
104         /**
105          * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
106          */
107         timeout : 30000,
108         /**
109          * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
110          * @type Boolean
111          */
112         autoAbort:false,
113
114         /**
115          * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
116          * @type Boolean
117          */
118         disableCaching: true,
119
120         /**
121          * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
122          * through a cache buster. Defaults to '_dc'
123          * @type String
124          */
125         disableCachingParam: '_dc',
126
127         /**
128          * <p>Sends an HTTP request to a remote server.</p>
129          * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
130          * return before the response has been received. Process any returned data
131          * in a callback function.</p>
132          * <pre><code>
133 Ext.Ajax.request({
134    url: 'ajax_demo/sample.json',
135    success: function(response, opts) {
136       var obj = Ext.decode(response.responseText);
137       console.dir(obj);
138    },
139    failure: function(response, opts) {
140       console.log('server-side failure with status code ' + response.status);
141    }
142 });
143          * </code></pre>
144          * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
145          * @param {Object} options An object which may contain the following properties:<ul>
146          * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
147          * which to send the request, or a function to call which returns a URL string. The scope of the
148          * function is specified by the <tt>scope</tt> option. Defaults to the configured
149          * <tt>{@link #url}</tt>.</div></li>
150          * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
151          * An object containing properties which are used as parameters to the
152          * request, a url encoded string or a function to call to get either. The scope of the function
153          * is specified by the <tt>scope</tt> option.</div></li>
154          * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
155          * for the request. Defaults to the configured method, or if no method was configured,
156          * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
157          * the method name is case-sensitive and should be all caps.</div></li>
158          * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
159          * function to be called upon receipt of the HTTP response. The callback is
160          * called regardless of success or failure and is passed the following
161          * parameters:<ul>
162          * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
163          * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
164          * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
165          * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
166          * accessing elements of the response.</div></li>
167          * </ul></div></li>
168          * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
169          * to be called upon success of the request. The callback is passed the following
170          * parameters:<ul>
171          * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
172          * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
173          * </ul></div></li>
174          * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
175          * to be called upon failure of the request. The callback is passed the
176          * following parameters:<ul>
177          * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
178          * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
179          * </ul></div></li>
180          * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
181          * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
182          * specified as functions from which to draw values, then this also serves as the scope for those function calls.
183          * Defaults to the browser window.</div></li>
184          * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
185          * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt>&lt;form&gt;</tt>
186          * Element or the id of the <tt>&lt;form&gt;</tt> to pull parameters from.</div></li>
187          * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
188          * with the <tt>form</tt> option</b>.
189          * <p>True if the form object is a file upload (will be set automatically if the form was
190          * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
191          * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
192          * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
193          * DOM <tt>&lt;form></tt> element temporarily modified to have its
194          * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
195          * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
196          * but removed after the return data has been gathered.</p>
197          * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
198          * server is using JSON to send the return object, then the
199          * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
200          * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
201          * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
202          * is created containing a <tt>responseText</tt> property in order to conform to the
203          * requirements of event handlers and callbacks.</p>
204          * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
205          * and some server technologies (notably JEE) may require some custom processing in order to
206          * retrieve parameter names and parameter values from the packet content.</p>
207          * </div></li>
208          * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
209          * headers to set for the request.</div></li>
210          * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
211          * to use for the post. Note: This will be used instead of params for the post
212          * data. Any params will be appended to the URL.</div></li>
213          * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
214          * data to use as the post. Note: This will be used instead of params for the post
215          * data. Any params will be appended to the URL.</div></li>
216          * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
217          * to add a unique cache-buster param to GET requests.</div></li>
218          * </ul></p>
219          * <p>The options object may also contain any other property which might be needed to perform
220          * postprocessing in a callback because it is passed to callback functions.</p>
221          * @return {Number} transactionId The id of the server transaction. This may be used
222          * to cancel the request.
223          */
224         request : function(o){
225             var me = this;
226             if(me.fireEvent(BEFOREREQUEST, me, o)){
227                 if (o.el) {
228                     if(!Ext.isEmpty(o.indicatorText)){
229                         me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
230                     }
231                     if(me.indicatorText) {
232                         Ext.getDom(o.el).innerHTML = me.indicatorText;
233                     }
234                     o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
235                         Ext.getDom(o.el).innerHTML = response.responseText;
236                     });
237                 }
238
239                 var p = o.params,
240                     url = o.url || me.url,
241                     method,
242                     cb = {success: me.handleResponse,
243                           failure: me.handleFailure,
244                           scope: me,
245                           argument: {options: o},
246                           timeout : o.timeout || me.timeout
247                     },
248                     form,
249                     serForm;
250
251
252                 if (Ext.isFunction(p)) {
253                     p = p.call(o.scope||WINDOW, o);
254                 }
255
256                 p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
257
258                 if (Ext.isFunction(url)) {
259                     url = url.call(o.scope || WINDOW, o);
260                 }
261
262                 if((form = Ext.getDom(o.form))){
263                     url = url || form.action;
264                      if(o.isUpload || /multipart\/form-data/i.test(form.getAttribute("enctype"))) {
265                          return me.doFormUpload.call(me, o, p, url);
266                      }
267                     serForm = Ext.lib.Ajax.serializeForm(form);
268                     p = p ? (p + '&' + serForm) : serForm;
269                 }
270
271                 method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
272
273                 if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
274                     var dcp = o.disableCachingParam || me.disableCachingParam;
275                     url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
276                 }
277
278                 o.headers = Ext.apply(o.headers || {}, me.defaultHeaders || {});
279
280                 if(o.autoAbort === true || me.autoAbort) {
281                     me.abort();
282                 }
283
284                 if((method == GET || o.xmlData || o.jsonData) && p){
285                     url = Ext.urlAppend(url, p);
286                     p = '';
287                 }
288                 return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
289             }else{
290                 return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
291             }
292         },
293
294         /**
295          * Determine whether this object has a request outstanding.
296          * @param {Number} transactionId (Optional) defaults to the last transaction
297          * @return {Boolean} True if there is an outstanding request.
298          */
299         isLoading : function(transId){
300             return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
301         },
302
303         /**
304          * Aborts any outstanding request.
305          * @param {Number} transactionId (Optional) defaults to the last transaction
306          */
307         abort : function(transId){
308             if(transId || this.isLoading()){
309                 Ext.lib.Ajax.abort(transId || this.transId);
310             }
311         },
312
313         // private
314         handleResponse : function(response){
315             this.transId = false;
316             var options = response.argument.options;
317             response.argument = options ? options.argument : null;
318             this.fireEvent(REQUESTCOMPLETE, this, response, options);
319             if(options.success){
320                 options.success.call(options.scope, response, options);
321             }
322             if(options.callback){
323                 options.callback.call(options.scope, options, true, response);
324             }
325         },
326
327         // private
328         handleFailure : function(response, e){
329             this.transId = false;
330             var options = response.argument.options;
331             response.argument = options ? options.argument : null;
332             this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
333             if(options.failure){
334                 options.failure.call(options.scope, response, options);
335             }
336             if(options.callback){
337                 options.callback.call(options.scope, options, false, response);
338             }
339         },
340
341         // private
342         doFormUpload : function(o, ps, url){
343             var id = Ext.id(),
344                 doc = document,
345                 frame = doc.createElement('iframe'),
346                 form = Ext.getDom(o.form),
347                 hiddens = [],
348                 hd,
349                 encoding = 'multipart/form-data',
350                 buf = {
351                     target: form.target,
352                     method: form.method,
353                     encoding: form.encoding,
354                     enctype: form.enctype,
355                     action: form.action
356                 };
357
358             Ext.fly(frame).set({
359                 id: id,
360                 name: id,
361                 cls: 'x-hidden'
362
363             });
364
365             doc.body.appendChild(frame);
366
367             //Reset the Frame to neutral domain
368             Ext.fly(frame).set({
369                src : Ext.SSL_SECURE_URL
370             });
371
372             // This is required so that IE doesn't pop the response up in a new window.
373             if(Ext.isIE){
374                document.frames[id].name = id;
375             }
376
377
378             Ext.fly(form).set({
379                 target: id,
380                 method: POST,
381                 enctype: encoding,
382                 encoding: encoding,
383                 action: url || buf.action
384             });
385
386             // add dynamic params
387             Ext.iterate(Ext.urlDecode(ps, false), function(k, v){
388                 hd = doc.createElement('input');
389                 Ext.fly(hd).set({
390                     type: 'hidden',
391                     value: v,
392                     name: k
393                 });
394                 form.appendChild(hd);
395                 hiddens.push(hd);
396             });
397
398             function cb(){
399                 var me = this,
400                     // bogus response object
401                     r = {responseText : '',
402                          responseXML : null,
403                          argument : o.argument},
404                     doc,
405                     firstChild;
406
407                 try{
408                     doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
409                     if(doc){
410                         if(doc.body){
411                             if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ // json response wrapped in textarea
412                                 r.responseText = firstChild.value;
413                             }else{
414                                 r.responseText = doc.body.innerHTML;
415                             }
416                         }
417                         //in IE the document may still have a body even if returns XML.
418                         r.responseXML = doc.XMLDocument || doc;
419                     }
420                 }
421                 catch(e) {}
422
423                 Ext.EventManager.removeListener(frame, LOAD, cb, me);
424
425                 me.fireEvent(REQUESTCOMPLETE, me, r, o);
426
427                 function runCallback(fn, scope, args){
428                     if(Ext.isFunction(fn)){
429                         fn.apply(scope, args);
430                     }
431                 }
432
433                 runCallback(o.success, o.scope, [r, o]);
434                 runCallback(o.callback, o.scope, [o, true, r]);
435
436                 if(!me.debugUploads){
437                     setTimeout(function(){Ext.removeNode(frame);}, 100);
438                 }
439             }
440
441             Ext.EventManager.on(frame, LOAD, cb, this);
442             form.submit();
443
444             Ext.fly(form).set(buf);
445             Ext.each(hiddens, function(h) {
446                 Ext.removeNode(h);
447             });
448         }
449     });
450 })();
451
452 /**
453  * @class Ext.Ajax
454  * @extends Ext.data.Connection
455  * <p>The global Ajax request class that provides a simple way to make Ajax requests
456  * with maximum flexibility.</p>
457  * <p>Since Ext.Ajax is a singleton, you can set common properties/events for it once
458  * and override them at the request function level only if necessary.</p>
459  * <p>Common <b>Properties</b> you may want to set are:<div class="mdetail-params"><ul>
460  * <li><b><tt>{@link #method}</tt></b><p class="sub-desc"></p></li>
461  * <li><b><tt>{@link #extraParams}</tt></b><p class="sub-desc"></p></li>
462  * <li><b><tt>{@link #url}</tt></b><p class="sub-desc"></p></li>
463  * </ul></div>
464  * <pre><code>
465 // Default headers to pass in every request
466 Ext.Ajax.defaultHeaders = {
467     'Powered-By': 'Ext'
468 };
469  * </code></pre>
470  * </p>
471  * <p>Common <b>Events</b> you may want to set are:<div class="mdetail-params"><ul>
472  * <li><b><tt>{@link Ext.data.Connection#beforerequest beforerequest}</tt></b><p class="sub-desc"></p></li>
473  * <li><b><tt>{@link Ext.data.Connection#requestcomplete requestcomplete}</tt></b><p class="sub-desc"></p></li>
474  * <li><b><tt>{@link Ext.data.Connection#requestexception requestexception}</tt></b><p class="sub-desc"></p></li>
475  * </ul></div>
476  * <pre><code>
477 // Example: show a spinner during all Ajax requests
478 Ext.Ajax.on('beforerequest', this.showSpinner, this);
479 Ext.Ajax.on('requestcomplete', this.hideSpinner, this);
480 Ext.Ajax.on('requestexception', this.hideSpinner, this);
481  * </code></pre>
482  * </p>
483  * <p>An example request:</p>
484  * <pre><code>
485 // Basic request
486 Ext.Ajax.{@link Ext.data.Connection#request request}({
487    url: 'foo.php',
488    success: someFn,
489    failure: otherFn,
490    headers: {
491        'my-header': 'foo'
492    },
493    params: { foo: 'bar' }
494 });
495
496 // Simple ajax form submission
497 Ext.Ajax.{@link Ext.data.Connection#request request}({
498     form: 'some-form',
499     params: 'foo=bar'
500 });
501  * </code></pre>
502  * </p>
503  * @singleton
504  */
505 Ext.Ajax = new Ext.data.Connection({
506     /**
507      * @cfg {String} url @hide
508      */
509     /**
510      * @cfg {Object} extraParams @hide
511      */
512     /**
513      * @cfg {Object} defaultHeaders @hide
514      */
515     /**
516      * @cfg {String} method (Optional) @hide
517      */
518     /**
519      * @cfg {Number} timeout (Optional) @hide
520      */
521     /**
522      * @cfg {Boolean} autoAbort (Optional) @hide
523      */
524
525     /**
526      * @cfg {Boolean} disableCaching (Optional) @hide
527      */
528
529     /**
530      * @property  disableCaching
531      * True to add a unique cache-buster param to GET requests. (defaults to true)
532      * @type Boolean
533      */
534     /**
535      * @property  url
536      * The default URL to be used for requests to the server. (defaults to undefined)
537      * If the server receives all requests through one URL, setting this once is easier than
538      * entering it on every request.
539      * @type String
540      */
541     /**
542      * @property  extraParams
543      * An object containing properties which are used as extra parameters to each request made
544      * by this object (defaults to undefined). Session information and other data that you need
545      * to pass with each request are commonly put here.
546      * @type Object
547      */
548     /**
549      * @property  defaultHeaders
550      * An object containing request headers which are added to each request made by this object
551      * (defaults to undefined).
552      * @type Object
553      */
554     /**
555      * @property  method
556      * The default HTTP method to be used for requests. Note that this is case-sensitive and
557      * should be all caps (defaults to undefined; if not set but params are present will use
558      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
559      * @type String
560      */
561     /**
562      * @property  timeout
563      * The timeout in milliseconds to be used for requests. (defaults to 30000)
564      * @type Number
565      */
566
567     /**
568      * @property  autoAbort
569      * Whether a new request should abort any pending requests. (defaults to false)
570      * @type Boolean
571      */
572     autoAbort : false,
573
574     /**
575      * Serialize the passed form into a url encoded string
576      * @param {String/HTMLElement} form
577      * @return {String}
578      */
579     serializeForm : function(form){
580         return Ext.lib.Ajax.serializeForm(form);
581     }
582 });