Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / src / ext-core / src / adapter / ext-base-ajax.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /*
8 * Portions of this file are based on pieces of Yahoo User Interface Library
9 * Copyright (c) 2007, Yahoo! Inc. All rights reserved.
10 * YUI licensed under the BSD License:
11 * http://developer.yahoo.net/yui/license.txt
12 */
13 Ext.lib.Ajax = function() {
14     var activeX = ['MSXML2.XMLHTTP.3.0',
15                    'MSXML2.XMLHTTP',
16                    'Microsoft.XMLHTTP'],
17         CONTENTTYPE = 'Content-Type';
18
19     // private
20     function setHeader(o) {
21         var conn = o.conn,
22             prop;
23
24         function setTheHeaders(conn, headers){
25             for (prop in headers) {
26                 if (headers.hasOwnProperty(prop)) {
27                     conn.setRequestHeader(prop, headers[prop]);
28                 }
29             }
30         }
31
32         if (pub.defaultHeaders) {
33             setTheHeaders(conn, pub.defaultHeaders);
34         }
35
36         if (pub.headers) {
37             setTheHeaders(conn, pub.headers);
38             delete pub.headers;
39         }
40     }
41
42     // private
43     function createExceptionObject(tId, callbackArg, isAbort, isTimeout) {
44         return {
45             tId : tId,
46             status : isAbort ? -1 : 0,
47             statusText : isAbort ? 'transaction aborted' : 'communication failure',
48             isAbort: isAbort,
49             isTimeout: isTimeout,
50             argument : callbackArg
51         };
52     }
53
54     // private
55     function initHeader(label, value) {
56         (pub.headers = pub.headers || {})[label] = value;
57     }
58
59     // private
60     function createResponseObject(o, callbackArg) {
61         var headerObj = {},
62             headerStr,
63             conn = o.conn,
64             t,
65             s,
66             // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
67             isBrokenStatus = conn.status == 1223;
68
69         try {
70             headerStr = o.conn.getAllResponseHeaders();
71             Ext.each(headerStr.replace(/\r\n/g, '\n').split('\n'), function(v){
72                 t = v.indexOf(':');
73                 if(t >= 0){
74                     s = v.substr(0, t).toLowerCase();
75                     if(v.charAt(t + 1) == ' '){
76                         ++t;
77                     }
78                     headerObj[s] = v.substr(t + 1);
79                 }
80             });
81         } catch(e) {}
82
83         return {
84             tId : o.tId,
85             // Normalize the status and statusText when IE returns 1223, see the above link.
86             status : isBrokenStatus ? 204 : conn.status,
87             statusText : isBrokenStatus ? 'No Content' : conn.statusText,
88             getResponseHeader : function(header){return headerObj[header.toLowerCase()];},
89             getAllResponseHeaders : function(){return headerStr},
90             responseText : conn.responseText,
91             responseXML : conn.responseXML,
92             argument : callbackArg
93         };
94     }
95
96     // private
97     function releaseObject(o) {
98         if (o.tId) {
99             pub.conn[o.tId] = null;
100         }
101         o.conn = null;
102         o = null;
103     }
104
105     // private
106     function handleTransactionResponse(o, callback, isAbort, isTimeout) {
107         if (!callback) {
108             releaseObject(o);
109             return;
110         }
111
112         var httpStatus, responseObject;
113
114         try {
115             if (o.conn.status !== undefined && o.conn.status != 0) {
116                 httpStatus = o.conn.status;
117             }
118             else {
119                 httpStatus = 13030;
120             }
121         }
122         catch(e) {
123             httpStatus = 13030;
124         }
125
126         if ((httpStatus >= 200 && httpStatus < 300) || (Ext.isIE && httpStatus == 1223)) {
127             responseObject = createResponseObject(o, callback.argument);
128             if (callback.success) {
129                 if (!callback.scope) {
130                     callback.success(responseObject);
131                 }
132                 else {
133                     callback.success.apply(callback.scope, [responseObject]);
134                 }
135             }
136         }
137         else {
138             switch (httpStatus) {
139                 case 12002:
140                 case 12029:
141                 case 12030:
142                 case 12031:
143                 case 12152:
144                 case 13030:
145                     responseObject = createExceptionObject(o.tId, callback.argument, (isAbort ? isAbort : false), isTimeout);
146                     if (callback.failure) {
147                         if (!callback.scope) {
148                             callback.failure(responseObject);
149                         }
150                         else {
151                             callback.failure.apply(callback.scope, [responseObject]);
152                         }
153                     }
154                     break;
155                 default:
156                     responseObject = createResponseObject(o, callback.argument);
157                     if (callback.failure) {
158                         if (!callback.scope) {
159                             callback.failure(responseObject);
160                         }
161                         else {
162                             callback.failure.apply(callback.scope, [responseObject]);
163                         }
164                     }
165             }
166         }
167
168         releaseObject(o);
169         responseObject = null;
170     }
171
172     // private
173     function handleReadyState(o, callback){
174     callback = callback || {};
175         var conn = o.conn,
176             tId = o.tId,
177             poll = pub.poll,
178             cbTimeout = callback.timeout || null;
179
180         if (cbTimeout) {
181             pub.conn[tId] = conn;
182             pub.timeout[tId] = setTimeout(function() {
183                 pub.abort(o, callback, true);
184             }, cbTimeout);
185         }
186
187         poll[tId] = setInterval(
188             function() {
189                 if (conn && conn.readyState == 4) {
190                     clearInterval(poll[tId]);
191                     poll[tId] = null;
192
193                     if (cbTimeout) {
194                         clearTimeout(pub.timeout[tId]);
195                         pub.timeout[tId] = null;
196                     }
197
198                     handleTransactionResponse(o, callback);
199                 }
200             },
201             pub.pollInterval);
202     }
203
204     // private
205     function asyncRequest(method, uri, callback, postData) {
206         var o = getConnectionObject() || null;
207
208         if (o) {
209             o.conn.open(method, uri, true);
210
211             if (pub.useDefaultXhrHeader) {
212                 initHeader('X-Requested-With', pub.defaultXhrHeader);
213             }
214
215             if(postData && pub.useDefaultHeader && (!pub.headers || !pub.headers[CONTENTTYPE])){
216                 initHeader(CONTENTTYPE, pub.defaultPostHeader);
217             }
218
219             if (pub.defaultHeaders || pub.headers) {
220                 setHeader(o);
221             }
222
223             handleReadyState(o, callback);
224             o.conn.send(postData || null);
225         }
226         return o;
227     }
228
229     // private
230     function getConnectionObject() {
231         var o;
232
233         try {
234             if (o = createXhrObject(pub.transactionId)) {
235                 pub.transactionId++;
236             }
237         } catch(e) {
238         } finally {
239             return o;
240         }
241     }
242
243     // private
244     function createXhrObject(transactionId) {
245         var http;
246
247         try {
248             http = new XMLHttpRequest();
249         } catch(e) {
250             for (var i = 0; i < activeX.length; ++i) {
251                 try {
252                     http = new ActiveXObject(activeX[i]);
253                     break;
254                 } catch(e) {}
255             }
256         } finally {
257             return {conn : http, tId : transactionId};
258         }
259     }
260
261     var pub = {
262         request : function(method, uri, cb, data, options) {
263             if(options){
264                 var me = this,
265                     xmlData = options.xmlData,
266                     jsonData = options.jsonData,
267                     hs;
268
269                 Ext.applyIf(me, options);
270
271                 if(xmlData || jsonData){
272                     hs = me.headers;
273                     if(!hs || !hs[CONTENTTYPE]){
274                         initHeader(CONTENTTYPE, xmlData ? 'text/xml' : 'application/json');
275                     }
276                     data = xmlData || (!Ext.isPrimitive(jsonData) ? Ext.encode(jsonData) : jsonData);
277                 }
278             }
279             return asyncRequest(method || options.method || "POST", uri, cb, data);
280         },
281
282         serializeForm : function(form) {
283             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
284                 hasSubmit = false,
285                 encoder = encodeURIComponent,
286                 element,
287                 options,
288                 name,
289                 val,
290                 data = '',
291                 type;
292
293             Ext.each(fElements, function(element) {
294                 name = element.name;
295                 type = element.type;
296
297                 if (!element.disabled && name){
298                     if(/select-(one|multiple)/i.test(type)) {
299                         Ext.each(element.options, function(opt) {
300                             if (opt.selected) {
301                                 data += String.format("{0}={1}&", encoder(name), encoder((opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttribute('value') !== null) ? opt.value : opt.text));
302                             }
303                         });
304                     } else if(!/file|undefined|reset|button/i.test(type)) {
305                             if(!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)){
306
307                                 data += encoder(name) + '=' + encoder(element.value) + '&';
308                                 hasSubmit = /submit/i.test(type);
309                             }
310                     }
311                 }
312             });
313             return data.substr(0, data.length - 1);
314         },
315
316         useDefaultHeader : true,
317         defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
318         useDefaultXhrHeader : true,
319         defaultXhrHeader : 'XMLHttpRequest',
320         poll : {},
321         timeout : {},
322         conn: {},
323         pollInterval : 50,
324         transactionId : 0,
325
326 //  This is never called - Is it worth exposing this?
327 //          setProgId : function(id) {
328 //              activeX.unshift(id);
329 //          },
330
331 //  This is never called - Is it worth exposing this?
332 //          setDefaultPostHeader : function(b) {
333 //              this.useDefaultHeader = b;
334 //          },
335
336 //  This is never called - Is it worth exposing this?
337 //          setDefaultXhrHeader : function(b) {
338 //              this.useDefaultXhrHeader = b;
339 //          },
340
341 //  This is never called - Is it worth exposing this?
342 //          setPollingInterval : function(i) {
343 //              if (typeof i == 'number' && isFinite(i)) {
344 //                  this.pollInterval = i;
345 //              }
346 //          },
347
348 //  This is never called - Is it worth exposing this?
349 //          resetDefaultHeaders : function() {
350 //              this.defaultHeaders = null;
351 //          },
352
353         abort : function(o, callback, isTimeout) {
354             var me = this,
355                 tId = o.tId,
356                 isAbort = false;
357
358             if (me.isCallInProgress(o)) {
359                 o.conn.abort();
360                 clearInterval(me.poll[tId]);
361                 me.poll[tId] = null;
362                 clearTimeout(pub.timeout[tId]);
363                 me.timeout[tId] = null;
364
365                 handleTransactionResponse(o, callback, (isAbort = true), isTimeout);
366             }
367             return isAbort;
368         },
369
370         isCallInProgress : function(o) {
371             // if there is a connection and readyState is not 0 or 4
372             return o.conn && !{0:true,4:true}[o.conn.readyState];
373         }
374     };
375     return pub;
376 }();