Changed dataConverters key format.
[jquery.git] / src / xhr.js
1 (function( jQuery ) {
2
3 var rquery_xhr = /\?/,
4         rhash = /#.*$/,
5         rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
6         rnoContent = /^(?:GET|HEAD)$/,
7         rts = /([?&])_=[^&]*/,
8         rurl = /^(\w+:)?\/\/([^\/?#]+)/,
9         
10         sliceFunc = Array.prototype.slice;
11         
12 // Creates a jQuery xhr object
13 jQuery.xhr = function( _native ) {
14
15         if ( _native ) {
16                 return jQuery.ajaxSettings.xhr();
17         }
18         
19         function reset( force ) {
20                 
21                 // We only need to reset if we went through the init phase
22                 // (with the exception of object creation)
23                 if ( force || internal ) {
24                         
25                         // Reset callbacks lists
26                         deferred = jQuery.deferred();
27                         completeDeferred = jQuery._deferred();
28                         
29                         xhr.success = xhr.then = deferred.then;
30                         xhr.error = xhr.fail = deferred.fail;
31                         xhr.complete = completeDeferred.then;
32                         
33                         // Reset private variables
34                         requestHeaders = {};
35                         responseHeadersString = responseHeaders = internal = done = timeoutTimer = s = undefined;
36
37                         // Reset state
38                         xhr.readyState = 0;
39                         sendFlag = 0;
40
41                         // Remove responseX fields
42                         for ( var name in xhr ) {
43                                 if ( /^response/.test(name) ) {
44                                         delete xhr[name];
45                                 }
46                         }
47                 }
48         }
49
50         function init() {
51
52                 var // Options extraction
53
54                         // Remove hash character (#7531: first for string promotion)
55                         url = s.url = ( "" + s.url ).replace( rhash , "" ),
56
57                         // Uppercase the type
58                         type = s.type = s.type.toUpperCase(),
59
60                         // Determine if request has content
61                         hasContent = s.hasContent = ! rnoContent.test( type ),
62
63                         // Extract dataTypes list
64                         dataType = s.dataType,
65                         dataTypes = s.dataTypes = dataType ? jQuery.trim(dataType).toLowerCase().split(/\s+/) : ["*"],
66
67                         // Determine if a cross-domain request is in order
68                         parts = rurl.exec( url.toLowerCase() ),
69                         loc = location,
70                         crossDomain = s.crossDomain = !!( parts && ( parts[1] && parts[1] != loc.protocol || parts[2] != loc.host ) ),
71
72                         // Get other options locally
73                         data = s.data,
74                         originalContentType = s.contentType,
75                         prefilters = s.prefilters,
76                         accepts = s.accepts,
77                         headers = s.headers,
78
79                         // Other Variables
80                         transportDataType,
81                         i;
82
83                 // Convert data if not already a string
84                 if ( data && s.processData && typeof data != "string" ) {
85                         data = s.data = jQuery.param( data , s.traditional );
86                 }
87
88                 // Get internal
89                 internal = jQuery.xhr.prefilter( s ).transport( s );
90                 
91                 // Re-actualize url & data
92                 url = s.url;
93                 data = s.data;
94
95                 // If internal was found
96                 if ( internal ) {
97
98                         // Get transportDataType
99                         transportDataType = dataTypes[0];
100
101                         // More options handling for requests with no content
102                         if ( ! hasContent ) {
103
104                                 // If data is available, append data to url
105                                 if ( data ) {
106                                         url += (rquery_xhr.test(url) ? "&" : "?") + data;
107                                 }
108
109                                 // Add anti-cache in url if needed
110                                 if ( s.cache === false ) {
111
112                                         var ts = jQuery.now(),
113                                                 // try replacing _= if it is there
114                                                 ret = url.replace(rts, "$1_=" + ts );
115
116                                         // if nothing was replaced, add timestamp to the end
117                                         url = ret + ((ret == url) ? (rquery_xhr.test(url) ? "&" : "?") + "_=" + ts : "");
118                                 }
119
120                                 s.url = url;
121                         }
122
123                         // Set the correct header, if data is being sent
124                         if ( ( data && hasContent ) || originalContentType ) {
125                                 requestHeaders["content-type"] = s.contentType;
126                         }
127
128                         // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
129                         if ( s.ifModified ) {
130                                 if ( jQuery_lastModified[url] ) {
131                                         requestHeaders["if-modified-since"] = jQuery_lastModified[url];
132                                 }
133                                 if ( jQuery_etag[url] ) {
134                                         requestHeaders["if-none-match"] = jQuery_etag[url];
135                                 }
136                         }
137
138                         // Set the Accepts header for the server, depending on the dataType
139                         requestHeaders.accept = transportDataType && accepts[ transportDataType ] ?
140                                 accepts[ transportDataType ] + ( transportDataType !== "*" ? ", */*; q=0.01" : "" ) :
141                                 accepts[ "*" ];
142
143                         // Check for headers option
144                         for ( i in headers ) {
145                                 requestHeaders[ i.toLowerCase() ] = headers[ i ];
146                         }
147                 }
148                 
149                 callbackContext = s.context || s;
150                 globalEventContext = s.context ? jQuery(s.context) : jQuery.event;
151                 
152                 for ( i in { success:1, error:1, complete:1 } ) {
153                         xhr[ i ]( s[ i ] );
154                 }
155
156                 // Watch for a new set of requests
157                 if ( s.global && jQuery.active++ === 0 ) {
158                         jQuery.event.trigger( "ajaxStart" );
159                 }
160
161                 done = whenDone;
162         }
163
164         function whenDone(status, statusText, response, headers) {
165
166                 // Called once
167                 done = undefined;
168
169                 // Reset sendFlag
170                 sendFlag = 0;
171
172                 // Cache response headers
173                 responseHeadersString = headers || "";
174
175                 // Clear timeout if it exists
176                 if ( timeoutTimer ) {
177                         clearTimeout(timeoutTimer);
178                 }
179
180                 var // Reference url
181                         url = s.url,
182                         // and ifModified status
183                         ifModified = s.ifModified,
184
185                         // Is it a success?
186                         isSuccess = 0,
187                         // Stored success
188                         success,
189                         // Stored error
190                         error = statusText;
191
192                 // If not timeout, force a jQuery-compliant status text
193                 if ( statusText != "timeout" ) {
194                         statusText = ( status >= 200 && status < 300 ) ?
195                                 "success" :
196                                 ( status === 304 ? "notmodified" : "error" );
197                 }
198
199                 // If successful, handle type chaining
200                 if ( statusText === "success" || statusText === "notmodified" ) {
201
202                         // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
203                         if ( ifModified ) {
204                                 var lastModified = xhr.getResponseHeader("Last-Modified"),
205                                         etag = xhr.getResponseHeader("Etag");
206
207                                 if (lastModified) {
208                                         jQuery_lastModified[url] = lastModified;
209                                 }
210                                 if (etag) {
211                                         jQuery_etag[url] = etag;
212                                 }
213                         }
214
215                         if ( ifModified && statusText === "notmodified" ) {
216
217                                 success = null;
218                                 isSuccess = 1;
219
220                         } else {
221                                 // Chain data conversions and determine the final value
222                                 // (if an exception is thrown in the process, it'll be notified as an error)
223                                 try {
224                                         
225                                         var i,
226                                                 current,
227                                                 prev,
228                                                 checker,
229                                                 conv1,
230                                                 conv2,
231                                                 oneConv,
232                                                 convertion,
233                                                 dataTypes = s.dataTypes,
234                                                 dataCheckers = s.dataCheckers,
235                                                 dataConverters = s.dataConverters,
236                                                 dataFilter = s.dataFilter,
237                                                 responses = {
238                                                         "xml": "XML",
239                                                         "text": "Text"
240                                                 };
241                                         
242                                         for( i = 0 ; i < dataTypes.length ; i++ ) {
243                                                 
244                                                 current = dataTypes[ i ];
245                                                 
246                                                 if ( i ) {
247                                                         
248                                                         prev = dataTypes[ i - 1 ];
249                                                         
250                                                         if ( prev === "*" ) {
251                                                                 
252                                                                 prev = current;
253                                                                 
254                                                         } else if ( current !== "*" && prev !== current ) {
255                                                         
256                                                                 oneConv = conv1 = 
257                                                                         dataConverters[ ( conversion = prev + " " + current ) ] ||
258                                                                         dataConverters[ "* " + current ];
259                                                                 
260                                                                 console.log( conversion );
261                                                                 
262                                                                 if ( ! oneConv && prev !== "text" && current !== "text" ) {
263                                                                         conv1 = dataConverters[ prev + " text" ] || dataConverters[ "* text" ];
264                                                                         conv2 = dataConverters[ "text " + current ];
265                                                                 }
266                                                                 if ( oneConv || conv1 && conv2 ) {
267                                                                         response = oneConv ? conv1( response ) : conv2( conv1( response ) );
268                                                                 } else {
269                                                                         throw "no " + conversion;
270                                                                 }
271                                                         }
272                                                 }
273                                                 
274                                                 checker = dataCheckers[ current ];
275                                                 
276                                                 if ( response != null && checker ) {
277                                                         checker( response );
278                                                 }
279                                                 
280                                                 if ( responses[ current ] ) {
281                                                         xhr[ "response" + responses[ current ] ] = response;
282                                                         responses[ current ] = 0;
283                                                 }
284                                                 
285                                                 if ( ! i && dataFilter ) {
286                                                         
287                                                         response = dataFilter( response );
288                                                         
289                                                         dataTypes = s.dataTypes;
290                                                         dataFilter = 0;
291                                                         i--;
292                                                 }
293                                         }
294
295                                         // We have a real success
296                                         success = response;
297                                         isSuccess = 1;
298
299                                 } catch(e) {
300
301                                         statusText = "parsererror";
302                                         error = "" + e;
303
304                                 }
305                         }
306
307                 } else { // if not success, mark it as an error
308
309                                 error = error || statusText;
310
311                 }
312
313                 // Set data for the fake xhr object
314                 xhr.status = status;
315                 xhr.statusText = statusText;
316
317                 // Keep local copies of vars in case callbacks re-use the xhr
318                 var _s = s,
319                         _deferred = deferred,
320                         _completeDeferred = completeDeferred,
321                         _callbackContext = callbackContext,
322                         _globalEventContext = globalEventContext;
323                         
324                         
325                 // Set state if the xhr hasn't been re-used
326                 function _setState( value ) {
327                         if ( xhr.readyState && s === _s ) {
328                                 setState( value );
329                         }
330                 }
331
332                 // Really completed?
333                 if ( status && s.async ) {
334                         setState( 2 );
335                         _setState( 3 );
336                 }
337
338                 // We're done
339                 _setState( 4 );
340                 
341                 // Success/Error
342                 if ( isSuccess ) {
343                         _deferred.fire( _callbackContext , [ success , statusText , xhr ] );
344                 } else {
345                         _deferred.fireReject( _callbackContext , [ xhr , statusText , error ] );
346                 }
347                 
348                 if ( _s.global ) {
349                         _globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , [ xhr , _s , isSuccess ? success : error ] );
350                 }
351                 
352                 // Complete
353                 _completeDeferred.fire( _callbackContext, [ xhr , statusText ] );
354                 
355                 if ( _s.global ) {
356                         _globalEventContext.trigger( "ajaxComplete", [xhr, _s] );
357                         // Handle the global AJAX counter
358                         if ( ! --jQuery.active ) {
359                                 jQuery.event.trigger( "ajaxStop" );
360                         }
361                 }
362         }
363
364         // Ready state control
365         function checkState( expected , test ) {
366                 if ( expected !== true && ( expected === false || test === false || xhr.readyState !== expected ) ) {
367                         jQuery.error("INVALID_STATE_ERR");
368                 }
369         }
370
371         // Ready state change
372         function setState( value ) {
373                 xhr.readyState = value;
374                 if ( jQuery.isFunction( xhr.onreadystatechange ) ) {
375                         xhr.onreadystatechange();
376                 }
377         }
378
379         var // jQuery lists
380                 jQuery_lastModified = jQuery.lastModified,
381                 jQuery_etag = jQuery.etag,
382                 // Options object
383                 s,
384                 // Callback stuff
385                 callbackContext,
386                 globalEventContext,
387                 deferred,
388                 completeDeferred,
389                 // Headers (they are sent all at once)
390                 requestHeaders,
391                 // Response headers
392                 responseHeadersString,
393                 responseHeaders,
394                 // Done callback
395                 done,
396                 // transport
397                 internal,
398                 // timeout handle
399                 timeoutTimer,
400                 // The send flag
401                 sendFlag,
402                 // Fake xhr
403                 xhr = {
404                         // state
405                         readyState: 0,
406
407                         // Callback
408                         onreadystatechange: null,
409
410                         // Open
411                         open: function(type, url, async, username, password) {
412
413                                 xhr.abort();
414                                 reset();
415
416                                 s = {
417                                         type: type,
418                                         url: url,
419                                         async: async,
420                                         username: username,
421                                         password: password
422                                 };
423
424                                 setState(1);
425
426                                 return xhr;
427                         },
428
429                         // Send
430                         send: function(data, moreOptions) {
431
432                                 checkState(1 , !sendFlag);
433
434                                 s.data = data;
435
436                                 s = jQuery.extend( true,
437                                         {},
438                                         jQuery.ajaxSettings,
439                                         s,
440                                         moreOptions || ( moreOptions === false ? { global: false } : {} ) );
441
442                                 if ( moreOptions ) {
443                                         // We force the original context
444                                         // (plain objects used as context get extended)
445                                         s.context = moreOptions.context;
446                                 }
447
448                                 init();
449
450                                 // If not internal, abort
451                                 if ( ! internal ) {
452                                         done( 0 , "transport not found" );
453                                         return false;
454                                 }
455
456                                 // Allow custom headers/mimetypes and early abort
457                                 if ( s.beforeSend ) {
458
459                                         var _s = s;
460
461                                         if ( s.beforeSend.call(callbackContext, xhr, s) === false || ! xhr.readyState || _s !== s ) {
462
463                                                 // Abort if not done
464                                                 if ( xhr.readyState && _s === s ) {
465                                                         xhr.abort();
466                                                 }
467
468                                                 // Handle the global AJAX counter
469                                                 if ( _s.global && ! --jQuery.active ) {
470                                                         jQuery.event.trigger( "ajaxStop" );
471                                                 }
472
473                                                 return false;
474                                         }
475                                 }
476
477                                 sendFlag = 1;
478
479                                 // Send global event
480                                 if ( s.global ) {
481                                         globalEventContext.trigger("ajaxSend", [xhr, s]);
482                                 }
483
484                                 // Timeout
485                                 if ( s.async && s.timeout > 0 ) {
486                                         timeoutTimer = setTimeout(function(){
487                                                 xhr.abort("timeout");
488                                         }, s.timeout);
489                                 }
490
491                                 if ( s.async ) {
492                                         setState(1);
493                                 }
494
495                                 try {
496
497                                         internal.send(requestHeaders, done);
498                                         return xhr;
499
500                                 } catch (e) {
501
502                                         if ( done ) {
503
504                                                 done(0, "error", "" + e);
505
506                                         } else {
507
508                                                 jQuery.error(e);
509
510                                         }
511                                 }
512
513                                 return false;
514                         },
515
516                         // Caches the header
517                         setRequestHeader: function(name,value) {
518                                 checkState(1, !sendFlag);
519                                 requestHeaders[ name.toLowerCase() ] = value;
520                                 return xhr;
521                         },
522
523                         // Raw string
524                         getAllResponseHeaders: function() {
525                                 return xhr.readyState <= 1 ? "" : responseHeadersString;
526                         },
527
528                         // Builds headers hashtable if needed
529                         getResponseHeader: function( key ) {
530
531                                 if ( xhr.readyState <= 1 ) {
532
533                                         return null;
534
535                                 }
536
537                                 if ( responseHeaders === undefined ) {
538
539                                         responseHeaders = {};
540
541                                         if ( typeof responseHeadersString === "string" ) {
542
543                                                 var match;
544
545                                                 while( ( match = rheaders.exec( responseHeadersString ) ) ) {
546                                                         responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
547                                                 }
548                                         }
549                                 }
550                                 return responseHeaders[ key.toLowerCase() ];
551                         },
552
553                         // Cancel the request
554                         abort: function(statusText) {
555                                 if (internal) {
556                                         internal.abort( statusText || "abort" );
557                                 }
558                                 xhr.readyState = 0;
559                         }
560                 };
561
562         // Init data (so that we can bind callbacks early
563         reset(1);
564
565         // Return the xhr emulation
566         return xhr;
567 };
568
569 // Execute or select from functions in a given structure of options
570 function xhr_selectOrExecute( structure , s ) {
571
572         var dataTypes = s.dataTypes,
573                 transportDataType,
574                 list,
575                 selected,
576                 i,
577                 length,
578                 checked = {},
579                 flag,
580                 noSelect = structure !== "transports";
581                 
582         function initSearch( dataType ) {
583
584                 flag = transportDataType !== dataType && ! checked[ dataType ];
585
586                 if ( flag ) {
587
588                         checked[ dataType ] = 1;
589                         transportDataType = dataType;
590                         list = s[ structure ][ dataType ];
591                         i = -1;
592                         length = list ? list.length : 0 ;
593                 }
594
595                 return flag;
596         }
597
598         initSearch( dataTypes[ 0 ] );
599
600         for ( i = 0 ; ( noSelect || ! selected ) && i <= length ; i++ ) {
601                 
602                 if ( i === length ) {
603
604                         initSearch( "*" );
605
606                 } else {
607
608                         selected = list[ i ]( s , determineDataType );
609
610                         // If we got redirected to another dataType
611                         // Search there (if not in progress or already tried)
612                         if ( typeof( selected ) === "string" &&
613                                 initSearch( selected ) ) {
614
615                                 dataTypes.unshift( selected );
616                                 selected = 0;
617                         }
618                 }
619         }
620
621         return noSelect ? jQuery.xhr : selected;
622 }
623
624 // Add an element to one of the xhr structures in ajaxSettings
625 function xhr_addElement( structure , args ) {
626                 
627         var i,
628                 j,
629                 start = 0,
630                 length = args.length,
631                 dataTypes = [ "*" ],
632                 dLength = 1,
633                 dataType,
634                 functors = [],
635                 first,
636                 append,
637                 list;
638                 
639         if ( length ) {
640                 
641                 first = jQuery.type( args[ 0 ] );
642                 
643                 if ( first === "object" ) {
644                         return xhr_selectOrExecute( structure , args[ 0 ] );
645                 }
646                 
647                 structure = jQuery.ajaxSettings[ structure ];
648                 
649                 if ( first !== "function" ) {
650                         
651                         dataTypes = args[ 0 ].toLowerCase().split(/\s+/);
652                         dLength = dataTypes.length;
653                         start = 1;
654                         
655                 }
656                 
657                 if ( dLength && start < length ) {
658                         
659                         functors = sliceFunc.call( args , start );
660                         
661                         length -= start;
662                                         
663                         for( i = 0 ; i < dLength ; i++ ) {
664                                 
665                                 dataType = dataTypes[ i ];
666                                 
667                                 first = /^\+/.test( dataType );
668                                 
669                                 if (first) {
670                                         dataType = dataType.substr(1);
671                                 }
672                                 
673                                 if ( dataType !== "" ) {
674                                 
675                                         append = Array.prototype[ first ? "unshift" : "push" ];
676                                         
677                                         list = structure[ dataType ] = structure[ dataType ] || [];
678                         
679                                         for ( j = 0; j < length; j++ ) {
680                                                 append.call( list , functors[ j ] );
681                                         }
682                                 }
683                         }
684                 }
685         }
686         
687         return jQuery.xhr;
688 }
689
690 // Install prefilter & transport methods
691 jQuery.each( [ "prefilter" , "transport" ] , function( _ , name ) {
692         _ = name + "s";
693         jQuery.xhr[ name ] = function() {
694                 return xhr_addElement( _ , arguments );
695         };
696 } );
697         
698 // Utility function that handles dataType when response is received
699 // (for those transports that can give text or xml responses)
700 function determineDataType( s , ct , text , xml ) {
701
702         var autoDataType = s.autoDataType,
703                 type,
704                 regexp,
705                 dataTypes = s.dataTypes,
706                 transportDataType = dataTypes[0],
707                 response;
708
709         // Auto (xml, json, script or text determined given headers)
710         if ( transportDataType === "*" ) {
711
712                 for ( type in autoDataType ) {
713                         if ( ( regexp = autoDataType[ type ] ) && regexp.test( ct ) ) {
714                                 transportDataType = dataTypes[0] = type;
715                                 break;
716                         }
717                 }
718         }
719
720         // xml and parsed as such
721         if ( transportDataType === "xml" &&
722                 xml &&
723                 xml.documentElement /* #4958 */ ) {
724
725                 response = xml;
726
727         // Text response was provided
728         } else {
729
730                 response = text;
731
732                 // If it's not really text, defer to dataConverters
733                 if ( transportDataType !== "text" ) {
734                         dataTypes.unshift( "text" );
735                 }
736
737         }
738
739         return response;
740 }
741
742 })( jQuery );