From: jaubourg Date: Tue, 28 Dec 2010 01:02:31 +0000 (+0100) Subject: Merge branch 'master' of github.com:jquery/jquery into deferred X-Git-Url: http://git.asbjorn.biz/?a=commitdiff_plain;ds=inline;h=97b32d78307273f8bd2e5147bf5c0f0a45607a95;hp=-c;p=jquery.git Merge branch 'master' of github.com:jquery/jquery into deferred --- 97b32d78307273f8bd2e5147bf5c0f0a45607a95 diff --combined src/ajax.js index 33c3645,18ea203..6cb2a62 --- a/src/ajax.js +++ b/src/ajax.js @@@ -1,20 -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; @@@ -121,9 -113,8 +121,8 @@@ jQuery.each( "ajaxStart ajaxStop ajaxCo }; }); - jQuery.extend({ - - get: function( url, data, callback, type ) { + jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { // shift arguments if data argument was omited if ( jQuery.isFunction( data ) ) { type = type || callback; @@@ -132,13 -123,16 +131,16 @@@ } return jQuery.ajax({ - type: "GET", + type: method, url: url, data: data, success: callback, dataType: type }); - }, + }; + }); + + jQuery.extend({ getScript: function( url, callback ) { return jQuery.get(url, null, callback, "script"); @@@ -148,23 -142,6 +150,6 @@@ return jQuery.get(url, data, callback, "json"); }, - post: function( url, data, callback, type ) { - // shift arguments if data argument was omited - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = {}; - } - - return jQuery.ajax({ - type: "POST", - url: url, - data: data, - success: callback, - dataType: type - }); - }, - ajaxSetup: function( settings ) { jQuery.extend( jQuery.ajaxSettings, settings ); }, @@@ -189,7 -166,12 +174,7 @@@ xhr: function() { return new window.XMLHttpRequest(); }, - xhrResponseFields: { - xml: "XML", - text: "Text", - json: "JSON" - }, - + accepts: { xml: "application/xml, text/xml", html: "text/html", @@@ -197,450 -179,97 +182,450 @@@ json: "application/json, text/javascript", "*": "*/*" }, - + autoDataType: { 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 + dataConverters: { + + // 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, + 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 ) { + + prev = dataTypes[ i - 1 ]; + + 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; + } + } + + // 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; } } - }, + + // Attach deferreds + jXHR.success = jXHR.then = deferred.then; + jXHR.error = jXHR.fail = deferred.fail; + jXHR.complete = completeDeferred.then; - // Main method - ajax: function( url , s ) { + // Remove hash character (#7531: and string promotion) + s.url = ( "" + s.url ).replace( rhash , "" ); - if ( arguments.length === 1 ) { - s = url; - url = s ? s.url : undefined; + // 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 @@@ -730,172 -359,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 --combined src/core.js index 3763cd0,e75c86f..eb0c1c8 --- a/src/core.js +++ b/src/core.js @@@ -60,8 -60,8 +60,8 @@@ var jQuery = function( selector, contex // 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,10 -75,7 +75,10 @@@ indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs - class2type = {}; + class2type = {}, + + // Marker for deferred + deferredMarker = []; jQuery.fn = jQuery.prototype = { init: function( selector, context ) { @@@ -256,12 -253,22 +256,12 @@@ 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 ) { @@@ -408,11 -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" ); } } }, @@@ -546,28 -565,6 +546,28 @@@ 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() {}, @@@ -803,150 -800,6 +803,150 @@@ 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; + } + }; + + // Add the deferred marker + deferred.then._ = deferredMarker; + + 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 + + } ); + + // Remove cancel related + delete deferred.cancel; + delete deferred.isCancelled; + + // 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; + }, + + // Check if an object is a deferred + isDeferred: function( object ) { + return !!( object && object.then && object.then._ === deferredMarker ); + }, + + // Deferred helper + when: function( object ) { + object = jQuery.isDeferred( object ) ? + object : + jQuery.Deferred().resolve( object ); + return object; + }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser @@@ -965,11 -818,6 +965,11 @@@ browser: {} }); +// Create readyList deferred +// also force $.fn.ready to be recognized as a defer +readyList = jQuery._Deferred(); +jQuery.fn.ready._ = deferredMarker; + // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); @@@ -1038,6 -886,11 +1038,11 @@@ function doScrollCheck() jQuery.ready(); } + // Expose jQuery as an Asynchronous Module + if ( typeof define !== "undefined" ) { + define( "jquery", [], function () { return jQuery; } ); + } + // Expose jQuery to the global object return (window.jQuery = window.$ = jQuery); diff --combined test/unit/core.js index eb88c4a,5c80569..e0938b6 --- a/test/unit/core.js +++ b/test/unit/core.js @@@ -12,7 -12,9 +12,9 @@@ test("Basic requirements", function() }); test("jQuery()", function() { - expect(23); + expect(24); + + strictEqual( commonJSDefined, jQuery, "CommonJS registered (Bug #7102)" ); // Basic constructor's behavior @@@ -151,7 -153,7 +153,7 @@@ test("selector state", function() test = jQuery("#main").eq(0); equals( test.selector, "#main.slice(0,1)", "#main eq Selector" ); equals( test.context, document, "#main eq Context" ); - + var d = "
"; equals( jQuery(d).appendTo(jQuery(d)).selector, @@@ -253,38 -255,38 +255,38 @@@ test("isPlainObject", function() // The use case that we want to match ok(jQuery.isPlainObject({}), "{}"); - + // Not objects shouldn't be matched ok(!jQuery.isPlainObject(""), "string"); ok(!jQuery.isPlainObject(0) && !jQuery.isPlainObject(1), "number"); ok(!jQuery.isPlainObject(true) && !jQuery.isPlainObject(false), "boolean"); ok(!jQuery.isPlainObject(null), "null"); ok(!jQuery.isPlainObject(undefined), "undefined"); - + // Arrays shouldn't be matched ok(!jQuery.isPlainObject([]), "array"); - + // Instantiated objects shouldn't be matched ok(!jQuery.isPlainObject(new Date), "new Date"); - + var fn = function(){}; - + // Functions shouldn't be matched ok(!jQuery.isPlainObject(fn), "fn"); - + // Again, instantiated objects shouldn't be matched ok(!jQuery.isPlainObject(new fn), "new fn (no methods)"); - + // Makes the function a little more realistic // (and harder to detect, incidentally) fn.prototype = {someMethod: function(){}}; - + // Again, instantiated objects shouldn't be matched ok(!jQuery.isPlainObject(new fn), "new fn"); // DOM Element ok(!jQuery.isPlainObject(document.createElement("div")), "DOM Element"); - + // Window ok(!jQuery.isPlainObject(window), "window"); @@@ -298,7 -300,7 +300,7 @@@ document.body.removeChild( iframe ); start(); }; - + var doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); doc.write(""); @@@ -659,7 -661,7 +661,7 @@@ test("jQuery.merge()", function() // Fixed at [5998], #3641 same( parse([-2,-1], [0,1,2]), [-2,-1,0,1,2], "Second array including a zero (falsy)"); - + // After fixing #5527 same( parse([], [null, undefined]), [null, undefined], "Second array including null and undefined values"); same( parse({length:0}, [1,2]), {length:2, 0:1, 1:2}, "First array like"); @@@ -694,7 -696,7 +696,7 @@@ test("jQuery.extend(Object, Object)", f equals( deep1.foo2, document, "Make sure that a deep clone was not attempted on the document" ); ok( jQuery.extend(true, {}, nestedarray).arr !== arr, "Deep extend of object must clone child array" ); - + // #5991 ok( jQuery.isArray( jQuery.extend(true, { arr: {} }, nestedarray).arr ), "Cloned array heve to be an Array" ); ok( jQuery.isPlainObject( jQuery.extend(true, { arr: arr }, { arr: {} }).arr ), "Cloned object heve to be an plain object" ); @@@ -715,13 -717,13 +717,13 @@@ empty = {}; jQuery.extend(true, empty, optionsWithCustomObject); ok( empty.foo && empty.foo.date === customObject, "Custom objects copy correctly (no methods)" ); - + // Makes the class a little more realistic myKlass.prototype = { someMethod: function(){} }; empty = {}; jQuery.extend(true, empty, optionsWithCustomObject); ok( empty.foo && empty.foo.date === customObject, "Custom objects copy correctly" ); - + var ret = jQuery.extend(true, { foo: 4 }, { foo: new Number(5) } ); ok( ret.foo == 5, "Wrapped numbers copy correctly" ); @@@ -849,10 -851,10 +851,10 @@@ test("jQuery.makeArray", function() test("jQuery.isEmptyObject", function(){ expect(2); - + equals(true, jQuery.isEmptyObject({}), "isEmptyObject on empty object literal" ); equals(false, jQuery.isEmptyObject({a:1}), "isEmptyObject on non-empty object literal" ); - + // What about this ? // equals(true, jQuery.isEmptyObject(null), "isEmptyObject on null" ); }); @@@ -878,23 -880,23 +880,23 @@@ test("jQuery.proxy", function() test("jQuery.parseJSON", function(){ expect(8); - + equals( jQuery.parseJSON(), null, "Nothing in, null out." ); equals( jQuery.parseJSON( null ), null, "Nothing in, null out." ); equals( jQuery.parseJSON( "" ), null, "Nothing in, null out." ); - + same( jQuery.parseJSON("{}"), {}, "Plain object parsing." ); same( jQuery.parseJSON('{"test":1}'), {"test":1}, "Plain object parsing." ); same( jQuery.parseJSON('\n{"test":1}'), {"test":1}, "Make sure leading whitespaces are handled." ); - + try { jQuery.parseJSON("{a:1}"); ok( false, "Test malformed JSON string." ); } catch( e ) { ok( true, "Test malformed JSON string." ); } - + try { jQuery.parseJSON("{'a':1}"); ok( false, "Test malformed JSON string." ); @@@ -902,167 -904,3 +904,167 @@@ 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.isDeferred()", function() { + + expect( 10 ); + + var object1 = { then: function() { return this; } }, + object2 = { then: function() { return this; } }; + + object2.then._ = []; + + // The use case that we want to match + ok(jQuery.isDeferred(jQuery._Deferred()), "Simple deferred"); + ok(jQuery.isDeferred(jQuery.Deferred()), "Failable deferred"); + + // Some other objects + ok(!jQuery.isDeferred(object1), "Object with then & no marker"); + ok(!jQuery.isDeferred(object2), "Object with then & marker"); + + // Not objects shouldn't be matched + ok(!jQuery.isDeferred(""), "string"); + ok(!jQuery.isDeferred(0) && !jQuery.isDeferred(1), "number"); + ok(!jQuery.isDeferred(true) && !jQuery.isDeferred(false), "boolean"); + ok(!jQuery.isDeferred(null), "null"); + ok(!jQuery.isDeferred(undefined), "undefined"); + + object1 = {custom: jQuery._Deferred().then}; + + ok(!jQuery.isDeferred(object1) , "custom method name not found automagically"); +}); + +test("jQuery.when()", function() { + + expect( 2 ); + + var cache, i; + + for( i = 1 ; i < 3 ; 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" ); + }); + } +});