From: jaubourg Date: Thu, 30 Dec 2010 03:43:31 +0000 (+0100) Subject: Merge branch 'master' of github.com:jquery/jquery into deferred X-Git-Url: http://git.asbjorn.biz/?a=commitdiff_plain;h=64902e03450dc9164dbdb046a701008a249497c1;hp=dfa57073069c6b60f623635df65e02c0c0c9a582;p=jquery.git Merge branch 'master' of github.com:jquery/jquery into deferred --- diff --git a/Makefile b/Makefile index 935f69c..f69aa63 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 5ea143b..4d72a1c 100644 --- a/Rakefile +++ b/Rakefile @@ -9,7 +9,7 @@ test_dir = File.join( prefix, 'test' ) # setting DIST_DIR before calling rake dist_dir = ENV['DIST_DIR'] || File.join( prefix, 'dist' ) -base_files = %w{intro core support data queue attributes event selector traversing manipulation css ajax xhr transports/jsonp transports/script transports/xhr effects offset dimensions outro}.map { |js| File.join( src_dir, "#{js}.js" ) } +base_files = %w{intro core support data queue attributes event selector traversing manipulation css ajax transports/jsonp transports/script transports/xhr effects offset dimensions outro}.map { |js| File.join( src_dir, "#{js}.js" ) } # Sizzle, QUnit and jQuery files/dirs sizzle_dir = File.join( src_dir, "sizzle" ) diff --git a/build.xml b/build.xml index f6650f4..f2f805f 100644 --- a/build.xml +++ b/build.xml @@ -63,7 +63,6 @@ - diff --git a/src/ajax.js b/src/ajax.js index 18ea203..46e5a14 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; @@ -166,12 +174,7 @@ jQuery.extend({ xhr: function() { return new window.XMLHttpRequest(); }, - xhrResponseFields: { - xml: "XML", - text: "Text", - json: "JSON" - }, - + accepts: { xml: "application/xml, text/xml", html: "text/html", @@ -179,97 +182,456 @@ jQuery.extend({ json: "application/json, text/javascript", "*": "*/*" }, - - autoDataType: { + + contents: { xml: /xml/, html: /html/, json: /json/ }, - + // Prefilters // 1) They are useful to introduce custom dataTypes (see transport/jsonp for an example) // 2) These are called: // * BEFORE asking for a transport // * AFTER param serialization (s.data is a string if s.processData is true) - // 3) They MUST be order agnostic - prefilters: [], - + // 3) key is the dataType + // 4) the catchall symbol "*" can be used + // 5) execution will start with transport dataType and THEN continue down to "*" if needed + prefilters: {}, + // Transports bindings // 1) key is the dataType // 2) the catchall symbol "*" can be used // 3) selection will start with transport dataType and THEN go to "*" if needed - transports: { - }, + transports: {}, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + } + }, + + // Main method + // (s is used internally) + ajax: function( url , options , s ) { - // Checkers - // 1) key is dataType - // 2) they are called to control successful response - // 3) error throws is used as error data - dataCheckers: { - - // Check if data is a string - "text": function(data) { - if ( typeof data != "string" ) { - jQuery.error("typeerror"); + // Handle varargs + if ( arguments.length === 1 ) { + 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; } - }, - - // Check if xml has been properly parsed - "xml": function(data) { - var documentElement = data ? data.documentElement : data; - if ( ! documentElement || ! documentElement.nodeName ) { - jQuery.error("typeerror"); + }; + + // 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 ( documentElement.nodeName == "parsererror" ) { - jQuery.error("parsererror"); + + 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, + conv, + conv1, + conv2, + convertion, + dataTypes = s.dataTypes, + converters = s.converters, + 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 ) { + + prev = dataTypes[ i - 1 ]; + + if ( prev !== "*" && current !== "*" && prev !== current ) { + + conv = converters[ ( conversion = prev + " " + current ) ] || + converters[ "* " + current ]; + + conv1 = conv2 = 0; + + if ( ! conv && prev !== "text" && current !== "text" ) { + conv1 = converters[ prev + " text" ] || converters[ "* text" ]; + conv2 = converters[ "text " + current ]; + if ( conv1 === true ) { + conv = conv2; + } else if ( conv2 === true ) { + conv = conv1; + } + } + + if ( ! ( conv || conv1 && conv2 ) ) { + throw conversion; + } + + if ( conv !== true ) { + response = conv ? conv( response ) : conv2( conv1( response ) ); + } + } + } else if ( s.dataFilter ) { + + response = s.dataFilter( response ); + dataTypes = s.dataTypes; + } + } + + // 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; } - }, - - // List of data converters - // 1) key format is "source_type => destination_type" (spaces required) - // 2) the catchall symbol "*" can be used for source_type - dataConverters: { - - // Convert anything to text - "* => text": function(data) { - return "" + data; - }, + + // Set data for the fake xhr object + jXHR.status = status; + jXHR.statusText = statusText; - // Text to html (no transformation) - "text => html": function(data) { - return data; - }, + // Success/Error + if ( isSuccess ) { + deferred.fire( callbackContext , [ success , statusText , jXHR ] ); + } else { + deferred.fireReject( callbackContext , [ jXHR , statusText , error ] ); + } - // Evaluate text as a json expression - "text => json": jQuery.parseJSON, + if ( s.global ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , + [ jXHR , s , isSuccess ? success : error ] ); + } - // Parse text as xml - "text => xml": function(data) { - var xml, parser; - if ( window.DOMParser ) { // Standard - parser = new DOMParser(); - xml = parser.parseFromString(data,"text/xml"); - } else { // IE - xml = new ActiveXObject("Microsoft.XMLDOM"); - xml.async="false"; - xml.loadXML(data); + // 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" ); } - return xml; } } - }, - - // Main method - ajax: function( url , s ) { - if ( arguments.length === 1 ) { - s = url; - url = s ? s.url : undefined; + // Attach deferreds + deferred.promise( jXHR ); + jXHR.success = jXHR.then; + jXHR.error = jXHR.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 ); + } + + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); } - return jQuery.xhr().open( s ? s.type : undefined , url ).send( undefined , s ); + // 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 @@ -359,6 +721,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 contents = s.contents, + type, + regexp, + dataTypes = s.dataTypes, + transportDataType = dataTypes[0], + response; + + // Auto (xml, json, script or text determined given headers) + if ( transportDataType === "*" ) { + + for ( type in contents ) { + if ( ( regexp = contents[ 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 converters + 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/core.js b/src/core.js index e75c86f..07666d9 100644 --- a/src/core.js +++ b/src/core.js @@ -60,8 +60,8 @@ var jQuery = function( selector, context ) { // Has the ready events already been bound? readyBound = false, - // The functions to execute on DOM ready - readyList = [], + // The deferred used on DOM ready + readyList, // The ready event handler DOMContentLoaded, @@ -75,7 +75,10 @@ var jQuery = function( selector, context ) { indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs - class2type = {}; + class2type = {}, + + // Marker for deferred + promiseMarker = []; jQuery.fn = jQuery.prototype = { init: function( selector, context ) { @@ -253,22 +256,12 @@ jQuery.fn = jQuery.prototype = { return jQuery.each( this, callback, args ); }, - ready: function( fn ) { + ready: function() { // Attach the listeners jQuery.bindReady(); - - // If the DOM is already ready - if ( jQuery.isReady ) { - // Execute the function immediately - fn.call( document, jQuery ); - - // Otherwise, remember the function for later - } else if ( readyList ) { - // Add the function to the wait list - readyList.push( fn ); - } - - return this; + + // Change ready & apply + return ( jQuery.fn.ready = readyList.then ).apply( this , arguments ); }, eq: function( i ) { @@ -415,23 +408,11 @@ jQuery.extend({ } // If there are functions bound, to execute - if ( readyList ) { - // Execute all of them - var fn, - i = 0, - ready = readyList; - - // Reset the list of functions - readyList = null; - - while ( (fn = ready[ i++ ]) ) { - fn.call( document, jQuery ); - } - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); - } + readyList.fire( document , [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); } } }, @@ -565,6 +546,28 @@ jQuery.extend({ jQuery.error( "Invalid JSON: " + data ); } }, + + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + + tmp = xml.documentElement; + + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, noop: function() {}, @@ -800,6 +803,154 @@ jQuery.extend({ now: function() { return (new Date()).getTime(); }, + + // Create a simple deferred (one callbacks list) + _Deferred: function() { + + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // then( f1, f2, ...) + then: function () { + + if ( ! cancelled ) { + + var args = arguments, + i, + length, + elem, + type, + _fired; + + if ( fired ) { + _fired = fired; + fired = 0; + } + + for ( i = 0, length = args.length ; i < length ; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.then.apply( deferred , elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + + if ( _fired ) { + deferred.fire( _fired[ 0 ] , _fired[ 1 ] ); + } + } + + return this; + }, + + // resolve with given context and args + fire: function( context , args ) { + if ( ! cancelled && ! fired && ! firing ) { + + firing = 1; + + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context , args ); + } + } + finally { + fired = [ context , args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.fire( this , arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + }, + + // Has this deferred been cancelled? + isCancelled: function() { + return !!cancelled; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + // Typical success/error system + Deferred: function( func ) { + + var errorDeferred = jQuery._Deferred(), + deferred = jQuery._Deferred(), + successCancel = deferred.cancel; + + // Add errorDeferred methods and redefine cancel + jQuery.extend( deferred , { + + fail: errorDeferred.then, + fireReject: errorDeferred.fire, + reject: errorDeferred.resolve, + isRejected: errorDeferred.isResolved, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + obj = obj || {}; + for ( var i in { then:1 , fail:1 , isResolved:1 , isRejected:1 , promise:1 } ) { + obj[ i ] = deferred[ i ]; + } + return obj; + } + + } ); + + // Remove cancel related + delete deferred.cancel; + delete deferred.isCancelled; + + // Add promise marker + deferred.promise._ = promiseMarker; + + // Make sure only one callback list will be used + deferred.then( errorDeferred.cancel ).fail( successCancel ); + + // Call given func if any + if ( func ) { + func.call( deferred , deferred ); + } + + return deferred; + }, + + // Deferred helper + when: function( object ) { + object = object && object.promise && object.promise._ === promiseMarker ? + object : + jQuery.Deferred().resolve( object ); + return object.promise(); + }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser @@ -818,6 +969,9 @@ jQuery.extend({ browser: {} }); +// Create readyList deferred +readyList = jQuery._Deferred(); + // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); diff --git a/src/transports/jsonp.js b/src/transports/jsonp.js index d9e77f2..aa30b5d 100644 --- a/src/transports/jsonp.js +++ b/src/transports/jsonp.js @@ -11,12 +11,14 @@ jQuery.ajaxSettings.jsonpCallback = function() { // Normalize jsonp queries // 1) put callback parameter in url or data -// 2) ensure transportDataType is json +// 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( function(s) { +jQuery.ajax.prefilter("json jsonp", function(s) { - var transportDataType = s.dataTypes[0]; + var transportDataType = s.dataTypes[ 0 ]; + + s.dataTypes[ 0 ] = "json"; if ( s.jsonp || transportDataType === "jsonp" || @@ -34,14 +36,10 @@ jQuery.xhr.prefilter( function(s) { s.url = url; s.data = data; - - s.dataTypes[0] = "json"; } -}); - // Bind transport to json dataType -jQuery.xhr.bindTransport("json", function(s) { +}).transport("json", function(s) { if ( s.jsonp ) { @@ -72,18 +70,16 @@ jQuery.xhr.bindTransport("json", function(s) { }, s.complete ]; // Use data converter to retrieve json after script execution - s.dataConverters["script => json"] = function() { + s.converters["script json"] = function() { if ( ! responseContainer ) { - jQuery.error("Callback '" + jsonpCallback + "' was not called"); + jQuery.error( jsonpCallback + " was not called" ); } return responseContainer[ 0 ]; }; // Delegate to script transport return "script"; - } - }); })( jQuery ); diff --git a/src/transports/script.js b/src/transports/script.js index 7416a2d..d18f4d7 100644 --- a/src/transports/script.js +++ b/src/transports/script.js @@ -7,17 +7,17 @@ jQuery.extend( true, jQuery.ajaxSettings , { script: "text/javascript, application/javascript" }, - autoDataType: { + contents: { script: /javascript/ }, - dataConverters: { - "text => script": jQuery.globalEval + converters: { + "text script": jQuery.globalEval } } ); // Bind script tag hack transport -jQuery.xhr.bindTransport("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 783ee46..5845087 100644 --- a/src/transports/xhr.js +++ b/src/transports/xhr.js @@ -10,7 +10,7 @@ var // Next fake timer id xhrUnloadAbortMarker = []; -jQuery.xhr.bindTransport( 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 4896e6c..0000000 --- a/src/xhr.js +++ /dev/null @@ -1,909 +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, - - isFunction = jQuery.isFunction; - -// 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 - callbacksLists = { - success: createCBList(), - error: createCBList(), - complete: createCBList() - }; - - // 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 ); - } - - // Apply option prefilters - for ( i = 0; i < prefilters.length; i++ ) { - prefilters[i](s); - } - - // Get internal - internal = selectTransport( 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 callbacksLists ) { - callbacksLists[i].bind(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 { - - function checkData(data) { - if ( data !== undefined ) { - var testFunction = s.dataCheckers[srcDataType]; - if ( isFunction( testFunction ) ) { - testFunction(data); - } - } - } - - 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 ) ); - }; - } - 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" ]; - - } - - if ( responseType !== 1 ) { - xhr[ "response" + responseType ] = data; - responseTypes[ responseType ] = 1; - } - - } - - // We have a real success - success = data; - 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, - _callbacksLists = callbacksLists, - _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 - _callbacksLists.success.fire( isSuccess , _callbackContext , success, statusText, xhr); - if ( isSuccess && _s.global ) { - _globalEventContext.trigger( "ajaxSuccess", [xhr, _s, success] ); - } - // Error - _callbacksLists.error.fire( ! isSuccess , _callbackContext , xhr, statusText, error); - if ( !isSuccess && _s.global ) { - _globalEventContext.trigger( "ajaxError", [xhr, _s, error] ); - } - // Complete - _callbacksLists.complete.fire( 1 , _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 ( isFunction( xhr.onreadystatechange ) ) { - xhr.onreadystatechange(); - } - } - - var // jQuery lists - jQuery_lastModified = jQuery.lastModified, - jQuery_etag = jQuery.etag, - // Options object - s, - // Callback stuff - callbackContext, - globalEventContext, - callbacksLists, - // 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); - - // 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 ) { - - var dataTypes = s.dataTypes, - transportDataType, - transportsList, - transport, - i, - length, - checked = {}, - flag; - - function initSearch( dataType ) { - - flag = transportDataType !== dataType && ! checked[ dataType ]; - - if ( flag ) { - - checked[ dataType ] = 1; - transportDataType = dataType; - transportsList = s.transports[ dataType ]; - i = -1; - length = transportsList ? transportsList.length : 0 ; - } - - return flag; - } - - initSearch( dataTypes[ 0 ] ); - - for ( i = 0 ; ! transport && i <= length ; i++ ) { - - if ( i === length ) { - - initSearch( "*" ); - - } else { - - transport = transportsList[ 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 ) ) { - - dataTypes.unshift( transport ); - transport = 0; - } - } - } - - return transport; -} - -// 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 238b7d5..accd349 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 35c0302..d609c02 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -70,44 +70,6 @@ test("jQuery.ajax() - success callbacks - (url, options) syntax", function() { }, 13); }); -test("jQuery.ajax() - success/error callbacks (remote)", function() { - - var supports = jQuery.support.cors; - - expect( supports ? 9 : 6 ); - - jQuery.ajaxSetup({ timeout: 0 }); - - stop(); - - setTimeout(function(){ - jQuery('#foo').ajaxStart(function(){ - ok( true, "ajaxStart" ); - }).ajaxStop(function(){ - ok( true, "ajaxStop" ); - start(); - }).ajaxSend(function(){ - ok( supports , "ajaxSend" ); - }).ajaxComplete(function(){ - ok( true, "ajaxComplete" ); - }).ajaxError(function(){ - ok( ! supports, "ajaxError" ); - }).ajaxSuccess(function(){ - ok( supports, "ajaxSuccess" ); - }); - - jQuery.ajax({ - // JULIAN TODO: Get an url especially for jQuery - url: "http://rockstarapps.com/test.php", - 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"); } - }); - }, 13); -}); - test("jQuery.ajax() - success callbacks (late binding)", function() { expect( 8 ); @@ -409,136 +371,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 +485,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(); @@ -707,6 +511,10 @@ test("jQuery.ajax - xml: non-namespace elements inside namespaced elements (over equals( jQuery("jsconf", resp).length, 1, 'jsconf in responseXML' ); equals( jQuery("thing", resp).length, 2, 'things in responseXML' ); start(); + }, + error: function(_1,_2,error) { + ok( false, error ); + start(); } }); }); @@ -1482,12 +1290,12 @@ test("jQuery.ajax() - json by content-type disabled with options", function() { jQuery.ajax({ url: url("data/json.php"), data: { header: "json", json: "array" }, - autoDataType: { + contents: { json: false }, success: function( text ) { equals( typeof text , "string" , "json wasn't auto-determined" ); - var json = this.dataConverters["text => json"]( text ); + var json = jQuery.parseJSON( text ); ok( json.length >= 2, "Check length"); equals( json[0].name, 'John', 'Check JSON: first, name' ); equals( json[0].age, 21, 'Check JSON: first, age' ); @@ -1831,19 +1639,25 @@ test("jQuery ajax - failing cross-domain", function() { var i = 2; - jQuery.ajax({ - url: 'http://somewebsitethatdoesnotexist.com', + if ( jQuery.ajax({ + url: 'http://somewebsitethatdoesnotexist-67864863574657654.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }); + }) === false ) { + ok( true , "no transport" ); + if ( ! --i ) start(); + } - jQuery.ajax({ + if ( jQuery.ajax({ url: 'http://www.google.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }); + }) === false ) { + ok( true , "no transport" ); + if ( ! --i ) start(); + } }); diff --git a/test/unit/core.js b/test/unit/core.js index 5c80569..1cd41a6 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -904,3 +904,163 @@ test("jQuery.parseJSON", function(){ ok( true, "Test malformed JSON string." ); } }); + +test("jQuery._Deferred()", function() { + + expect( 10 ); + + var deferred, + object, + test; + + deferred = jQuery._Deferred(); + + test = false; + + deferred.then( function( value ) { + equals( value , "value" , "Test pre-resolve callback" ); + test = true; + } ); + + deferred.resolve( "value" ); + + ok( test , "Test pre-resolve callbacks called right away" ); + + test = false; + + deferred.then( function( value ) { + equals( value , "value" , "Test post-resolve callback" ); + test = true; + } ); + + ok( test , "Test post-resolve callbacks called right away" ); + + deferred.cancel(); + + test = true; + + deferred.then( function() { + ok( false , "Cancel was ignored" ); + test = false; + } ); + + ok( test , "Test cancel" ); + + deferred = jQuery._Deferred().resolve(); + + try { + deferred.then( function() { + throw "Error"; + } , function() { + ok( true , "Test deferred do not cancel on exception" ); + } ); + } catch( e ) { + strictEqual( e , "Error" , "Test deferred propagates exceptions"); + deferred.then(); + } + + test = ""; + deferred = jQuery._Deferred().then( function() { + + test += "A"; + + }, function() { + + test += "B"; + + } ).resolve(); + + strictEqual( test , "AB" , "Test multiple then parameters" ); + + test = ""; + + deferred.then( function() { + + deferred.then( function() { + + test += "C"; + + } ); + + test += "A"; + + }, function() { + + test += "B"; + } ); + + strictEqual( test , "ABC" , "Test then callbacks order" ); + + deferred = jQuery._Deferred(); + + deferred.fire( jQuery , [ document ] ).then( function( doc ) { + ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); + }); +}); + +test("jQuery.Deferred()", function() { + + expect( 4 ); + + jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument" ); + this.resolve( "done" ); + }).then( function( value ) { + strictEqual( value , "done" , "Passed function executed" ); + }); + + jQuery.Deferred().resolve().then( function() { + ok( true , "Success on resolve" ); + }).fail( function() { + ok( false , "Error on resolve" ); + }); + + jQuery.Deferred().reject().then( function() { + ok( false , "Success on reject" ); + }).fail( function() { + ok( true , "Error on reject" ); + }); +}); + +test("jQuery.when()", function() { + + expect( 14 ); + + var fakeDeferred = { then: function() { return this; } }; + + fakeDeferred.then._ = []; + + // Some other objects + jQuery.each( { + + "Object with then & no marker": { then: jQuery.noop }, + "Object with then & marker": fakeDeferred, + "string 1/2": "", + "string 2/2": "some string", + "number 1/2": 0, + "number 2/2": 1, + "boolean 1/2": true, + "boolean 2/2": false, + "null": null, + "undefined": undefined, + "custom method name not found automagically": {custom: jQuery._Deferred().then} + + } , function( message , value ) { + + notStrictEqual( jQuery.when( value ) , value , message ); + + } ); + + var cache, i; + + for( i = 1 ; i < 4 ; i++ ) { + jQuery.when( cache || jQuery.Deferred( function() { + this.resolve( i ); + }) ).then( function( value ) { + strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) ); + cache = value; + }).fail( function() { + ok( false , "Fail called" ); + }); + } +});