aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorjaubourg <j@ubourg.net>2011-09-12 17:37:41 +0200
committertimmywil <timmywillisn@gmail.com>2011-09-19 15:42:30 -0400
commit4092e3d2754e3847cd3159edb23184d4cfd4cf03 (patch)
tree79a0a58644939da906ec750281488beee862a41d /src
parent1878885fb7a09b65d95980a9b7dce2df2944e4f9 (diff)
downloadjquery-4092e3d2754e3847cd3159edb23184d4cfd4cf03.tar.gz
jquery-4092e3d2754e3847cd3159edb23184d4cfd4cf03.zip
$.Callbacks, $.Topic and notify/progress on $.Deferred.
Diffstat (limited to 'src')
-rw-r--r--src/ajax.js8
-rw-r--r--src/callbacks.js257
-rw-r--r--src/core.js6
-rw-r--r--src/deferred.js232
-rw-r--r--src/queue.js6
-rw-r--r--src/topic.js45
6 files changed, 398 insertions, 156 deletions
diff --git a/src/ajax.js b/src/ajax.js
index 623e9fe71..47aaf192f 100644
--- a/src/ajax.js
+++ b/src/ajax.js
@@ -43,7 +43,7 @@ var r20 = /%20/g,
// Document location segments
ajaxLocParts,
-
+
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
allTypes = ["*/"] + ["*"];
@@ -403,7 +403,7 @@ jQuery.extend({
jQuery( callbackContext ) : jQuery.event,
// Deferreds
deferred = jQuery.Deferred(),
- completeDeferred = jQuery._Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
// Status-dependent callbacks
statusCode = s.statusCode || {},
// ifModified key
@@ -582,7 +582,7 @@ jQuery.extend({
}
// Complete
- completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
@@ -597,7 +597,7 @@ jQuery.extend({
deferred.promise( jqXHR );
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
- jqXHR.complete = completeDeferred.done;
+ jqXHR.complete = completeDeferred.add;
// Status-dependent callbacks
jqXHR.statusCode = function( map ) {
diff --git a/src/callbacks.js b/src/callbacks.js
new file mode 100644
index 000000000..a1caf91f2
--- /dev/null
+++ b/src/callbacks.js
@@ -0,0 +1,257 @@
+(function( jQuery ) {
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * filter: an optional function that will be applied to each added callbacks,
+ * what filter returns will then be added provided it is not falsy.
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * queue: only first callback in the list is called each time the list is fired
+ * (cannot be used in conjunction with memory)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * relocate: like "unique" but will relocate the callback at the end of the list
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ * addAfterFire: if callbacks are added while firing, then they are not executed until after
+ * the next call to fire/fireWith
+ *
+ */
+jQuery.Callbacks = function( flags, filter ) {
+
+ // flags are optional
+ if ( typeof flags !== "string" ) {
+ filter = flags;
+ flags = undefined;
+ }
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // If we have to relocate, we remove the callback
+ // if it already exists
+ if ( flags.relocate ) {
+ self.remove( elem );
+ // Skip if we're in unique mode and callback is already in
+ } else if ( flags.unique && self.has( elem ) ) {
+ continue;
+ }
+ // Get the filtered function if needs be
+ actual = filter ? filter( elem ) : elem;
+ if ( actual ) {
+ list.push( [ elem, actual ] );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ][ 1 ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ } else if ( flags.queue ) {
+ list.splice( firingIndex, 1 );
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ if ( !flags.addAfterFire ) {
+ firingLength = list.length;
+ }
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ][ 0 ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique || flags.relocate ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ][ 0 ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!memory;
+ }
+ };
+
+ return self;
+};
+
+})( jQuery );
diff --git a/src/core.js b/src/core.js
index 18cf10235..8d9a94f63 100644
--- a/src/core.js
+++ b/src/core.js
@@ -258,7 +258,7 @@ jQuery.fn = jQuery.prototype = {
jQuery.bindReady();
// Add the callback
- readyList.done( fn );
+ readyList.add( fn );
return this;
},
@@ -413,7 +413,7 @@ jQuery.extend({
}
// If there are functions bound, to execute
- readyList.resolveWith( document, [ jQuery ] );
+ readyList.fireWith( document, [ jQuery ] );
// Trigger any bound ready events
if ( jQuery.fn.trigger ) {
@@ -427,7 +427,7 @@ jQuery.extend({
return;
}
- readyList = jQuery._Deferred();
+ readyList = jQuery.Callbacks( "once memory" );
// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
diff --git a/src/deferred.js b/src/deferred.js
index e543f1518..57014af3d 100644
--- a/src/deferred.js
+++ b/src/deferred.js
@@ -1,187 +1,127 @@
(function( jQuery ) {
var // Promise methods
- promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
+ promiseMethods = "done removeDone fail removeFail progress removeProgress isResolved isRejected promise then always pipe".split( " " ),
// Static reference to slice
sliceDeferred = [].slice;
jQuery.extend({
- // 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 = {
- // done( f1, f2, ...)
- done: 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.done.apply( deferred, elem );
- } else if ( type === "function" ) {
- callbacks.push( elem );
- }
- }
- if ( _fired ) {
- deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
- }
- }
- return this;
- },
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ promise,
+ deferred = {
+ // Copy existing methods from lists
+ done: doneList.add,
+ removeDone: doneList.remove,
+ fail: failList.add,
+ removeFail: failList.remove,
+ progress: progressList.add,
+ removeProgress: progressList.remove,
+ resolve: doneList.fire,
+ resolveWith: doneList.fireWith,
+ reject: failList.fire,
+ rejectWith: failList.fireWith,
+ notify: progressList.fire,
+ notifyWith: progressList.fireWith,
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
- // resolve with given context and args
- resolveWith: function( context, args ) {
- if ( !cancelled && !fired && !firing ) {
- // make sure args are available (#8421)
- args = args || [];
- firing = 1;
- try {
- while( callbacks[ 0 ] ) {
- callbacks.shift().apply( context, args );
- }
- }
- finally {
- fired = [ context, args ];
- firing = 0;
- }
- }
+ // Create Deferred-specific methods
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
return this;
},
-
- // resolve with this as context and given arguments
- resolve: function() {
- deferred.resolveWith( this, arguments );
- return this;
+ always: function() {
+ return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
},
-
- // Has this deferred been resolved?
- isResolved: function() {
- return !!( firing || fired );
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
},
-
- // Cancel
- cancel: function() {
- cancelled = 1;
- callbacks = [];
- return this;
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ if ( promise ) {
+ return promise;
+ }
+ promise = obj = {};
+ }
+ var i = promiseMethods.length;
+ while( i-- ) {
+ obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
+ }
+ return obj;
}
};
- return deferred;
- },
+ // Handle lists exclusiveness
+ deferred.done( failList.disable, progressList.lock )
+ .fail( doneList.disable, progressList.lock );
- // Full fledged deferred (two callbacks list)
- Deferred: function( func ) {
- var deferred = jQuery._Deferred(),
- failDeferred = jQuery._Deferred(),
- promise;
- // Add errorDeferred methods, then and promise
- jQuery.extend( deferred, {
- then: function( doneCallbacks, failCallbacks ) {
- deferred.done( doneCallbacks ).fail( failCallbacks );
- return this;
- },
- always: function() {
- return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
- },
- fail: failDeferred.done,
- rejectWith: failDeferred.resolveWith,
- reject: failDeferred.resolve,
- isRejected: failDeferred.isResolved,
- pipe: function( fnDone, fnFail ) {
- return jQuery.Deferred(function( newDefer ) {
- jQuery.each( {
- done: [ fnDone, "resolve" ],
- fail: [ fnFail, "reject" ]
- }, function( handler, data ) {
- var fn = data[ 0 ],
- action = data[ 1 ],
- returned;
- if ( jQuery.isFunction( fn ) ) {
- deferred[ handler ](function() {
- returned = fn.apply( this, arguments );
- if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise().then( newDefer.resolve, newDefer.reject );
- } else {
- newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
- }
- });
- } else {
- deferred[ handler ]( newDefer[ action ] );
- }
- });
- }).promise();
- },
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {
- if ( obj == null ) {
- if ( promise ) {
- return promise;
- }
- promise = obj = {};
- }
- var i = promiseMethods.length;
- while( i-- ) {
- obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
- }
- return obj;
- }
- });
- // Make sure only one callback list will be used
- deferred.done( failDeferred.cancel ).fail( deferred.cancel );
- // Unexpose cancel
- delete deferred.cancel;
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
+
+ // All done!
return deferred;
},
// Deferred helper
when: function( firstParam ) {
- var args = arguments,
+ var args = sliceDeferred.call( arguments, 0 ),
i = 0,
length = args.length,
+ pValues = new Array( length ),
count = length,
+ pCount = length,
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
- jQuery.Deferred();
+ jQuery.Deferred(),
+ promise = deferred.promise();
function resolveFunc( i ) {
return function( value ) {
args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
if ( !( --count ) ) {
- // Strange bug in FF4:
- // Values changed onto the arguments object sometimes end up as undefined values
- // outside the $.when method. Cloning the object into a fresh array solves the issue
- deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+ deferred.resolveWith( deferred, args );
}
};
}
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
if ( length > 1 ) {
for( ; i < length; i++ ) {
- if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
- args[ i ].promise().then( resolveFunc(i), deferred.reject );
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
} else {
--count;
}
@@ -192,8 +132,8 @@ jQuery.extend({
} else if ( deferred !== firstParam ) {
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
- return deferred.promise();
+ return promise;
}
});
-})( jQuery ); \ No newline at end of file
+})( jQuery );
diff --git a/src/queue.js b/src/queue.js
index cd6aeb26b..0c678064e 100644
--- a/src/queue.js
+++ b/src/queue.js
@@ -14,7 +14,7 @@ function handleQueueMarkDefer( elem, type, src ) {
if ( !jQuery._data( elem, queueDataKey ) &&
!jQuery._data( elem, markDataKey ) ) {
jQuery.removeData( elem, deferDataKey, true );
- defer.resolve();
+ defer.fire();
}
}, 0 );
}
@@ -160,9 +160,9 @@ jQuery.fn.extend({
if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
- jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
count++;
- tmp.done( resolve );
+ tmp.add( resolve );
}
}
resolve();
diff --git a/src/topic.js b/src/topic.js
new file mode 100644
index 000000000..c856db8c2
--- /dev/null
+++ b/src/topic.js
@@ -0,0 +1,45 @@
+(function( jQuery ) {
+
+ var topics = {},
+ sliceTopic = [].slice;
+
+ jQuery.Topic = function( id ) {
+ var callbacks,
+ method,
+ topic = id && topics[ id ];
+ if ( !topic ) {
+ callbacks = jQuery.Callbacks();
+ topic = {
+ publish: callbacks.fire,
+ subscribe: callbacks.add,
+ unsubscribe: callbacks.remove
+ };
+ if ( id ) {
+ topics[ id ] = topic;
+ }
+ }
+ return topic;
+ };
+
+ jQuery.extend({
+ subscribe: function( id ) {
+ var topic = jQuery.Topic( id ),
+ args = sliceTopic.call( arguments, 1 );
+ topic.subscribe.apply( topic, args );
+ return {
+ topic: topic,
+ args: args
+ };
+ },
+ unsubscribe: function( id ) {
+ var topic = id && id.topic || jQuery.Topic( id );
+ topic.unsubscribe.apply( topic, id && id.args ||
+ sliceTopic.call( arguments, 1 ) );
+ },
+ publish: function( id ) {
+ var topic = jQuery.Topic( id );
+ topic.publish.apply( topic, sliceTopic.call( arguments, 1 ) );
+ }
+ });
+
+})( jQuery );