Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

$.Callbacks, $.Topic and notify/progress on $.Deferred.

  • Loading branch information...
commit 4092e3d2754e3847cd3159edb23184d4cfd4cf03 1 parent 1878885
Julian Aubourg authored September 12, 2011 timmywil committed September 19, 2011
1  .gitignore
... ...
@@ -1,5 +1,6 @@
1 1
 src/selector.js
2 2
 dist
  3
+.project
3 4
 .settings
4 5
 *~
5 6
 *.diff
2  Makefile
@@ -10,7 +10,9 @@ COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe
10 10
 POST_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/post-compile.js
11 11
 
12 12
 BASE_FILES = ${SRC_DIR}/core.js\
  13
+	${SRC_DIR}/callbacks.js\
13 14
 	${SRC_DIR}/deferred.js\
  15
+	${SRC_DIR}/topic.js\
14 16
 	${SRC_DIR}/support.js\
15 17
 	${SRC_DIR}/data.js\
16 18
 	${SRC_DIR}/queue.js\
8  src/ajax.js
@@ -43,7 +43,7 @@ var r20 = /%20/g,
43 43
 
44 44
 	// Document location segments
45 45
 	ajaxLocParts,
46  
-	
  46
+
47 47
 	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
48 48
 	allTypes = ["*/"] + ["*"];
49 49
 
