From 981d1e08eb00f4b5c29ad1f3977a45e30c93ad70 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sat, 25 Dec 2010 18:54:37 +0100 Subject: [PATCH] Removed re-usability from jXHR object (no more open, send & onreadystatechange support). Streamlined the implementation and put it back into ajax.js (removed xhr.js in the process). Went back to a more simple & direct approach to options handling (keeping much room to further minification-related optimizations). Code is cleaner, smaller & faster. Removed & edited unit tests accordingly. All build files have had xhr.js removed. --- Makefile | 1 - Rakefile | 1 - build.xml | 1 - src/ajax.js | 586 ++++++++++++++++++++++++++++++- src/transports/jsonp.js | 2 +- src/transports/script.js | 2 +- src/transports/xhr.js | 2 +- src/xhr.js | 728 --------------------------------------- test/index.html | 1 - test/unit/ajax.js | 166 +-------- 10 files changed, 583 insertions(+), 907 deletions(-) delete mode 100644 src/xhr.js diff --git a/Makefile b/Makefile index 5792bb478..cf985549b 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,6 @@ BASE_FILES = ${SRC_DIR}/core.js\ ${SRC_DIR}/manipulation.js\ ${SRC_DIR}/css.js\ ${SRC_DIR}/ajax.js\ - ${SRC_DIR}/xhr.js\ ${SRC_DIR}/transports/jsonp.js\ ${SRC_DIR}/transports/script.js\ ${SRC_DIR}/transports/xhr.js\ diff --git a/Rakefile b/Rakefile index 52d789952..fd5fc1813 100644 --- a/Rakefile +++ b/Rakefile @@ -22,7 +22,6 @@ base_files = %w{ manipulation css ajax - xhr transports/jsonp transports/script transports/xhr diff --git a/build.xml b/build.xml index 6a28aa849..d4cd39d0d 100644 --- a/build.xml +++ b/build.xml @@ -63,7 +63,6 @@ - diff --git a/src/ajax.js b/src/ajax.js index 27e8a1f5e..347273d15 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,12 +1,20 @@ (function( jQuery ) { - -var rscript = /)<[^<]*)*<\/script>/gi, - rselectTextarea = /^(?:select|textarea)/i, - rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + +var r20 = /%20/g, rbracket = /\[\]$/, + rhash = /#.*$/, + rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL + rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rnoContent = /^(?:GET|HEAD)$/, rquery = /\?/, - r20 = /%20/g, + rscript = /)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rts = /([?&])_=[^&]*/, + rurl = /^(\w+:)?\/\/([^\/?#]+)/, + // Slice function + sliceFunc = Array.prototype.slice, + // Keep a copy of the old load method _load = jQuery.fn.load; @@ -217,15 +225,407 @@ jQuery.extend({ }, // Main method - ajax: function( url , s ) { - + // (s is used internally) + ajax: function( url , options , s ) { + + // Handle varargs if ( arguments.length === 1 ) { - s = url; - url = s ? s.url : undefined; + options = url; + url = options ? options.url : undefined; + } + + // Force options to be an object + options = options || {}; + + // Get the url if provided separately + options.url = url || options.url; + + // Create the final options object + s = jQuery.extend( true , {} , jQuery.ajaxSettings , options ); + + // We force the original context + // (plain objects used as context get extended) + s.context = options.context; + + var // jQuery lists + jQuery_lastModified = jQuery.lastModified, + jQuery_etag = jQuery.etag, + // Callbacks contexts + callbackContext = s.context || s, + globalEventContext = s.context ? jQuery( s.context ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery._Deferred(), + // Headers (they are sent all at once) + requestHeaders = {}, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // The jXHR state + state = 0, + // Loop variable + i, + // Fake xhr + jXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function(name,value) { + if ( state === 0 ) { + requestHeaders[ name.toLowerCase() ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + // (match is used internally) + getResponseHeader: function( key , match ) { + + if ( state !== 2 ) { + return null; + } + + if ( responseHeaders === undefined ) { + + responseHeaders = {}; + + if ( typeof responseHeadersString === "string" ) { + + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + } + return responseHeaders[ key.toLowerCase() ]; + }, + + // Cancel the request + abort: function( statusText ) { + if ( transport && state !== 2 ) { + transport.abort( statusText || "abort" ); + done( 0 , statusText ); + } + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status , statusText , response , headers) { + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Set readyState + jXHR.readyState = status ? 4 : 0; + + // Cache response headers + responseHeadersString = headers || ""; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout(timeoutTimer); + } + + var // Reference url + url = s.url, + // and ifModified status + ifModified = s.ifModified, + + // Is it a success? + isSuccess = 0, + // Stored success + success, + // Stored error + error = statusText; + + // If not timeout, force a jQuery-compliant status text + if ( statusText != "timeout" ) { + statusText = ( status >= 200 && status < 300 ) ? + "success" : + ( status === 304 ? "notmodified" : "error" ); + } + + // If successful, handle type chaining + if ( statusText === "success" || statusText === "notmodified" ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + var lastModified = jXHR.getResponseHeader("Last-Modified"), + etag = jXHR.getResponseHeader("Etag"); + + if (lastModified) { + jQuery_lastModified[ s.url ] = lastModified; + } + if (etag) { + jQuery_etag[ s.url ] = etag; + } + } + + if ( s.ifModified && statusText === "notmodified" ) { + + success = null; + isSuccess = 1; + + } else { + // Chain data conversions and determine the final value + // (if an exception is thrown in the process, it'll be notified as an error) + try { + + var i, + current, + prev, + checker, + conv1, + conv2, + oneConv, + convertion, + dataTypes = s.dataTypes, + dataConverters = s.dataConverters, + responses = { + "xml": "XML", + "text": "Text" + }; + + for( i = 0 ; i < dataTypes.length ; i++ ) { + + current = dataTypes[ i ]; + + if ( responses[ current ] ) { + jXHR[ "response" + responses[ current ] ] = response; + responses[ current ] = 0; + } + + 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; + } + } + } + } else if ( s.dataFilter ) { + + response = s.dataFilter( response ); + dataTypes = s.dataTypes; + } + + prev = current; + } + + // We have a real success + success = response; + isSuccess = 1; + + } catch(e) { + + statusText = "parsererror"; + error = "" + e; + + } + } + + } else { // if not success, mark it as an error + + error = error || statusText; + } + + // Set data for the fake xhr object + jXHR.status = status; + jXHR.statusText = statusText; + + // Success/Error + if ( isSuccess ) { + deferred.fire( callbackContext , [ success , statusText , jXHR ] ); + } else { + deferred.fireReject( callbackContext , [ jXHR , statusText , error ] ); + } + + if ( s.global ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , + [ jXHR , s , isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fire( callbackContext, [ jXHR , statusText ] ); + + if ( s.global ) { + globalEventContext.trigger( "ajaxComplete" , [ jXHR , s] ); + // Handle the global AJAX counter + if ( ! --jQuery.active ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + jXHR.success = jXHR.then = deferred.then; + jXHR.error = jXHR.fail = deferred.fail; + jXHR.complete = completeDeferred.then; + + // Remove hash character (#7531: and string promotion) + s.url = ( "" + s.url ).replace( rhash , "" ); + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = ! rnoContent.test( s.type ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( /\s+/ ); + + // Determine if a cross-domain request is in order + var parts = rurl.exec( s.url.toLowerCase() ), + loc = location; + s.crossDomain = !!( parts && ( parts[ 1 ] && parts[ 1 ] != loc.protocol || parts[ 2 ] != loc.host ) ); + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data != "string" ) { + s.data = jQuery.param( s.data , s.traditional ); } - return jQuery.xhr().open( s ? s.type : undefined , url ).send( undefined , s ); + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Get transport + transport = jQuery.ajax.prefilter( s ).transport( s ); + + // If no transport, we auto-abort + if ( ! transport ) { + + done( 0 , "transport not found" ); + jXHR = false; + + } else { + + // More options handling for requests with no content + if ( ! s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + } + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts , "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); + } + } + + // Set the correct header, if data is being sent + if ( ( s.data && s.hasContent ) || options.contentType ) { + requestHeaders[ "content-type" ] = s.contentType; + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery_lastModified[ s.url ] ) { + requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; + } + if ( jQuery_etag[ s.url ] ) { + requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; + } + } + + // Set the Accepts header for the server, depending on the dataType + requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ]; + + // Check for headers option + for ( i in s.headers ) { + requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { + + // Abort if not done already + done( 0 , "abort" ); + jXHR = false; + + } else { + + // Set state as sending + state = 1; + jXHR.readyState = 1; + + // Install callbacks on deferreds + for ( i in { success:1, error:1, complete:1 } ) { + jXHR[ i ]( s[ i ] ); + } + + // Send global event + if ( s.global ) { + globalEventContext.trigger( "ajaxSend" , [ jXHR , s ] ); + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout(function(){ + jXHR.abort( "timeout" ); + }, s.timeout); + } + + // Try to send + try { + transport.send(requestHeaders, done); + } catch (e) { + // Propagate exception as error if not done + if ( status === 1 ) { + done(0, "error", "" + e); + jXHR = false; + + // Simply rethrow otherwise + } else { + jQuery.error(e); + } + } + } + } + + return jXHR; }, // Serialize an array of form elements or a set of @@ -315,6 +715,172 @@ jQuery.extend({ }); +//Execute or select from functions in a given structure of options +function ajax_selectOrExecute( structure , s ) { + + var dataTypes = s.dataTypes, + transportDataType, + list, + selected, + i, + length, + checked = {}, + flag, + noSelect = structure !== "transports"; + + function initSearch( dataType ) { + + flag = transportDataType !== dataType && ! checked[ dataType ]; + + if ( flag ) { + + checked[ dataType ] = 1; + transportDataType = dataType; + list = s[ structure ][ dataType ]; + i = -1; + length = list ? list.length : 0 ; + } + + return flag; + } + + initSearch( dataTypes[ 0 ] ); + + for ( i = 0 ; ( noSelect || ! selected ) && i <= length ; i++ ) { + + if ( i === length ) { + + initSearch( "*" ); + + } else { + + selected = list[ i ]( s , determineDataType ); + + // If we got redirected to another dataType + // Search there (if not in progress or already tried) + if ( typeof( selected ) === "string" && + initSearch( selected ) ) { + + dataTypes.unshift( selected ); + selected = 0; + } + } + } + + return noSelect ? jQuery.ajax : selected; +} + +// Add an element to one of the structures in ajaxSettings +function ajax_addElement( structure , args ) { + + var i, + start = 0, + length = args.length, + dataTypes = [ "*" ], + dLength = 1, + dataType, + functors = [], + first, + append, + list; + + if ( length ) { + + first = jQuery.type( args[ 0 ] ); + + if ( first === "object" ) { + return ajax_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 ); + + 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 ] || []; + append.apply( list , functors ); + } + } + } + } + + return jQuery.ajax; +} + +// Install prefilter & transport methods +jQuery.each( [ "prefilter" , "transport" ] , function( _ , name ) { + _ = name + "s"; + jQuery.ajax[ name ] = function() { + return ajax_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 ) { + + var autoDataType = s.autoDataType, + type, + regexp, + dataTypes = s.dataTypes, + transportDataType = dataTypes[0], + response; + + // Auto (xml, json, script or text determined given headers) + if ( transportDataType === "*" ) { + + for ( type in autoDataType ) { + if ( ( regexp = autoDataType[ type ] ) && regexp.test( ct ) ) { + transportDataType = dataTypes[0] = type; + break; + } + } + } + + // xml and parsed as such + if ( transportDataType === "xml" && + xml && + xml.documentElement /* #4958 */ ) { + + response = xml; + + // Text response was provided + } else { + + response = text; + + // If it's not really text, defer to dataConverters + if ( transportDataType !== "text" ) { + dataTypes.unshift( "text" ); + } + + } + + return response; +} + /* * Create the request object; Microsoft failed to properly * implement the XMLHttpRequest in IE7 (can't request local files), diff --git a/src/transports/jsonp.js b/src/transports/jsonp.js index a685bb196..f16b394ba 100644 --- a/src/transports/jsonp.js +++ b/src/transports/jsonp.js @@ -14,7 +14,7 @@ jQuery.ajaxSettings.jsonpCallback = function() { // 2) sneakily ensure transportDataType is json // 3) ensure options jsonp is always provided so that jsonp requests are always // json request with the jsonp option set -jQuery.xhr.prefilter("json jsonp", function(s) { +jQuery.ajax.prefilter("json jsonp", function(s) { var transportDataType = s.dataTypes[ 0 ]; diff --git a/src/transports/script.js b/src/transports/script.js index 4f55dc5ad..ddfaf3682 100644 --- a/src/transports/script.js +++ b/src/transports/script.js @@ -17,7 +17,7 @@ jQuery.extend( true, jQuery.ajaxSettings , { } ); // Bind script tag hack transport -jQuery.xhr.transport("script", function(s) { +jQuery.ajax.transport("script", function(s) { // Handle cache special case if ( s.cache === undefined ) { diff --git a/src/transports/xhr.js b/src/transports/xhr.js index 978f6c259..5845087b8 100644 --- a/src/transports/xhr.js +++ b/src/transports/xhr.js @@ -10,7 +10,7 @@ var // Next fake timer id xhrUnloadAbortMarker = []; -jQuery.xhr.transport( function( s , determineDataType ) { +jQuery.ajax.transport( function( s , determineDataType ) { // Cross domain only allowed if supported through XMLHttpRequest if ( ! s.crossDomain || jQuery.support.cors ) { diff --git a/src/xhr.js b/src/xhr.js deleted file mode 100644 index b9db71771..000000000 --- a/src/xhr.js +++ /dev/null @@ -1,728 +0,0 @@ -(function( jQuery ) { - -var rquery_xhr = /\?/, - rhash = /#.*$/, - rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL - rnoContent = /^(?:GET|HEAD)$/, - rts = /([?&])_=[^&]*/, - rurl = /^(\w+:)?\/\/([^\/?#]+)/, - - sliceFunc = Array.prototype.slice; - -// Creates a jQuery xhr object -jQuery.xhr = function( _native ) { - - if ( _native ) { - return jQuery.ajaxSettings.xhr(); - } - - 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 - 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; - - // Reset state - xhr.readyState = 0; - sendFlag = 0; - - // Remove responseX fields - for ( var name in xhr ) { - if ( /^response/.test(name) ) { - delete xhr[name]; - } - } - } - } - - function init() { - - var // Options extraction - - // Remove hash character (#7531: first for string promotion) - url = s.url = ( "" + s.url ).replace( rhash , "" ), - - // Uppercase the type - type = s.type = s.type.toUpperCase(), - - // Determine if request has content - hasContent = s.hasContent = ! rnoContent.test( type ), - - // Extract dataTypes list - dataType = s.dataType, - dataTypes = s.dataTypes = dataType ? jQuery.trim(dataType).toLowerCase().split(/\s+/) : ["*"], - - // Determine if a cross-domain request is in order - parts = rurl.exec( url.toLowerCase() ), - loc = location, - crossDomain = s.crossDomain = !!( parts && ( parts[1] && parts[1] != loc.protocol || parts[2] != loc.host ) ), - - // Get other options locally - data = s.data, - originalContentType = s.contentType, - prefilters = s.prefilters, - accepts = s.accepts, - headers = s.headers, - - // Other Variables - transportDataType, - i; - - // Convert data if not already a string - if ( data && s.processData && typeof data != "string" ) { - data = s.data = jQuery.param( data , s.traditional ); - } - - // Get internal - internal = jQuery.xhr.prefilter( s ).transport( s ); - - // Re-actualize url & data - url = s.url; - data = s.data; - - // If internal was found - if ( internal ) { - - // Get transportDataType - transportDataType = dataTypes[0]; - - // More options handling for requests with no content - if ( ! hasContent ) { - - // If data is available, append data to url - if ( data ) { - url += (rquery_xhr.test(url) ? "&" : "?") + data; - } - - // Add anti-cache in url if needed - if ( s.cache === false ) { - - var ts = jQuery.now(), - // try replacing _= if it is there - ret = url.replace(rts, "$1_=" + ts ); - - // if nothing was replaced, add timestamp to the end - url = ret + ((ret == url) ? (rquery_xhr.test(url) ? "&" : "?") + "_=" + ts : ""); - } - - s.url = url; - } - - // Set the correct header, if data is being sent - if ( ( data && hasContent ) || originalContentType ) { - requestHeaders["content-type"] = s.contentType; - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery_lastModified[url] ) { - requestHeaders["if-modified-since"] = jQuery_lastModified[url]; - } - if ( jQuery_etag[url] ) { - requestHeaders["if-none-match"] = jQuery_etag[url]; - } - } - - // Set the Accepts header for the server, depending on the dataType - requestHeaders.accept = transportDataType && accepts[ transportDataType ] ? - accepts[ transportDataType ] + ( transportDataType !== "*" ? ", */*; q=0.01" : "" ) : - accepts[ "*" ]; - - // Check for headers option - for ( i in headers ) { - requestHeaders[ i.toLowerCase() ] = headers[ i ]; - } - } - - callbackContext = s.context || s; - globalEventContext = s.context ? jQuery(s.context) : jQuery.event; - - for ( i in { success:1, error:1, complete:1 } ) { - xhr[ i ]( s[ i ] ); - } - - // Watch for a new set of requests - if ( s.global && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - done = whenDone; - } - - function whenDone(status, statusText, response, headers) { - - // Called once - done = undefined; - - // Reset sendFlag - sendFlag = 0; - - // Cache response headers - responseHeadersString = headers || ""; - - // Clear timeout if it exists - if ( timeoutTimer ) { - clearTimeout(timeoutTimer); - } - - var // Reference url - url = s.url, - // and ifModified status - ifModified = s.ifModified, - - // Is it a success? - isSuccess = 0, - // Stored success - success, - // Stored error - error = statusText; - - // If not timeout, force a jQuery-compliant status text - if ( statusText != "timeout" ) { - statusText = ( status >= 200 && status < 300 ) ? - "success" : - ( status === 304 ? "notmodified" : "error" ); - } - - // If successful, handle type chaining - if ( statusText === "success" || statusText === "notmodified" ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( ifModified ) { - var lastModified = xhr.getResponseHeader("Last-Modified"), - etag = xhr.getResponseHeader("Etag"); - - if (lastModified) { - jQuery_lastModified[url] = lastModified; - } - if (etag) { - jQuery_etag[url] = etag; - } - } - - if ( ifModified && statusText === "notmodified" ) { - - success = null; - isSuccess = 1; - - } else { - // Chain data conversions and determine the final value - // (if an exception is thrown in the process, it'll be notified as an error) - try { - - 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; - } - - 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; - } - } - } - } else if ( dataFilter ) { - - response = s.dataFilter( response ); - dataTypes = s.dataTypes; - } - - prev = current; - } - - // We have a real success - success = response; - isSuccess = 1; - - } catch(e) { - - statusText = "parsererror"; - error = "" + e; - - } - } - - } else { // if not success, mark it as an error - - error = error || statusText; - - } - - // Set data for the fake xhr object - xhr.status = status; - xhr.statusText = statusText; - - // Keep local copies of vars in case callbacks re-use the xhr - var _s = s, - _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 ) { - setState( value ); - } - } - - // Really completed? - if ( status && s.async ) { - setState( 2 ); - _setState( 3 ); - } - - // We're done - _setState( 4 ); - - // Success/Error - if ( isSuccess ) { - _deferred.fire( _callbackContext , [ success , statusText , xhr ] ); - } else { - _deferred.fireReject( _callbackContext , [ xhr , statusText , error ] ); - } - - if ( _s.global ) { - _globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , [ xhr , _s , isSuccess ? success : error ] ); - } - - // Complete - _completeDeferred.fire( _callbackContext, [ xhr , statusText ] ); - - if ( _s.global ) { - _globalEventContext.trigger( "ajaxComplete", [xhr, _s] ); - // Handle the global AJAX counter - if ( ! --jQuery.active ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - // Ready state control - function checkState( expected , test ) { - if ( expected !== true && ( expected === false || test === false || xhr.readyState !== expected ) ) { - jQuery.error("INVALID_STATE_ERR"); - } - } - - // Ready state change - function setState( value ) { - xhr.readyState = value; - if ( jQuery.isFunction( xhr.onreadystatechange ) ) { - xhr.onreadystatechange(); - } - } - - var // jQuery lists - jQuery_lastModified = jQuery.lastModified, - jQuery_etag = jQuery.etag, - // Options object - s, - // Callback stuff - callbackContext, - globalEventContext, - deferred, - completeDeferred, - // Headers (they are sent all at once) - requestHeaders, - // Response headers - responseHeadersString, - responseHeaders, - // Done callback - done, - // transport - internal, - // timeout handle - timeoutTimer, - // The send flag - sendFlag, - // Fake xhr - xhr = { - // state - readyState: 0, - - // Callback - onreadystatechange: null, - - // Open - open: function(type, url, async, username, password) { - - xhr.abort(); - reset(); - - s = { - type: type, - url: url, - async: async, - username: username, - password: password - }; - - setState(1); - - return xhr; - }, - - // Send - send: function(data, moreOptions) { - - checkState(1 , !sendFlag); - - s.data = data; - - s = jQuery.extend( true, - {}, - jQuery.ajaxSettings, - s, - moreOptions || ( moreOptions === false ? { global: false } : {} ) ); - - if ( moreOptions ) { - // We force the original context - // (plain objects used as context get extended) - s.context = moreOptions.context; - } - - init(); - - // If not internal, abort - if ( ! internal ) { - done( 0 , "transport not found" ); - return false; - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend ) { - - var _s = s; - - if ( s.beforeSend.call(callbackContext, xhr, s) === false || ! xhr.readyState || _s !== s ) { - - // Abort if not done - if ( xhr.readyState && _s === s ) { - xhr.abort(); - } - - // Handle the global AJAX counter - if ( _s.global && ! --jQuery.active ) { - jQuery.event.trigger( "ajaxStop" ); - } - - return false; - } - } - - sendFlag = 1; - - // Send global event - if ( s.global ) { - globalEventContext.trigger("ajaxSend", [xhr, s]); - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = setTimeout(function(){ - xhr.abort("timeout"); - }, s.timeout); - } - - if ( s.async ) { - setState(1); - } - - try { - - internal.send(requestHeaders, done); - return xhr; - - } catch (e) { - - if ( done ) { - - done(0, "error", "" + e); - - } else { - - jQuery.error(e); - - } - } - - return false; - }, - - // Caches the header - setRequestHeader: function(name,value) { - checkState(1, !sendFlag); - requestHeaders[ name.toLowerCase() ] = value; - return xhr; - }, - - // Raw string - getAllResponseHeaders: function() { - return xhr.readyState <= 1 ? "" : responseHeadersString; - }, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - - if ( xhr.readyState <= 1 ) { - - return null; - - } - - if ( responseHeaders === undefined ) { - - responseHeaders = {}; - - if ( typeof responseHeadersString === "string" ) { - - var match; - - while( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - } - return responseHeaders[ key.toLowerCase() ]; - }, - - // Cancel the request - abort: function(statusText) { - if (internal) { - internal.abort( statusText || "abort" ); - } - xhr.readyState = 0; - } - }; - - // Init data (so that we can bind callbacks early - reset(1); - - // Return the xhr emulation - return xhr; -}; - -// Execute or select from functions in a given structure of options -function xhr_selectOrExecute( structure , s ) { - - var dataTypes = s.dataTypes, - transportDataType, - list, - selected, - i, - length, - checked = {}, - flag, - noSelect = structure !== "transports"; - - function initSearch( dataType ) { - - flag = transportDataType !== dataType && ! checked[ dataType ]; - - if ( flag ) { - - checked[ dataType ] = 1; - transportDataType = dataType; - list = s[ structure ][ dataType ]; - i = -1; - length = list ? list.length : 0 ; - } - - return flag; - } - - initSearch( dataTypes[ 0 ] ); - - for ( i = 0 ; ( noSelect || ! selected ) && i <= length ; i++ ) { - - if ( i === length ) { - - initSearch( "*" ); - - } else { - - selected = list[ i ]( s , determineDataType ); - - // If we got redirected to another dataType - // Search there (if not in progress or already tried) - if ( typeof( selected ) === "string" && - initSearch( selected ) ) { - - dataTypes.unshift( selected ); - selected = 0; - } - } - } - - 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 ) { - - var autoDataType = s.autoDataType, - type, - regexp, - dataTypes = s.dataTypes, - transportDataType = dataTypes[0], - response; - - // Auto (xml, json, script or text determined given headers) - if ( transportDataType === "*" ) { - - for ( type in autoDataType ) { - if ( ( regexp = autoDataType[ type ] ) && regexp.test( ct ) ) { - transportDataType = dataTypes[0] = type; - break; - } - } - } - - // xml and parsed as such - if ( transportDataType === "xml" && - xml && - xml.documentElement /* #4958 */ ) { - - response = xml; - - // Text response was provided - } else { - - response = text; - - // If it's not really text, defer to dataConverters - if ( transportDataType !== "text" ) { - dataTypes.unshift( "text" ); - } - - } - - return response; -} - -})( jQuery ); diff --git a/test/index.html b/test/index.html index 238b7d582..accd349f9 100644 --- a/test/index.html +++ b/test/index.html @@ -20,7 +20,6 @@ - diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 88d66bf47..1a1ec3321 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -73,8 +73,8 @@ test("jQuery.ajax() - success callbacks - (url, options) syntax", function() { test("jQuery.ajax() - success/error callbacks (remote)", function() { var supports = jQuery.support.cors; - - expect( supports ? 9 : 6 ); + + expect( supports ? 9 : 4 ); jQuery.ajaxSetup({ timeout: 0 }); @@ -102,8 +102,8 @@ test("jQuery.ajax() - success/error callbacks (remote)", function() { dataType: "text", beforeSend: function(){ ok(supports, "beforeSend"); }, success: function( val ){ ok(supports, "success"); ok(supports && val.length, "data received"); }, - error: function(_ , a , b ){ ok(!supports, "error"); }, - complete: function(){ ok(true, "complete"); } + error: function(_ , a , b ){ ok(false, "error"); }, + complete: function(){ ok(supports, "complete"); } }); }, 13); }); @@ -409,136 +409,6 @@ test("jQuery.ajax() - abort", function() { equals( xhr.readyState, 0, "XHR readyState indicates successful abortion" ); }); -test("jQuery.ajax() - readyState (success)", function() { - expect( 1 ); - - jQuery.ajaxSetup({ timeout: 0 }); - - stop(); - - var control = ""; - - setTimeout(function(){ - jQuery.ajax({ - url: url("data/name.html"), - beforeSend: function( xhr ) { - xhr.onreadystatechange = function() { - control += xhr.readyState; - } - }, - complete: function(){ - setTimeout( function() { - equals( control , "1234" , "onreadystatechange was properly called" ); - }, 13 ); - start(); - } - }); - }, 13); -}); - -test("jQuery.ajax() - readyState (abort)", function() { - expect( 2 ); - - jQuery.ajaxSetup({ timeout: 0 }); - - stop(); - - var control = ""; - - setTimeout(function(){ - - jQuery.ajaxSetup({ timeout: 500 }); - - jQuery.ajax({ - url: url("data/name.php?wait=5"), - beforeSend: function( xhr ) { - xhr.onreadystatechange = function() { - control += xhr.readyState; - } - }, - complete: function( xhr ){ - setTimeout( function() { - equals( control , "14" , "onreadystatechange was properly called" ); - equals( xhr.readyState, 0 , "readyState is 0" ); - }, 13 ); - start(); - } - }); - }, 13); -}); - -test("jQuery.xhr() - reuse", function() { - expect( 15 ); - - jQuery.ajaxSetup({ timeout: 0 }); - - stop(); - - var number = 0; - - setTimeout(function(){ - jQuery('#foo').ajaxStart(function(){ - ok( true, "ajaxStart" ); - }).ajaxStop(function(){ - ok( true, "ajaxStop" ); - start(); - }).ajaxSend(function(){ - number++; - ok( true, "ajaxSend (" + number +")" ); - }).ajaxComplete(function(){ - ok( true, "ajaxComplete (" + number +")" ); - }).ajaxError(function(){ - ok( false, "ajaxError (" + number +")" ); - }).ajaxSuccess(function(){ - ok( true, "ajaxSuccess (" + number +")" ); - }); - - jQuery.ajax({ - url: url("data/name.html"), - beforeSend: function(){ ok(true, "beforeSend (1)"); }, - success: function( _1 , _2 , xhr ){ - ok(true, "success (1)"); - xhr.complete(function() { - ok(true, "complete (1bis)"); - }); - xhr.open( "GET", url("data/name.html") ); - xhr.success( function(){ ok(true, "beforeSend (2)"); } ) - xhr.send( null, { - success: function(){ ok(true, "success (2)"); }, - error: function(){ ok(false, "error (2)"); }, - complete: function(){ ok(true, "complete (2)"); } - } ); - }, - error: function(){ ok(false, "error (1)"); }, - complete: function(){ ok(true, "complete (1)"); } - }); - }, 13); -}); - -test("jQuery.xhr() - early binding", function() { - expect( 2 ); - - jQuery.ajaxSetup({ timeout: 0 }); - - stop(); - - jQuery.xhr() - .success( function(){ ok(true, "success"); } ) - .error( function(){ ok(false, "error"); } ) - .complete( function(){ ok(true, "complete"); start(); } ) - .open( "GET", url("data/name.html") ) - .send(); -}); - -test("jQuery.xhr() - get native implementation", function() { - - var xhr = jQuery.xhr(true); - - ok( xhr.readyState !== undefined , "implements XMLHttpRequest" ); - ok( ! jQuery.isFunction( xhr.success ) , "is not jQuery's abstraction" ); - -}); - test("Ajax events with context", function() { expect(14); @@ -653,34 +523,6 @@ test("jQuery.ajax() - disabled globals", function() { }); }); -test("jQuery.xhr() - disabled globals through xhr.send(data , false)", function() { - expect( 2 ); - stop(); - - jQuery('#foo').ajaxStart(function(){ - ok( false, "ajaxStart" ); - }).ajaxStop(function(){ - ok( false, "ajaxStop" ); - }).ajaxSend(function(){ - ok( false, "ajaxSend" ); - }).ajaxComplete(function(){ - ok( false, "ajaxComplete" ); - }).ajaxError(function(){ - ok( false, "ajaxError" ); - }).ajaxSuccess(function(){ - ok( false, "ajaxSuccess" ); - }); - - jQuery.xhr() - .success(function(){ ok(true, "success"); }) - .error(function(){ ok(false, "error"); }) - .complete(function(){ - ok(true, "complete"); - setTimeout(function(){ start(); }, 13); - }) - .open("GET", url("data/name.html")).send(undefined, false); -}); - test("jQuery.ajax - xml: non-namespace elements inside namespaced elements", function() { expect(3); stop();