elemdisplay = {},
rfxtypes = /^(?:toggle|show|hide)$/,
rfxnum = /^([\-+]=)?((?:\d*\.)?\d+)([a-z%]*)$/i,
- rrun = /\.run$/,
+ rrun = /queueHooks$/,
animationPrefilters = [ defaultPrefilter ],
tweeners = {
"*": [function( prop, value ) {
});
function defaultPrefilter( elem, props, opts ) {
- var index, prop, value, length, dataShow, tween,
+ var index, prop, value, length, dataShow, tween, hooks, oldfire,
+ anim = this,
style = elem.style,
orig = {},
handled = [],
hidden = elem.nodeType && isHidden( elem );
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ }
+
// height/width overflow pass
if ( elem.nodeType === 1 && ( props.height || props.width ) ) {
// Make sure that nothing sneaks out
if ( opts.overflow ) {
style.overflow = "hidden";
- this.finish(function() {
+ anim.finish(function() {
style.overflow = opts.overflow[ 0 ];
style.overflowX = opts.overflow[ 1 ];
style.overflowY = opts.overflow[ 2 ];
if ( hidden ) {
showHide([ elem ], true );
} else {
- this.finish(function() {
+ anim.finish(function() {
showHide([ elem ]);
});
}
- this.finish(function() {
+ anim.finish(function() {
var prop;
jQuery.removeData( elem, "fxshow", true );
for ( prop in orig ) {
});
for ( index = 0 ; index < length ; index++ ) {
prop = handled[ index ];
- tween = this.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+ tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
if ( !( prop in dataShow ) ) {
this.queue( optall.queue, doAnimation );
},
stop: function( type, clearQueue, gotoEnd ) {
- var stopQueue = function( elem, data, index ) {
- var hooks = data[ index ];
- jQuery.removeData( elem, index, true );
- hooks.stop( gotoEnd );
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
};
if ( typeof type !== "string" ) {
}
return this.each(function() {
- var index,
- hadTimers = false,
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
timers = jQuery.timers,
data = jQuery._data( this );
- // clear marker counters if we know they won't be
- if ( !gotoEnd ) {
- jQuery._unmark( true, this );
- }
-
- if ( type == null ) {
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
for ( index in data ) {
if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
- stopQueue( this, data, index );
+ stopQueue( data[ index ] );
}
}
- } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
- stopQueue( this, data, index );
}
for ( index = timers.length; index--; ) {
if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
timers[ index ].anim.stop( gotoEnd );
- hadTimers = true;
+ dequeue = false;
timers.splice( index, 1 );
}
}
// start the next in the queue if the last step wasn't forced
// timers currently will call their complete callbacks, which will dequeue
// but only if they were gotoEnd
- if ( !( gotoEnd && hadTimers ) ) {
+ if ( dequeue || !gotoEnd ) {
jQuery.dequeue( this, type );
}
});
// Queueing
opt.old = opt.complete;
- opt.complete = function( noUnmark ) {
+ opt.complete = function() {
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
}
if ( opt.queue ) {
jQuery.dequeue( this, opt.queue );
- } else if ( noUnmark !== false ) {
- jQuery._unmark( this );
}
};
(function( jQuery ) {
-function handleQueueMarkDefer( elem, type, src ) {
- var deferDataKey = type + "defer",
- queueDataKey = type + "queue",
- markDataKey = type + "mark",
- defer = jQuery._data( elem, deferDataKey );
- if ( defer &&
- ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
- ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
- // Give room for hard-coded callbacks to fire first
- // and eventually mark/queue something else on the element
- setTimeout( function() {
- if ( !jQuery._data( elem, queueDataKey ) &&
- !jQuery._data( elem, markDataKey ) ) {
- jQuery.removeData( elem, deferDataKey, true );
- defer.fire();
- }
- }, 0 );
- }
-}
-
jQuery.extend({
-
- _mark: function( elem, type ) {
- if ( elem ) {
- type = ( type || "fx" ) + "mark";
- jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
- }
- },
-
- _unmark: function( force, elem, type ) {
- if ( force !== true ) {
- type = elem;
- elem = force;
- force = false;
- }
- if ( elem ) {
- type = type || "fx";
- var key = type + "mark",
- count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
- if ( count ) {
- jQuery._data( elem, key, count );
- } else {
- jQuery.removeData( elem, key, true );
- handleQueueMarkDefer( elem, type, "mark" );
- }
- }
- },
-
queue: function( elem, type, data ) {
- var q;
+ var queue;
+
if ( elem ) {
type = ( type || "fx" ) + "queue";
- q = jQuery._data( elem, type );
+ queue = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
- if ( !q || jQuery.isArray(data) ) {
- q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
- q.push( data );
+ queue.push( data );
}
}
- return q || [];
+ return queue || [];
}
},
var queue = jQuery.queue( elem, type ),
fn = queue.shift(),
- hooks = {};
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
// If the fx queue is dequeued, always remove the progress sentinel
if ( fn === "inprogress" ) {
}
if ( fn ) {
+
// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
if ( type === "fx" ) {
queue.unshift( "inprogress" );
}
- jQuery._data( elem, type + ".run", hooks );
- fn.call( elem, function() {
- jQuery.dequeue( elem, type );
- }, hooks );
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
}
-
- if ( !queue.length ) {
- jQuery.removeData( elem, type + "queue " + type + ".run", true );
- handleQueueMarkDefer( elem, type, "queue" );
+ if ( !queue.length && hooks ) {
+ hooks.empty.fire();
}
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
}
});
this.each(function() {
var queue = jQuery.queue( this, type, data );
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
// Get a promise resolved when queues of a certain type
// are emptied (fx is the type by default)
promise: function( type, object ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
if ( typeof type !== "string" ) {
object = type;
type = undefined;
}
type = type || "fx";
- var defer = jQuery.Deferred(),
- elements = this,
- i = elements.length,
- count = 1,
- deferDataKey = type + "defer",
- queueDataKey = type + "queue",
- markDataKey = type + "mark",
- tmp;
- function resolve() {
- if ( !( --count ) ) {
- defer.resolveWith( elements, [ elements ] );
- }
- }
+
while( i-- ) {
- 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.Callbacks( "once memory" ), true ) )) {
+ if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
count++;
- tmp.add( resolve );
+ tmp.empty.add( resolve );
}
}
resolve();
start();
});
});
+
+asyncTest( "Handle queue:false promises", 10, function() {
+ var foo = jQuery( "#foo" ).clone().andSelf(),
+ step = 1;
+
+ foo.animate({
+ top: 1
+ }, {
+ duration: 10,
+ queue: false,
+ complete: function() {
+ ok( step++ <= 2, "Step one or two" );
+ }
+ }).animate({
+ bottom: 1
+ }, {
+ duration: 10,
+ complete: function() {
+ ok( step > 2 && step < 5, "Step three or four" );
+ step++;
+ }
+ });
+
+ foo.promise().done( function() {
+ equal( step++, 5, "steps 1-5: queue:false then queue:fx done" );
+ foo.animate({
+ top: 10
+ }, {
+ duration: 10,
+ complete: function() {
+ ok( step > 5 && step < 8, "Step six or seven" );
+ step++;
+ }
+ }).animate({
+ bottom: 10
+ }, {
+ duration: 10,
+ queue: false,
+ complete: function() {
+ ok( step > 7 && step < 10, "Step eight or nine" );
+ step++;
+ }
+ }).promise().done( function() {
+ equal( step++, 10, "steps 6-10: queue:fx then queue:false" );
+ start();
+ });
+
+ });
+});
+
+asyncTest( "multiple unqueued and promise", 4, function() {
+ var foo = jQuery( "#foo" ),
+ step = 1;
+ foo.animate({
+ marginLeft: 300
+ }, {
+ duration: 500,
+ queue: false,
+ complete: function() {
+ strictEqual( step++, 2, "Step 2" );
+ }
+ }).animate({
+ top: 100
+ }, {
+ duration: 1500,
+ queue: false,
+ complete: function() {
+ strictEqual( step++, 3, "Step 3" );
+ }
+ }).animate({}, {
+ duration: 2000,
+ queue: false,
+ complete: function() {
+ // no properties is a non-op and finishes immediately
+ strictEqual( step++, 1, "Step 1" );
+ }
+ }).promise().done( function() {
+ strictEqual( step++, 4, "Step 4" );
+ start();
+ });
+});
\ No newline at end of file
-module("queue", { teardown: moduleTeardown });
+module( "queue", {
+ teardown: moduleTeardown
+});
-test("queue() with other types",function() {
- expect(12);
+test( "queue() with other types", 12, function() {
var counter = 0;
stop();
var $div = jQuery({}),
defer;
-
- $div.promise("foo").done(function() {
+ $div.promise( "foo" ).done(function() {
equal( counter, 0, "Deferred for collection with no queue is automatically resolved" );
});
});
defer = $div.promise("foo").done(function() {
- equal( counter, 4, "Testing previous call to dequeue in deferred" );
+ equal( counter, 4, "Testing previous call to dequeue in deferred" );
start();
});
div.removeData();
});
-test("_mark() and _unmark()", function() {
- expect(1);
-
- var div = {},
- $div = jQuery( div );
+asyncTest( "fn.promise() - called when fx queue is empty", 3, function() {
+ var foo = jQuery( "#foo" ).clone().andSelf(),
+ promised = false;
- stop();
-
- jQuery._mark( div, "foo" );
- jQuery._mark( div, "foo" );
- jQuery._unmark( div, "foo" );
- jQuery._unmark( div, "foo" );
-
- $div.promise( "foo" ).done(function() {
- ok( true, "No more marks" );
- start();
+ foo.queue( function( next ) {
+ // called twice!
+ ok( !promised, "Promised hasn't been called" );
+ setTimeout( next, 10 );
});
-});
-
-test("_mark() and _unmark() default to 'fx'", function() {
- expect(1);
-
- var div = {},
- $div = jQuery( div );
-
- stop();
-
- jQuery._mark( div );
- jQuery._mark( div );
- jQuery._unmark( div, "fx" );
- jQuery._unmark( div );
-
- $div.promise().done(function() {
- ok( true, "No more marks" );
+ foo.promise().done( function() {
+ ok( promised = true, "Promised" );
start();
});
});
-test("promise()", function() {
- expect(1);
-
- stop();
-
- var objects = [];
-
- jQuery.each( [{}, {}], function( i, div ) {
- var $div = jQuery( div );
- $div.queue(function( next ) {
- setTimeout( function() {
- if ( i ) {
- next();
- setTimeout( function() {
- jQuery._unmark( div );
- }, 20 );
- } else {
- jQuery._unmark( div );
- setTimeout( function() {
- next();
- }, 20 );
- }
- }, 50 );
- }).queue(function( next ) {
- next();
- });
- jQuery._mark( div );
- objects.push( $div );
+asyncTest( "fn.promise( \"queue\" ) - called whenever last queue function is dequeued", 5, function() {
+ var foo = jQuery( "#foo" ),
+ test;
+ foo.promise( "queue" ).done( function() {
+ strictEqual( test, undefined, "called immediately when queue was already empty" );
});
-
- jQuery.when.apply( jQuery, objects ).done(function() {
- ok( true, "Deferred resolved" );
- start();
+ test = 1;
+ foo.queue( "queue", function( next ) {
+ strictEqual( test++, 1, "step one" );
+ setTimeout( next, 0 );
+ }).queue( "queue", function( next ) {
+ strictEqual( test++, 2, "step two" );
+ setTimeout( function() {
+ strictEqual( test++, 4, "step four" );
+ next();
+ start();
+ }, 10 );
+ }).promise( "queue" ).done( function() {
+ strictEqual( test++, 3, "step three" );
});
- jQuery.each( objects, function() {
- this.dequeue();
- });
+ foo.dequeue( "queue" );
});
-test(".promise(obj)", function() {
+test( ".promise(obj)", function() {
expect(2);
var obj = {};
ok( jQuery.isFunction( promise.promise ), ".promise(type, obj) returns a promise" );
strictEqual( promise, obj, ".promise(type, obj) returns obj" );
});
+
+asyncTest( "queue stop hooks", 2, function() {
+ var foo = jQuery( "#foo" );
+
+ foo.queue( function( next, hooks ) {
+ hooks.stop = function( gotoEnd ) {
+ equal( !!gotoEnd, false, "Stopped without gotoEnd" );
+ };
+ });
+ foo.stop();
+
+ foo.queue( function( next, hooks ) {
+ hooks.stop = function( gotoEnd ) {
+ equal( gotoEnd, true, "Stopped with gotoEnd" );
+ start();
+ };
+ });
+
+ foo.stop( false, true );
+});