@@ -403,7 +403,7 @@ jQuery.extend({
403 403
 						jQuery( callbackContext ) : jQuery.event,
404 404
 			// Deferreds
405 405
 			deferred = jQuery.Deferred(),
406  
-			completeDeferred = jQuery._Deferred(),
  406
+			completeDeferred = jQuery.Callbacks( "once memory" ),
407 407
 			// Status-dependent callbacks
408 408
 			statusCode = s.statusCode || {},
409 409
 			// ifModified key
@@ -582,7 +582,7 @@ jQuery.extend({
582 582
 			}
583 583
 
584 584
 			// Complete
585  
-			completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
  585
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
586 586
 
587 587
 			if ( fireGlobals ) {
588 588
 				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
@@ -597,7 +597,7 @@ jQuery.extend({
597 597
 		deferred.promise( jqXHR );
598 598
 		jqXHR.success = jqXHR.done;
599 599
 		jqXHR.error = jqXHR.fail;
600  
-		jqXHR.complete = completeDeferred.done;
  600
+		jqXHR.complete = completeDeferred.add;
601 601
 
602 602
 		// Status-dependent callbacks
603 603
 		jqXHR.statusCode = function( map ) {
257  src/callbacks.js
... ...
@@ -0,0 +1,257 @@
  1
+(function( jQuery ) {
  2
+
  3
+// String to Object flags format cache
  4
+var flagsCache = {};
  5
+
  6
+// Convert String-formatted flags into Object-formatted ones and store in cache
  7
+function createFlags( flags ) {
  8
+	var object = flagsCache[ flags ] = {},
  9
+		i, length;
  10
+	flags = flags.split( /\s+/ );
  11
+	for ( i = 0, length = flags.length; i < length; i++ ) {
  12
+		object[ flags[i] ] = true;
  13
+	}
  14
+	return object;
  15
+}
  16
+
  17
+/*
  18
+ * Create a callback list using the following parameters:
  19
+ *
  20
+ *	flags:	an optional list of space-separated flags that will change how
  21
+ *			the callback list behaves
  22
+ *
  23
+ *	filter:	an optional function that will be applied to each added callbacks,
  24
+ *			what filter returns will then be added provided it is not falsy.
  25
+ *
  26
+ * By default a callback list will act like an event callback list and can be
  27
+ * "fired" multiple times.
  28
+ *
  29
+ * Possible flags:
  30
+ *
  31
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
  32
+ *
  33
+ *	memory:			will keep track of previous values and will call any callback added
  34
+ *					after the list has been fired right away with the latest "memorized"
  35
+ *					values (like a Deferred)
  36
+ *
  37
+ *	queue:			only first callback in the list is called each time the list is fired
  38
+ *					(cannot be used in conjunction with memory)
  39
+ *
  40
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
  41
+ *
  42
+ *	relocate:		like "unique" but will relocate the callback at the end of the list
  43
+ *
  44
+ *	stopOnFalse:	interrupt callings when a callback returns false
  45
+ *
  46
+ *	addAfterFire:	if callbacks are added while firing, then they are not executed until after
  47
+ *					the next call to fire/fireWith
  48
+ *
  49
+ */
  50
+jQuery.Callbacks = function( flags, filter ) {
  51
+
  52
+	// flags are optional
  53
+	if ( typeof flags !== "string" ) {
  54
+		filter = flags;
  55
+		flags = undefined;
  56
+	}
  57
+
  58
+	// Convert flags from String-formatted to Object-formatted
  59
+	// (we check in cache first)
  60
+	flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
  61
+
  62
+	var // Actual callback list
  63
+		list = [],
  64
+		// Stack of fire calls for repeatable lists
  65
+		stack = [],
  66
+		// Last fire value (for non-forgettable lists)
  67
+		memory,
  68
+		// Flag to know if list is currently firing
  69
+		firing,
  70
+		// First callback to fire (used internally by add and fireWith)
  71
+		firingStart,
  72
+		// End of the loop when firing
  73
+		firingLength,
  74
+		// Index of currently firing callback (modified by remove if needed)
  75
+		firingIndex,
  76
+		// Add one or several callbacks to the list
  77
+		add = function( args ) {
  78
+			var i,
  79
+				length,
  80
+				elem,
  81
+				type,
  82
+				actual;
  83
+			for ( i = 0, length = args.length; i < length; i++ ) {
  84
+				elem = args[ i ];
  85
+				type = jQuery.type( elem );
  86
+				if ( type === "array" ) {
  87
+					// Inspect recursively
  88
+					add( elem );
  89
+				} else if ( type === "function" ) {
  90
+					// If we have to relocate, we remove the callback
  91
+					// if it already exists
  92
+					if ( flags.relocate ) {
  93
+						self.remove( elem );
  94
+					// Skip if we're in unique mode and callback is already in
  95
+					} else if ( flags.unique && self.has( elem ) ) {
  96
+						continue;
  97
+					}
  98
+					// Get the filtered function if needs be
  99
+					actual = filter ? filter( elem ) : elem;
  100
+					if ( actual ) {
  101
+						list.push( [ elem, actual ] );
  102
+					}
  103
+				}
  104
+			}
  105
+		},
  106
+		// Fire callbacks
  107
+		fire = function( context, args ) {
  108
+			args = args || [];
  109
+			memory = !flags.memory || [ context, args ];
  110
+			firing = true;
  111
+			firingIndex = firingStart || 0;
  112
+			firingStart = 0;
  113
+			firingLength = list.length;
  114
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
  115
+				if ( list[ firingIndex ][ 1 ].apply( context, args ) === false && flags.stopOnFalse ) {
  116
+					memory = true; // Mark as halted
  117
+					break;
  118
+				} else if ( flags.queue ) {
  119
+					list.splice( firingIndex, 1 );
  120
+					break;
  121
+				}
  122
+			}
  123
+			firing = false;
  124
+			if ( list ) {
  125
+				if ( !flags.once ) {
  126
+					if ( stack && stack.length ) {
  127
+						memory = stack.shift();
  128
+						self.fireWith( memory[ 0 ], memory[ 1 ] );
  129
+					}
  130
+				} else if ( memory === true ) {
  131
+					self.disable();
  132
+				} else {
  133
+					list = [];
  134
+				}
  135
+			}
  136
+		},
  137
+		// Actual Callbacks object
  138
+		self = {
  139
+			// Add a callback or a collection of callbacks to the list
  140
+			add: function() {
  141
+				if ( list ) {
  142
+					var length = list.length;
  143
+					add( arguments );
  144
+					// Do we need to add the callbacks to the
  145
+					// current firing batch?
  146
+					if ( firing ) {
  147
+						if ( !flags.addAfterFire ) {
  148
+							firingLength = list.length;
  149
+						}
  150
+					// With memory, if we're not firing then
  151
+					// we should call right away, unless previous
  152
+					// firing was halted (stopOnFalse)
  153
+					} else if ( memory && memory !== true ) {
  154
+						firingStart = length;
  155
+						fire( memory[ 0 ], memory[ 1 ] );
  156
+					}
  157
+				}
  158
+				return this;
  159
+			},
  160
+			// Remove a callback from the list
  161
+			remove: function() {
  162
+				if ( list ) {
  163
+					var args = arguments,
  164
+						argIndex = 0,
  165
+						argLength = args.length;
  166
+					for ( ; argIndex < argLength ; argIndex++ ) {
  167
+						for ( var i = 0; i < list.length; i++ ) {
  168
+							if ( args[ argIndex ] === list[ i ][ 0 ] ) {
  169
+								// Handle firingIndex and firingLength
  170
+								if ( firing ) {
  171
+									if ( i <= firingLength ) {
  172
+										firingLength--;
  173
+										if ( i <= firingIndex ) {
  174
+											firingIndex--;
  175
+										}
  176
+									}
  177
+								}
  178
+								// Remove the element
  179
+								list.splice( i--, 1 );
  180
+								// If we have some unicity property then
  181
+								// we only need to do this once
  182
+								if ( flags.unique || flags.relocate ) {
  183
+									break;
  184
+								}
  185
+							}
  186
+						}
  187
+					}
  188
+				}
  189
+				return this;
  190
+			},
  191
+			// Control if a given callback is in the list
  192
+			has: function( fn ) {
  193
+				if ( list ) {
  194
+					var i = 0,
  195
+						length = list.length;
  196
+					for ( ; i < length; i++ ) {
  197
+						if ( fn === list[ i ][ 0 ] ) {
  198
+							return true;
  199
+						}
  200
+					}
  201
+				}
  202
+				return false;
  203
+			},
  204
+			// Remove all callbacks from the list
  205
+			empty: function() {
  206
+				list = [];
  207
+				return this;
  208
+			},
  209
+			// Have the list do nothing anymore
  210
+			disable: function() {
  211
+				list = stack = memory = undefined;
  212
+				return this;
  213
+			},
  214
+			// Is it disabled?
  215
+			disabled: function() {
  216
+				return !list;
  217
+			},
  218
+			// Lock the list in its current state
  219
+			lock: function() {
  220
+				stack = undefined;
  221
+				if ( !memory || memory === true ) {
  222
+					self.disable();
  223
+				}
  224
+				return this;
  225
+			},
  226
+			// Is it locked?
  227
+			locked: function() {
  228
+				return !stack;
  229
+			},
  230
+			// Call all callbacks with the given context and arguments
  231
+			fireWith: function( context, args ) {
  232
+				if ( stack ) {
  233
+					if ( firing ) {
  234
+						if ( !flags.once ) {
  235
+							stack.push( [ context, args ] );
  236
+						}
  237
+					} else if ( !( flags.once && memory ) ) {
  238
+						fire( context, args );
  239
+					}
  240
+				}
  241
+				return this;
  242
+			},
  243
+			// Call all the callbacks with the given arguments
  244
+			fire: function() {
  245
+				self.fireWith( this, arguments );
  246
+				return this;
  247
+			},
  248
+			// To know if the callbacks have already been called at least once
  249
+			fired: function() {
  250
+				return !!memory;
  251
+			}
  252
+		};
  253
+
  254
+	return self;
  255
+};
  256
+
  257
+})( jQuery );
6  src/core.js
@@ -258,7 +258,7 @@ jQuery.fn = jQuery.prototype = {
258 258
 		jQuery.bindReady();
259 259
 
260 260
 		// Add the callback
261  
-		readyList.done( fn );
  261
+		readyList.add( fn );
262 262
 
263 263
 		return this;
264 264
 	},
@@ -413,7 +413,7 @@ jQuery.extend({
413 413
 			}
414 414
 
415 415
 			// If there are functions bound, to execute
416  
-			readyList.resolveWith( document, [ jQuery ] );
  416
+			readyList.fireWith( document, [ jQuery ] );
417 417
 
418 418
 			// Trigger any bound ready events
419 419
 			if ( jQuery.fn.trigger ) {
@@ -427,7 +427,7 @@ jQuery.extend({
427 427
 			return;
428 428
 		}
429 429
 
430  
-		readyList = jQuery._Deferred();
  430
+		readyList = jQuery.Callbacks( "once memory" );
431 431
 
432 432
 		// Catch cases where $(document).ready() is called after the
433 433
 		// browser event has already occurred.
232  src/deferred.js
... ...
@@ -1,187 +1,127 @@
1 1
 (function( jQuery ) {
2 2
 
3 3
 var // Promise methods
4  
-	promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
  4
+	promiseMethods = "done removeDone fail removeFail progress removeProgress isResolved isRejected promise then always pipe".split( " " ),
5 5
 	// Static reference to slice
6 6
 	sliceDeferred = [].slice;
7 7
 
8 8
 jQuery.extend({
9  
-	// Create a simple deferred (one callbacks list)
10  
-	_Deferred: function() {
11  
-		var // callbacks list
12  
-			callbacks = [],
13  
-			// stored [ context , args ]
14  
-			fired,
15  
-			// to avoid firing when already doing so
16  
-			firing,
17  
-			// flag to know if the deferred has been cancelled
18  
-			cancelled,
19  
-			// the deferred itself
20  
-			deferred  = {
21 9
 
22  
-				// done( f1, f2, ...)
23  
-				done: function() {
24  
-					if ( !cancelled ) {
25  
-						var args = arguments,
26  
-							i,
27  
-							length,
28  
-							elem,
29  
-							type,
30  
-							_fired;
31  
-						if ( fired ) {
32  
-							_fired = fired;
33  
-							fired = 0;
34  
-						}
35  
-						for ( i = 0, length = args.length; i < length; i++ ) {
36  
-							elem = args[ i ];
37  
-							type = jQuery.type( elem );
38  
-							if ( type === "array" ) {
39  
-								deferred.done.apply( deferred, elem );
40  
-							} else if ( type === "function" ) {
41  
-								callbacks.push( elem );
42  
-							}
43  
-						}
44  
-						if ( _fired ) {
45  
-							deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
46  
-						}
47  
-					}
48  
-					return this;
49  
-				},
  10
+	Deferred: function( func ) {
  11
+		var doneList = jQuery.Callbacks( "once memory" ),
  12
+			failList = jQuery.Callbacks( "once memory" ),
  13
+			progressList = jQuery.Callbacks( "memory" ),
  14
+			promise,
  15
+			deferred = {
  16
+				// Copy existing methods from lists
  17
+				done: doneList.add,
  18
+				removeDone: doneList.remove,
  19
+				fail: failList.add,
  20
+				removeFail: failList.remove,
  21
+				progress: progressList.add,
  22
+				removeProgress: progressList.remove,
  23
+				resolve: doneList.fire,
  24
+				resolveWith: doneList.fireWith,
  25
+				reject: failList.fire,
  26
+				rejectWith: failList.fireWith,
  27
+				notify: progressList.fire,
  28
+				notifyWith: progressList.fireWith,
  29
+				isResolved: doneList.fired,
  30
+				isRejected: failList.fired,
50 31
 
51  
-				// resolve with given context and args
52  
-				resolveWith: function( context, args ) {
53  
-					if ( !cancelled && !fired && !firing ) {
54  
-						// make sure args are available (#8421)
55  
-						args = args || [];
56  
-						firing = 1;
57  
-						try {
58  
-							while( callbacks[ 0 ] ) {
59  
-								callbacks.shift().apply( context, args );
60  
-							}
61  
-						}
62  
-						finally {
63  
-							fired = [ context, args ];
64  
-							firing = 0;
65  
-						}
66  
-					}
  32
+				// Create Deferred-specific methods
  33
+				then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
  34
+					deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
67 35
 					return this;
68 36
 				},
69  
-
70  
-				// resolve with this as context and given arguments
71  
-				resolve: function() {
72  
-					deferred.resolveWith( this, arguments );
73  
-					return this;
  37
+				always: function() {
  38
+					return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
74 39
 				},
75  
-
76  
-				// Has this deferred been resolved?
77  
-				isResolved: function() {
78  
-					return !!( firing || fired );
  40
+				pipe: function( fnDone, fnFail, fnProgress ) {
  41
+					return jQuery.Deferred(function( newDefer ) {
  42
+						jQuery.each( {
  43
+							done: [ fnDone, "resolve" ],
  44
+							fail: [ fnFail, "reject" ],
  45
+							progress: [ fnProgress, "notify" ]
  46
+						}, function( handler, data ) {
  47
+							var fn = data[ 0 ],
  48
+								action = data[ 1 ],
  49
+								returned;
  50
+							if ( jQuery.isFunction( fn ) ) {
  51
+								deferred[ handler ](function() {
  52
+									returned = fn.apply( this, arguments );
  53
+									if ( returned && jQuery.isFunction( returned.promise ) ) {
  54
+										returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
  55
+									} else {
  56
+										newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
  57
+									}
  58
+								});
  59
+							} else {
  60
+								deferred[ handler ]( newDefer[ action ] );
  61
+							}
  62
+						});
  63
+					}).promise();
79 64
 				},
80  
-
81  
-				// Cancel
82  
-				cancel: function() {
83  
-					cancelled = 1;
84  
-					callbacks = [];
85  
-					return this;
  65
+				// Get a promise for this deferred
  66
+				// If obj is provided, the promise aspect is added to the object
  67
+				promise: function( obj ) {
  68
+					if ( obj == null ) {
  69
+						if ( promise ) {
  70
+							return promise;
  71
+						}
  72
+						promise = obj = {};
  73
+					}
  74
+					var i = promiseMethods.length;
  75
+					while( i-- ) {
  76
+						obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
  77
+					}
  78
+					return obj;
86 79
 				}
87 80
 			};
88 81
 
89  
-		return deferred;
90  
-	},
  82
+		// Handle lists exclusiveness
  83
+		deferred.done( failList.disable, progressList.lock )
  84
+			.fail( doneList.disable, progressList.lock );
91 85
 
92  
-	// Full fledged deferred (two callbacks list)
93  
-	Deferred: function( func ) {
94  
-		var deferred = jQuery._Deferred(),
95  
-			failDeferred = jQuery._Deferred(),
96  
-			promise;
97  
-		// Add errorDeferred methods, then and promise
98  
-		jQuery.extend( deferred, {
99  
-			then: function( doneCallbacks, failCallbacks ) {
100  
-				deferred.done( doneCallbacks ).fail( failCallbacks );
101  
-				return this;
102  
-			},
103  
-			always: function() {
104  
-				return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
105  
-			},
106  
-			fail: failDeferred.done,
107  
-			rejectWith: failDeferred.resolveWith,
108  
-			reject: failDeferred.resolve,
109  
-			isRejected: failDeferred.isResolved,
110  
-			pipe: function( fnDone, fnFail ) {
111  
-				return jQuery.Deferred(function( newDefer ) {
112  
-					jQuery.each( {
113  
-						done: [ fnDone, "resolve" ],
114  
-						fail: [ fnFail, "reject" ]
115  
-					}, function( handler, data ) {
116  
-						var fn = data[ 0 ],
117  
-							action = data[ 1 ],
118  
-							returned;
119  
-						if ( jQuery.isFunction( fn ) ) {
120  
-							deferred[ handler ](function() {
121  
-								returned = fn.apply( this, arguments );
122  
-								if ( returned && jQuery.isFunction( returned.promise ) ) {
123  
-									returned.promise().then( newDefer.resolve, newDefer.reject );
124  
-								} else {
125  
-									newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
126  
-								}
127  
-							});
128  
-						} else {
129  
-							deferred[ handler ]( newDefer[ action ] );
130  
-						}
131  
-					});
132  
-				}).promise();
133  
-			},
134  
-			// Get a promise for this deferred
135  
-			// If obj is provided, the promise aspect is added to the object
136  
-			promise: function( obj ) {
137  
-				if ( obj == null ) {
138  
-					if ( promise ) {
139  
-						return promise;
140  
-					}
141  
-					promise = obj = {};
142  
-				}
143  
-				var i = promiseMethods.length;
144  
-				while( i-- ) {
145  
-					obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
146  
-				}
147  
-				return obj;
148  
-			}
149  
-		});
150  
-		// Make sure only one callback list will be used
151  
-		deferred.done( failDeferred.cancel ).fail( deferred.cancel );
152  
-		// Unexpose cancel
153  
-		delete deferred.cancel;
154 86
 		// Call given func if any
155 87
 		if ( func ) {
156 88
 			func.call( deferred, deferred );
157 89
 		}
  90
+
  91
+		// All done!
158 92
 		return deferred;
159 93
 	},
160 94
 
161 95
 	// Deferred helper
162 96
 	when: function( firstParam ) {
163  
-		var args = arguments,
  97
+		var args = sliceDeferred.call( arguments, 0 ),
164 98
 			i = 0,
165 99
 			length = args.length,
  100
+			pValues = new Array( length ),
166 101
 			count = length,
  102
+			pCount = length,
167 103
 			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
168 104
 				firstParam :
169  
-				jQuery.Deferred();
  105
+				jQuery.Deferred(),
  106
+			promise = deferred.promise();
170 107
 		function resolveFunc( i ) {
171 108
 			return function( value ) {
172 109
 				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
173 110
 				if ( !( --count ) ) {
174  
-					// Strange bug in FF4:
175  
-					// Values changed onto the arguments object sometimes end up as undefined values
176  
-					// outside the $.when method. Cloning the object into a fresh array solves the issue
177  
-					deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
  111
+					deferred.resolveWith( deferred, args );
178 112
 				}
179 113
 			};
180 114
 		}
  115
+		function progressFunc( i ) {
  116
+			return function( value ) {
  117
+				pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
  118
+				deferred.notifyWith( promise, pValues );
  119
+			};
  120
+		}
181 121
 		if ( length > 1 ) {
182 122
 			for( ; i < length; i++ ) {
183  
-				if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
184  
-					args[ i ].promise().then( resolveFunc(i), deferred.reject );
  123
+				if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
  124
+					args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
185 125
 				} else {
186 126
 					--count;
187 127
 				}
@@ -192,8 +132,8 @@ jQuery.extend({
192 132
 		} else if ( deferred !== firstParam ) {
193 133
 			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
194 134
 		}
195  
-		return deferred.promise();
  135
+		return promise;
196 136
 	}
197 137
 });
198 138
 
199  
-})( jQuery );
  139
+})( jQuery );
6  src/queue.js
@@ -14,7 +14,7 @@ function handleQueueMarkDefer( elem, type, src ) {
14 14
 			if ( !jQuery._data( elem, queueDataKey ) &&
15 15
 				!jQuery._data( elem, markDataKey ) ) {
16 16
 				jQuery.removeData( elem, deferDataKey, true );
17  
-				defer.resolve();
  17
+				defer.fire();
18 18
 			}
19 19
 		}, 0 );
20 20
 	}
@@ -160,9 +160,9 @@ jQuery.fn.extend({
160 160
 			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
161 161
 					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
162 162
 						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
163  
-					jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
  163
+					jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
164 164
 				count++;
165  
-				tmp.done( resolve );
  165
+				tmp.add( resolve );
166 166
 			}
167 167
 		}
168 168
 		resolve();
45  src/topic.js
... ...
@@ -0,0 +1,45 @@
  1
+(function( jQuery ) {
  2
+
  3
+	var topics = {},
  4
+		sliceTopic = [].slice;
  5
+
  6
+	jQuery.Topic = function( id ) {
  7
+		var callbacks,
  8
+			method,
  9
+			topic = id && topics[ id ];
  10
+		if ( !topic ) {
  11
+			callbacks = jQuery.Callbacks();
  12
+			topic = {
  13
+				publish: callbacks.fire,
  14
+				subscribe: callbacks.add,
  15
+				unsubscribe: callbacks.remove
  16
+			};
  17
+			if ( id ) {
  18
+				topics[ id ] = topic;
  19
+			}
  20
+		}
  21
+		return topic;
  22
+	};
  23
+
  24
+	jQuery.extend({
  25
+		subscribe: function( id ) {
  26
+			var topic = jQuery.Topic( id ),
  27
+				args = sliceTopic.call( arguments, 1 );
  28
+			topic.subscribe.apply( topic, args );
  29
+			return {
  30
+				topic: topic,
  31
+				args: args
  32
+			};
  33
+		},
  34
+		unsubscribe: function( id ) {
  35
+			var topic = id && id.topic || jQuery.Topic( id );
  36
+			topic.unsubscribe.apply( topic, id && id.args ||
  37
+					sliceTopic.call( arguments, 1 ) );
  38
+		},
  39
+		publish: function( id ) {
  40
+			var topic = jQuery.Topic( id );
  41
+			topic.publish.apply( topic, sliceTopic.call( arguments, 1 ) );
  42
+		}
  43
+	});
  44
+
  45
+})( jQuery );
2  test/data/offset/absolute.html
@@ -16,7 +16,9 @@
16 16
 			#positionTest { position: absolute; }
17 17
 		</style>
18 18
 		<script src="../../../src/core.js"></script>
  19
+		<script src="../../../src/callbacks.js"></script>
19 20
 		<script src="../../../src/deferred.js"></script>
  21
+		<script src="../../../src/topic.js"></script>
20 22
 		<script src="../../../src/support.js"></script>
21 23
 		<script src="../../../src/sizzle/sizzle.js"></script>
22 24
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/offset/body.html
@@ -9,7 +9,9 @@
9 9
 			#marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
10 10
 		</style>
11 11
 		<script src="../../../src/core.js"></script>
  12
+		<script src="../../../src/callbacks.js"></script>
12 13
 		<script src="../../../src/deferred.js"></script>
  14
+		<script src="../../../src/topic.js"></script>
13 15
 		<script src="../../../src/support.js"></script>
14 16
 		<script src="../../../src/sizzle/sizzle.js"></script>
15 17
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/offset/fixed.html
@@ -13,7 +13,9 @@
13 13
 			#marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
14 14
 		</style>
15 15
 		<script src="../../../src/core.js"></script>
  16
+		<script src="../../../src/callbacks.js"></script>
16 17
 		<script src="../../../src/deferred.js"></script>
  18
+		<script src="../../../src/topic.js"></script>
17 19
 		<script src="../../../src/support.js"></script>
18 20
 		<script src="../../../src/sizzle/sizzle.js"></script>
19 21
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/offset/relative.html
@@ -11,7 +11,9 @@
11 11
 			#marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
12 12
 		</style>
13 13
 		<script src="../../../src/core.js"></script>
  14
+		<script src="../../../src/callbacks.js"></script>
14 15
 		<script src="../../../src/deferred.js"></script>
  16
+		<script src="../../../src/topic.js"></script>
15 17
 		<script src="../../../src/support.js"></script>
16 18
 		<script src="../../../src/sizzle/sizzle.js"></script>
17 19
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/offset/scroll.html
@@ -14,7 +14,9 @@
14 14
 			#marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
15 15
 		</style>
16 16
 		<script src="../../../src/core.js"></script>
  17
+		<script src="../../../src/callbacks.js"></script>
17 18
 		<script src="../../../src/deferred.js"></script>
  19
+		<script src="../../../src/topic.js"></script>
18 20
 		<script src="../../../src/support.js"></script>
19 21
 		<script src="../../../src/sizzle/sizzle.js"></script>
20 22
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/offset/static.html
@@ -11,7 +11,9 @@
11 11
 			#marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
12 12
 		</style>
13 13
 		<script src="../../../src/core.js"></script>
  14
+		<script src="../../../src/callbacks.js"></script>
14 15
 		<script src="../../../src/deferred.js"></script>
  16
+		<script src="../../../src/topic.js"></script>
15 17
 		<script src="../../../src/support.js"></script>
16 18
 		<script src="../../../src/sizzle/sizzle.js"></script>
17 19
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/offset/table.html
@@ -11,7 +11,9 @@
11 11
 			#marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
12 12
 		</style>
13 13
 		<script src="../../../src/core.js"></script>
  14
+		<script src="../../../src/callbacks.js"></script>
14 15
 		<script src="../../../src/deferred.js"></script>
  16
+		<script src="../../../src/topic.js"></script>
15 17
 		<script src="../../../src/support.js"></script>
16 18
 		<script src="../../../src/sizzle/sizzle.js"></script>
17 19
 		<script src="../../../src/sizzle-jquery.js"></script>
2  test/data/support/bodyBackground.html
@@ -11,7 +11,9 @@
11 11
 <body>
12 12
 	<div>
13 13
 		<script src="../../../src/core.js"></script>
  14
+		<script src="../../../src/callbacks.js"></script>
14 15
 		<script src="../../../src/deferred.js"></script>
  16
+		<script src="../../../src/topic.js"></script>
15 17
 		<script src="../../../src/support.js"></script>
16 18
 		<script src="../../../src/data.js"></script>
17 19
 		<script src="../../../src/queue.js"></script>
2  test/data/support/boxModelIE.html
@@ -2,7 +2,9 @@
2 2
 <html>
3 3
 <body>
4 4
 	<script src="../../../src/core.js"></script>
  5
+	<script src="../../../src/callbacks.js"></script>
5 6
 	<script src="../../../src/deferred.js"></script>
  7
+	<script src="../../../src/topic.js"></script>
6 8
 	<script src="../../../src/support.js"></script>
7 9
 	<script src="../../../src/data.js"></script>
8 10
 	<script src="../../../src/queue.js"></script>
2  test/data/support/hiddenIFrameFF.html
... ...
@@ -1,7 +1,9 @@
1 1
 <html>
2 2
 	<head>
3 3
 		<script src="../../../src/core.js"></script>
  4
+		<script src="../../../src/callbacks.js"></script>
4 5
 		<script src="../../../src/deferred.js"></script>
  6
+		<script src="../../../src/topic.js"></script>
5 7
 		<script src="../../../src/support.js"></script>
6 8
 		<script src="../../../src/data.js"></script>
7 9
 		<script src="../../../src/queue.js"></script>
4  test/index.html
@@ -9,7 +9,9 @@
9 9
 	<script src="data/testinit.js"></script>
10 10
 
11 11
 	<script src="../src/core.js"></script>
  12
+	<script src="../src/callbacks.js"></script>
12 13
 	<script src="../src/deferred.js"></script>
  14
+	<script src="../src/topic.js"></script>
13 15
 	<script src="../src/support.js"></script>
14 16
 	<script src="../src/data.js"></script>
15 17
 	<script src="../src/queue.js"></script>
@@ -35,7 +37,9 @@
35 37
 
36 38
 	<script src="unit/core.js"></script>
37 39
 	<script src="unit/support.js"></script>
  40
+	<script src="unit/callbacks.js"></script>
38 41
 	<script src="unit/deferred.js"></script>
  42
+	<script src="unit/topic.js"></script>
39 43
 	<script src="unit/data.js"></script>
40 44
 	<script src="unit/queue.js"></script>
41 45
 	<script src="unit/attributes.js"></script>
194  test/unit/callbacks.js
... ...
@@ -0,0 +1,194 @@
  1
+module("callbacks", { teardown: moduleTeardown });
  2
+
  3
+(function() {
  4
+
  5
+var output,
  6
+	addToOutput = function( string ) {
  7
+		return function() {
  8
+			output += string;
  9
+		};
  10
+	},
  11
+	outputA = addToOutput( "A" ),
  12
+	outputB = addToOutput( "B" ),
  13
+	outputC = addToOutput( "C" ),
  14
+	tests = {
  15
+		"":							"XABC 	X		XABCABCC 	X 	XBB X	XABA	X",
  16
+		"once":						"XABC 	X 		X 			X 	X 	X	XABA	X",
  17
+		"memory":					"XABC 	XABC 	XABCABCCC 	XA 	XBB	XB	XABA	XC",
  18
+		"unique":					"XABC 	X		XABCA		X	XBB	X	XAB		X",
  19
+		"relocate":					"XABC 	X		XAABC		X	XBB X	XBA		X",
  20
+		"stopOnFalse":				"XABC 	X		XABCABCC	X	XBB	X	XA		X",
  21
+		"addAfterFire":				"XAB	X		XABCAB		X	XBB	X	XABA	X",
  22
+		"queue":					"XA		X		XB			X	XB	X	XA		X",
  23
+		"once memory":				"XABC 	XABC	X			XA	X	XA	XABA	XC",
  24
+		"once unique":				"XABC 	X		X			X	X	X	XAB		X",
  25
+		"once relocate":			"XABC 	X		X			X	X	X	XBA		X",
  26
+		"once stopOnFalse":			"XABC 	X		X			X	X	X	XA		X",
  27
+		"once addAfterFire":		"XAB	X		X			X	X	X	XABA	X",
  28
+		"memory unique":			"XABC 	XA		XABCA		XA	XBB	XB	XAB		XC",
  29
+		"memory relocate":			"XABC 	XB		XAABC		XA	XBB	XB	XBA		XC",
  30
+		"memory stopOnFalse":		"XABC 	XABC	XABCABCCC	XA	XBB	XB	XA		X",
  31
+		"memory addAfterFire":		"XAB	XAB		XABCABC		XA	XBB	XB	XABA	XC",
  32
+		"unique relocate":			"XABC 	X		XAABC		X	XBB	X	XBA		X",
  33
+		"unique stopOnFalse":		"XABC 	X		XABCA		X	XBB	X	XA		X",
  34
+		"unique addAfterFire":		"XAB	X		XABCA		X	XBB	X	XAB		X",
  35
+		"relocate stopOnFalse":		"XABC 	X		XAABC		X	XBB	X	X		X",
  36
+		"relocate addAfterFire":	"XAB	X		XAA			X	XBB	X	XBA		X",
  37
+		"stopOnFalse addAfterFire":	"XAB	X		XABCAB		X	XBB	X	XA		X"
  38
+	},
  39
+	filters = {
  40
+		"no filter": undefined,
  41
+		"filter": function( fn ) {
  42
+			return function() {
  43
+				return fn.apply( this, arguments );
  44
+			};
  45
+		}
  46
+	};
  47
+
  48
+jQuery.each( tests, function( flags, resultString ) {
  49
+
  50
+		jQuery.each( filters, function( filterLabel, filter ) {
  51
+
  52
+			test( "jQuery.Callbacks( \"" + flags + "\" ) - " + filterLabel, function() {
  53
+
  54
+				expect( 19 );
  55
+
  56
+				// Give qunit a little breathing room
  57
+				stop();
  58
+				setTimeout( start, 0 );
  59
+
  60
+				var cblist;
  61
+					results = resultString.split( /\s+/ );
  62
+
  63
+				// Basic binding and firing
  64
+				output = "X";
  65
+				cblist = jQuery.Callbacks( flags );
  66
+				cblist.add(function( str ) {
  67
+					output += str;
  68
+				});
  69
+				cblist.fire( "A" );
  70
+				strictEqual( output, "XA", "Basic binding and firing" );
  71
+				output = "X";
  72
+				cblist.disable();
  73
+				cblist.add(function( str ) {
  74
+					output += str;
  75
+				});
  76
+				strictEqual( output, "X", "Adding a callback after disabling" );
  77
+				cblist.fire( "A" );
  78
+				strictEqual( output, "X", "Firing after disabling" );
  79
+
  80
+				// Basic binding and firing (context, arguments)
  81
+				output = "X";
  82
+				cblist = jQuery.Callbacks( flags );
  83
+				cblist.add(function() {
  84
+					equals( this, window, "Basic binding and firing (context)" );
  85
+					output += Array.prototype.join.call( arguments, "" );
  86
+				});
  87
+				cblist.fireWith( window, [ "A", "B" ] );
  88
+				strictEqual( output, "XAB", "Basic binding and firing (arguments)" );
  89
+
  90
+				// fireWith with no arguments
  91
+				output = "";
  92
+				cblist = jQuery.Callbacks( flags );
  93
+				cblist.add(function() {
  94
+					equals( this, window, "fireWith with no arguments (context is window)" );
  95
+					strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" );
  96
+				});
  97
+				cblist.fireWith();
  98
+
  99
+				// Basic binding, removing and firing
  100
+				output = "X";
  101
+				cblist = jQuery.Callbacks( flags );
  102
+				cblist.add( outputA, outputB, outputC );
  103
+				cblist.remove( outputB, outputC );
  104
+				cblist.fire();
  105
+				strictEqual( output, "XA", "Basic binding, removing and firing" );
  106
+
  107
+				// Empty
  108
+				output = "X";
  109
+				cblist = jQuery.Callbacks( flags );
  110
+				cblist.add( outputA );
  111
+				cblist.add( outputB );
  112
+				cblist.add( outputC );
  113
+				cblist.empty();
  114
+				cblist.fire();
  115
+				strictEqual( output, "X", "Empty" );
  116
+
  117
+				// Locking
  118
+				output = "X";
  119
+				cblist = jQuery.Callbacks( flags );
  120
+				cblist.add( function( str ) {
  121
+					output += str;
  122
+				});
  123
+				cblist.lock();
  124
+				cblist.add( function( str ) {
  125
+					output += str;
  126
+				});
  127
+				cblist.fire( "A" );
  128
+				cblist.add( function( str ) {
  129
+					output += str;
  130
+				});
  131
+				strictEqual( output, "X", "Lock early" );
  132
+
  133
+				// Ordering
  134
+				output = "X";
  135
+				cblist = jQuery.Callbacks( flags );
  136
+				cblist.add( function() {
  137
+					cblist.add( outputC );
  138
+					outputA();
  139
+				}, outputB );
  140
+				cblist.fire();
  141
+				strictEqual( output, results.shift(), "Proper ordering" );
  142
+
  143
+				// Add and fire again
  144
+				output = "X";
  145
+				cblist.add( function() {
  146
+					cblist.add( outputC );
  147
+					outputA();
  148
+				}, outputB );
  149
+				strictEqual( output, results.shift(), "Add after fire" );
  150
+
  151
+				output = "X";
  152
+				cblist.fire();
  153
+				strictEqual( output, results.shift(), "Fire again" );
  154
+
  155
+				// Multiple fire
  156
+				output = "X";
  157
+				cblist = jQuery.Callbacks( flags );
  158
+				cblist.add( function( str ) {
  159
+					output += str;
  160
+				} );
  161
+				cblist.fire( "A" );
  162
+				strictEqual( output, "XA", "Multiple fire (first fire)" );
  163
+				output = "X";
  164
+				cblist.add( function( str ) {
  165
+					output += str;
  166
+				} );
  167
+				strictEqual( output, results.shift(), "Multiple fire (first new callback)" );
  168
+				output = "X";
  169
+				cblist.fire( "B" );
  170
+				strictEqual( output, results.shift(), "Multiple fire (second fire)" );
  171
+				output = "X";
  172
+				cblist.add( function( str ) {
  173
+					output += str;
  174
+				} );
  175
+				strictEqual( output, results.shift(), "Multiple fire (second new callback)" );
  176
+
  177
+				// Return false
  178
+				output = "X";
  179
+				cblist = jQuery.Callbacks( flags );
  180
+				cblist.add( outputA, function() { return false; }, outputB );
  181
+				cblist.add( outputA );
  182
+				cblist.fire();
  183
+				strictEqual( output, results.shift(), "Callback returning false" );
  184
+
  185
+				// Add another callback (to control lists with memory do not fire anymore)
  186
+				output = "X";
  187
+				cblist.add( outputC );
  188
+				strictEqual( output, results.shift(), "Adding a callback after one returned false" );
  189
+
  190
+			});
  191
+		});
  192
+});
  193
+
  194
+})();
217  test/unit/deferred.js
@@ -2,119 +2,13 @@ module("deferred", { teardown: moduleTeardown });
2 2
 
3 3
 jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
4 4
 
5  
-	function createDeferred() {
6  
-		return withNew ? new jQuery._Deferred() : jQuery._Deferred();
7  
-	}
8  
-
9  
-	test("jQuery._Deferred" + withNew, function() {
10  
-
11  
-		expect( 11 );
12  
-
13  
-		var deferred,
14  
-			object,
15  
-			test;
16  
-
17  
-		deferred = createDeferred();
18  
-
19  
-		test = false;
20  
-
21  
-		deferred.done( function( value ) {
22  
-			equals( value , "value" , "Test pre-resolve callback" );
23  
-			test = true;
24  
-		} );
25  
-
26  
-		deferred.resolve( "value" );
27  
-
28  
-		ok( test , "Test pre-resolve callbacks called right away" );
29  
-
30  
-		test = false;
31  
-
32  
-		deferred.done( function( value ) {
33  
-			equals( value , "value" , "Test post-resolve callback" );
34  
-			test = true;
35  
-		} );
36  
-
37  
-		ok( test , "Test post-resolve callbacks called right away" );
38  
-
39  
-		deferred.cancel();
40  
-
41  
-		test = true;
42  
-
43  
-		deferred.done( function() {
44  
-			ok( false , "Cancel was ignored" );
45  
-			test = false;
46  
-		} );
47  
-
48  
-		ok( test , "Test cancel" );
49  
-
50  
-		deferred = createDeferred().resolve();
51  
-
52  
-		try {
53  
-			deferred.done( function() {
54  
-				throw "Error";
55  
-			} , function() {
56  
-				ok( true , "Test deferred do not cancel on exception" );
57  
-			} );
58  
-		} catch( e ) {
59  
-			strictEqual( e , "Error" , "Test deferred propagates exceptions");
60  
-			deferred.done();
61  
-		}
62  
-
63  
-		test = "";
64  
-		deferred = createDeferred().done( function() {
65  
-
66  
-			test += "A";
67  
-
68  
-		}, function() {
69  
-
70  
-			test += "B";
71  
-
72  
-		} ).resolve();
73  
-
74  
-		strictEqual( test , "AB" , "Test multiple done parameters" );
75  
-
76  
-		test = "";
77  
-
78  
-		deferred.done( function() {
79  
-
80  
-			deferred.done( function() {
81  
-
82  
-				test += "C";
83  
-
84  
-			} );
85  
-
86  
-			test += "A";
87  
-
88  
-		}, function() {
89  
-
90  
-			test += "B";
91  
-		} );
92  
-
93  
-		strictEqual( test , "ABC" , "Test done callbacks order" );
94  
-
95  
-		deferred = createDeferred();
96  
-
97  
-		deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) {
98  
-			ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" );
99  
-		});
100  
-
101  
-		// #8421
102  
-		deferred = createDeferred();
103  
-		deferred.resolveWith().done(function() {
104  
-			ok( true, "Test resolveWith can be called with no argument" );
105  
-		});
106  
-	});
107  
-} );
108  
-
109  
-jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
110  
-
111 5
 	function createDeferred( fn ) {
112 6
 		return withNew ? new jQuery.Deferred( fn ) : jQuery.Deferred( fn );
113 7
 	}
114 8
 
115 9
 	test("jQuery.Deferred" + withNew, function() {
116 10
 
117  
-		expect( 8 );
  11
+		expect( 14 );
118 12
 
119 13
 		createDeferred().resolve().then( function() {
120 14
 			ok( true , "Success on resolve" );
@@ -140,6 +34,20 @@ jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
140 34
 		}).then( function( value ) {
141 35
 			strictEqual( value , "done" , "Passed function executed" );
142 36
 		});
  37
+
  38
+		jQuery.each( "resolve reject".split( " " ), function( _, change ) {
  39
+			createDeferred( function( defer ) {
  40
+				var checked = 0;
  41
+				defer.progress(function( value ) {
  42
+					strictEqual( value, checked, "Progress: right value (" + value + ") received" );
  43
+				});
  44
+				for( checked = 0; checked < 3 ; checked++ ) {
  45
+					defer.notify( checked );
  46
+				}
  47
+				defer[ change ]();
  48
+				defer.notify();
  49
+			});
  50
+		});
143 51
 	});
144 52
 } );
145 53
 
@@ -215,6 +123,34 @@ test( "jQuery.Deferred.pipe - filtering (fail)", function() {
215 123
 	});
216 124
 });
217 125
 
  126
+test( "jQuery.Deferred.pipe - filtering (progress)", function() {
  127
+
  128
+	expect(3);
  129
+
  130
+	var defer = jQuery.Deferred(),
  131
+		piped = defer.pipe( null, null, function( a, b ) {