Removed dataCheckers, added true as possible value for dataConverters indicating...
[jquery.git] / src / xhr.js
index c715396..b9db717 100644 (file)
@@ -6,31 +6,30 @@ var rquery_xhr = /\?/,
        rnoContent = /^(?:GET|HEAD)$/,
        rts = /([?&])_=[^&]*/,
        rurl = /^(\w+:)?\/\/([^\/?#]+)/,
-
-       sliceFunc = Array.prototype.slice,
-
-       isFunction = jQuery.isFunction;
-
+       
+       sliceFunc = Array.prototype.slice;
+       
 // Creates a jQuery xhr object
 jQuery.xhr = function( _native ) {
 
        if ( _native ) {
                return jQuery.ajaxSettings.xhr();
        }
-
-       function reset(force) {
-
+       
+       function reset( force ) {
+               
                // We only need to reset if we went through the init phase
                // (with the exception of object creation)
                if ( force || internal ) {
-
+                       
                        // Reset callbacks lists
-                       callbacksLists = {
-                               success: createCBList(),
-                               error: createCBList(),
-                               complete: createCBList()
-                       };
-
+                       deferred = jQuery.Deferred();
+                       completeDeferred = jQuery._Deferred();
+                       
+                       xhr.success = xhr.then = deferred.then;
+                       xhr.error = xhr.fail = deferred.fail;
+                       xhr.complete = completeDeferred.then;
+                       
                        // Reset private variables
                        requestHeaders = {};
                        responseHeadersString = responseHeaders = internal = done = timeoutTimer = s = undefined;
@@ -86,14 +85,9 @@ jQuery.xhr = function( _native ) {
                        data = s.data = jQuery.param( data , s.traditional );
                }
 
-               // Apply option prefilters
-               for ( i = 0; i < prefilters.length; i++ ) {
-                       prefilters[i](s);
-               }
-
                // Get internal
-               internal = selectTransport( s );
-
+               internal = jQuery.xhr.prefilter( s ).transport( s );
+               
                // Re-actualize url & data
                url = s.url;
                data = s.data;
@@ -151,12 +145,12 @@ jQuery.xhr = function( _native ) {
                                requestHeaders[ i.toLowerCase() ] = headers[ i ];
                        }
                }
-
+               
                callbackContext = s.context || s;
                globalEventContext = s.context ? jQuery(s.context) : jQuery.event;
-
-               for ( i in callbacksLists ) {
-                       callbacksLists[i].bind(s[i]);
+               
+               for ( i in { success:1, error:1, complete:1 } ) {
+                       xhr[ i ]( s[ i ] );
                }
 
                // Watch for a new set of requests
@@ -227,112 +221,65 @@ jQuery.xhr = function( _native ) {
                                // Chain data conversions and determine the final value
                                // (if an exception is thrown in the process, it'll be notified as an error)
                                try {
-
-                                       function checkData(data) {
-                                               if ( data !== undefined ) {
-                                                       var testFunction = s.dataCheckers[srcDataType];
-                                                       if ( isFunction( testFunction ) ) {
-                                                               testFunction(data);
-                                                       }
+                                       
+                                       var i,
+                                               current,
+                                               prev,
+                                               checker,
+                                               conv1,
+                                               conv2,
+                                               oneConv,
+                                               convertion,
+                                               dataTypes = s.dataTypes,
+                                               dataConverters = s.dataConverters,
+                                               dataFilter = s.dataFilter,
+                                               responses = {
+                                                       "xml": "XML",
+                                                       "text": "Text"
+                                               };
+                                       
+                                       for( i = 0 ; i < dataTypes.length ; i++ ) {
+                                               
+                                               current = dataTypes[ i ];
+                                               
+                                               if ( responses[ current ] ) {
+                                                       xhr[ "response" + responses[ current ] ] = response;
+                                                       responses[ current ] = 0;
                                                }
-                                       }
-
-                                       function convertData (data) {
-                                               var conversionFunction = dataConverters[srcDataType+" => "+destDataType] ||
-                                                               dataConverters["* => "+destDataType],
-                                                       noFunction = ! isFunction( conversionFunction );
-                                               if ( noFunction ) {
-                                                       if ( srcDataType != "text" && destDataType != "text" ) {
-                                                               // We try to put text inbetween
-                                                               var first = dataConverters[srcDataType+" => text"] ||
-                                                                               dataConverters["* => text"],
-                                                                       second = dataConverters["text => "+destDataType] ||
-                                                                               dataConverters["* => "+destDataType],
-                                                                       areFunctions = isFunction( first ) && isFunction( second );
-                                                               if ( areFunctions ) {
-                                                                       conversionFunction = function (data) {
-                                                                               return second( first ( data ) );
-                                                                       };
+                                               
+                                               if ( i ) {
+                                                       
+                                                       if ( prev !== "*" && current !== "*" && prev !== current ) {
+                                                       
+                                                               oneConv = conv1 = 
+                                                                       dataConverters[ ( conversion = prev + " " + current ) ] ||
+                                                                       dataConverters[ "* " + current ];
+                                                               
+                                                               if ( oneConv !== true ) {
+                                                                       
+                                                                       if ( ! oneConv && prev !== "text" && current !== "text" ) {
+                                                                               conv1 = dataConverters[ prev + " text" ] || dataConverters[ "* text" ];
+                                                                               conv2 = dataConverters[ "text " + current ];
+                                                                       }
+                                                                       
+                                                                       if ( oneConv || conv1 && conv2 ) {
+                                                                               response = oneConv ? conv1( response ) : conv2( conv1( response ) );
+                                                                       } else {
+                                                                               throw "no " + conversion;
+                                                                       }
                                                                }
-                                                               noFunction = ! areFunctions;
-                                                       }
-                                                       if ( noFunction ) {
-                                                               jQuery.error( "no data converter between " + srcDataType + " and " + destDataType );
-                                                       }
-
-                                               }
-                                               return conversionFunction(data);
-                                       }
-
-                                       var dataTypes = s.dataTypes,
-                                               i,
-                                               length,
-                                               data = response,
-                                               dataConverters = s.dataConverters,
-                                               srcDataType,
-                                               destDataType,
-                                               responseTypes = s.xhrResponseFields;
-
-                                       for ( i = 0, length = dataTypes.length ; i < length ; i++ ) {
-
-                                               destDataType = dataTypes[i];
-
-                                               if ( !srcDataType ) { // First time
-
-                                                       // Copy type
-                                                       srcDataType = destDataType;
-                                                       // Check
-                                                       checkData(data);
-                                                       // Apply dataFilter
-                                                       if ( isFunction( s.dataFilter ) ) {
-                                                               data = s.dataFilter(data, s.dataType);
-                                                               // Recheck data
-                                                               checkData(data);
                                                        }
-
-                                               } else { // Subsequent times
-
-                                                       // handle auto
-                                                       // JULIAN: for reasons unknown to me === doesn't work here
-                                                       if (destDataType == "*") {
-
-                                                               destDataType = srcDataType;
-
-                                                       } else if ( srcDataType != destDataType ) {
-
-                                                               // Convert
-                                                               data = convertData(data);
-                                                               // Copy type & check
-                                                               srcDataType = destDataType;
-                                                               checkData(data);
-
-                                                       }
-
-                                               }
-
-                                               // Copy response into the xhr if it hasn't been already
-                                               var responseDataType,
-                                                       responseType = responseTypes[srcDataType];
-
-                                               if ( responseType ) {
-
-                                                       responseDataType = srcDataType;
-
-                                               } else {
-
-                                                       responseType = responseTypes[ responseDataType = "text" ];
-
+                                               } else if ( dataFilter ) {
+                                                       
+                                                       response = s.dataFilter( response );
+                                                       dataTypes = s.dataTypes;
                                                }
-
-                                               if ( responseType !== 1 ) {
-                                                       xhr[ "response" + responseType ] = data;
-                                                       responseTypes[ responseType ] = 1;
-                                               }
-
+                                               
+                                               prev = current;
                                        }
 
                                        // We have a real success
-                                       success = data;
+                                       success = response;
                                        isSuccess = 1;
 
                                } catch(e) {
@@ -355,10 +302,12 @@ jQuery.xhr = function( _native ) {
 
                // Keep local copies of vars in case callbacks re-use the xhr
                var _s = s,
-                       _callbacksLists = callbacksLists,
+                       _deferred = deferred,
+                       _completeDeferred = completeDeferred,
                        _callbackContext = callbackContext,
                        _globalEventContext = globalEventContext;
-
+                       
+                       
                // Set state if the xhr hasn't been re-used
                function _setState( value ) {
                        if ( xhr.readyState && s === _s ) {
@@ -374,19 +323,21 @@ jQuery.xhr = function( _native ) {
 
                // We're done
                _setState( 4 );
-
-               // Success
-               _callbacksLists.success.fire( isSuccess , _callbackContext , success, statusText, xhr);
-               if ( isSuccess && _s.global ) {
-                       _globalEventContext.trigger( "ajaxSuccess", [xhr, _s, success] );
+               
+               // Success/Error
+               if ( isSuccess ) {
+                       _deferred.fire( _callbackContext , [ success , statusText , xhr ] );
+               } else {
+                       _deferred.fireReject( _callbackContext , [ xhr , statusText , error ] );
                }
-               // Error
-               _callbacksLists.error.fire( ! isSuccess , _callbackContext , xhr, statusText, error);
-               if ( !isSuccess && _s.global ) {
-                       _globalEventContext.trigger( "ajaxError", [xhr, _s, error] );
+               
+               if ( _s.global ) {
+                       _globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , [ xhr , _s , isSuccess ? success : error ] );
                }
+               
                // Complete
-               _callbacksLists.complete.fire( 1 , _callbackContext, xhr, statusText);
+               _completeDeferred.fire( _callbackContext, [ xhr , statusText ] );
+               
                if ( _s.global ) {
                        _globalEventContext.trigger( "ajaxComplete", [xhr, _s] );
                        // Handle the global AJAX counter
@@ -406,7 +357,7 @@ jQuery.xhr = function( _native ) {
        // Ready state change
        function setState( value ) {
                xhr.readyState = value;
-               if ( isFunction( xhr.onreadystatechange ) ) {
+               if ( jQuery.isFunction( xhr.onreadystatechange ) ) {
                        xhr.onreadystatechange();
                }
        }
@@ -419,7 +370,8 @@ jQuery.xhr = function( _native ) {
                // Callback stuff
                callbackContext,
                globalEventContext,
-               callbacksLists,
+               deferred,
+               completeDeferred,
                // Headers (they are sent all at once)
                requestHeaders,
                // Response headers
@@ -596,230 +548,23 @@ jQuery.xhr = function( _native ) {
        // Init data (so that we can bind callbacks early
        reset(1);
 
-       // Install callbacks related methods
-       jQuery.each(callbacksLists, function(name) {
-               var list;
-               xhr[name] = function() {
-                       list = callbacksLists[name];
-                       if ( list ) {
-                               list.bind.apply(list, arguments );
-                       }
-                       return this;
-               };
-       });
-
        // Return the xhr emulation
        return xhr;
 };
 
-// Create a callback list
-function createCBList() {
-
-       var functors = [],
-               autoFire = 0,
-               fireArgs,
-               list = {
-
-                       fire: function( flag , context ) {
-
-                               // Save info for later bindings
-                               fireArgs = arguments;
-
-                               // Remove autoFire to keep bindings in order
-                               autoFire = 0;
-
-                               var args = sliceFunc.call( fireArgs , 2 );
-
-                               // Execute callbacks
-                               while ( flag && functors.length ) {
-                                       flag = functors.shift().apply( context , args ) !== false;
-                               }
-
-                               // Clean if asked to stop
-                               if ( ! flag ) {
-                                       clean();
-                               }
-
-                               // Set autoFire
-                               autoFire = 1;
-                       },
-
-                       bind: function() {
-
-                               var args = arguments,
-                                       i = 0,
-                                       length = args.length,
-                                       func;
-
-                               for ( ; i < length ; i++ ) {
-
-                                       func = args[ i ];
-
-                                       if ( jQuery.isArray(func) ) {
-
-                                               list.bind.apply( list , func );
-
-                                       } else if ( isFunction(func) ) {
-
-                                               // Add if not already in
-                                               if ( ! pos( func ) ) {
-                                                       functors.push( func );
-                                               }
-                                       }
-                               }
-
-                               if ( autoFire ) {
-                                       list.fire.apply( list , fireArgs );
-                               }
-                       },
-
-                       unbind: function() {
-
-                               var i = 0,
-                                       args = arguments,
-                                       length = args.length,
-                                       func,
-                                       position;
-
-                               if ( length ) {
-
-                                       for( ; i < length ; i++ ) {
-                                               func = args[i];
-                                               if ( jQuery.isArray(func) ) {
-                                                       list.unbind.apply(list,func);
-                                               } else if ( isFunction(func) ) {
-                                                       position = pos(func);
-                                                       if ( position ) {
-                                                               functors.splice(position-1,1);
-                                                       }
-                                               }
-                                       }
-
-                               } else {
-
-                                       functors = [];
-
-                               }
-
-                       }
-
-               };
-
-       // Get the index of the functor in the list (1-based)
-       function pos( func ) {
-               for (var i = 0, length = functors.length; i < length && functors[i] !== func; i++) {
-               }
-               return i < length ? ( i + 1 ) : 0;
-       }
-
-       // Clean the object
-       function clean() {
-               // Empty callbacks list
-               functors = [];
-               // Inhibit methods
-               for (var i in list) {
-                       list[i] = jQuery.noop;
-               }
-       }
-
-       return list;
-}
-
-jQuery.extend(jQuery.xhr, {
-
-       // Add new prefilter
-       prefilter: function (functor) {
-               if ( isFunction(functor) ) {
-                       jQuery.ajaxSettings.prefilters.push( functor );
-               }
-               return this;
-       },
-
-       // Bind a transport to one or more dataTypes
-       bindTransport: function () {
-
-               var args = arguments,
-                       i,
-                       start = 0,
-                       length = args.length,
-                       dataTypes = [ "*" ],
-                       functors = [],
-                       functor,
-                       first,
-                       append,
-                       list,
-                       transports = jQuery.ajaxSettings.transports;
-
-               if ( length ) {
-
-                       if ( ! isFunction( args[ 0 ] ) ) {
-
-                               dataTypes = args[ 0 ].toLowerCase().split(/\s+/);
-                               start = 1;
-
-                       }
-
-                       if ( dataTypes.length && start < length ) {
-
-                               for ( i = start; i < length; i++ ) {
-                                       functor = args[i];
-                                       if ( isFunction(functor) ) {
-                                               functors.push( functor );
-                                       }
-                               }
-
-                               if ( functors.length ) {
-
-                                       jQuery.each ( dataTypes, function( _ , dataType ) {
-
-                                               first = /^\+/.test( dataType );
-
-                                               if (first) {
-                                                       dataType = dataType.substr(1);
-                                               }
-
-                                               if ( dataType !== "" ) {
-
-                                                       append = Array.prototype[ first ? "unshift" : "push" ];
-
-                                                       list = transports[ dataType ];
-
-                                                       jQuery.each ( functors, function( _ , functor ) {
-
-                                                               if ( ! list ) {
-
-                                                                       list = transports[ dataType ] = [ functor ];
-
-                                                               } else {
-
-                                                                       append.call( list , functor );
-                                                               }
-                                                       } );
-                                               }
-
-                                       } );
-                               }
-                       }
-               }
-
-               return this;
-       }
-
-
-});
-
-// Select a transport given options
-function selectTransport( s ) {
+// Execute or select from functions in a given structure of options
+function xhr_selectOrExecute( structure , s ) {
 
        var dataTypes = s.dataTypes,
                transportDataType,
-               transportsList,
-               transport,
+               list,
+               selected,
                i,
                length,
                checked = {},
-               flag;
-
+               flag,
+               noSelect = structure !== "transports";
+               
        function initSearch( dataType ) {
 
                flag = transportDataType !== dataType && ! checked[ dataType ];
@@ -828,9 +573,9 @@ function selectTransport( s ) {
 
                        checked[ dataType ] = 1;
                        transportDataType = dataType;
-                       transportsList = s.transports[ dataType ];
+                       list = s[ structure ][ dataType ];
                        i = -1;
-                       length = transportsList ? transportsList.length : 0 ;
+                       length = list ? list.length : 0 ;
                }
 
                return flag;
@@ -838,30 +583,104 @@ function selectTransport( s ) {
 
        initSearch( dataTypes[ 0 ] );
 
-       for ( i = 0 ; ! transport && i <= length ; i++ ) {
-
+       for ( i = 0 ; ( noSelect || ! selected ) && i <= length ; i++ ) {
+               
                if ( i === length ) {
 
                        initSearch( "*" );
 
                } else {
 
-                       transport = transportsList[ i ]( s , determineDataType );
+                       selected = list[ i ]( s , determineDataType );
 
                        // If we got redirected to another dataType
                        // Search there (if not in progress or already tried)
-                       if ( typeof( transport ) === "string" &&
-                               initSearch( transport ) ) {
+                       if ( typeof( selected ) === "string" &&
+                               initSearch( selected ) ) {
 
-                               dataTypes.unshift( transport );
-                               transport = 0;
+                               dataTypes.unshift( selected );
+                               selected = 0;
                        }
                }
        }
 
-       return transport;
+       return noSelect ? jQuery.xhr : selected;
+}
+
+// Add an element to one of the xhr structures in ajaxSettings
+function xhr_addElement( structure , args ) {
+               
+       var i,
+               j,
+               start = 0,
+               length = args.length,
+               dataTypes = [ "*" ],
+               dLength = 1,
+               dataType,
+               functors = [],
+               first,
+               append,
+               list;
+               
+       if ( length ) {
+               
+               first = jQuery.type( args[ 0 ] );
+               
+               if ( first === "object" ) {
+                       return xhr_selectOrExecute( structure , args[ 0 ] );
+               }
+               
+               structure = jQuery.ajaxSettings[ structure ];
+               
+               if ( first !== "function" ) {
+                       
+                       dataTypes = args[ 0 ].toLowerCase().split(/\s+/);
+                       dLength = dataTypes.length;
+                       start = 1;
+                       
+               }
+               
+               if ( dLength && start < length ) {
+                       
+                       functors = sliceFunc.call( args , start );
+                       
+                       length -= start;
+                                       
+                       for( i = 0 ; i < dLength ; i++ ) {
+                               
+                               dataType = dataTypes[ i ];
+                               
+                               first = /^\+/.test( dataType );
+                               
+                               if (first) {
+                                       dataType = dataType.substr(1);
+                               }
+                               
+                               if ( dataType !== "" ) {
+                               
+                                       append = Array.prototype[ first ? "unshift" : "push" ];
+                                       
+                                       list = structure[ dataType ] = structure[ dataType ] || [];
+                       
+                                       for ( j = 0; j < length; j++ ) {
+                                               append.call( list , functors[ j ] );
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       return jQuery.xhr;
 }
 
+// Install prefilter & transport methods
+jQuery.each( [ "prefilter" , "transport" ] , function( _ , name ) {
+       _ = name + "s";
+       jQuery.xhr[ name ] = function() {
+               return xhr_addElement( _ , arguments );
+       };
+} );
+       
 // Utility function that handles dataType when response is received
 // (for those transports that can give text or xml responses)
 function determineDataType( s , ct , text , xml ) {