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