From a3b59d7f92c9e15af1888fc4e87639a290763a50 Mon Sep 17 00:00:00 2001 From: Corey Frang Date: Wed, 28 Sep 2011 11:55:29 -0400 Subject: [PATCH] Landing pull request 514. 1.7 - queue refactoring to handle delay stop - Fixes #6150. More Details: - https://github.com/jquery/jquery/pull/514 - http://bugs.jquery.com/ticket/6150 --- src/effects.js | 66 +++++++++++++++++++++++++++++++------------- src/queue.js | 22 ++++++++------- test/unit/effects.js | 54 +++++++++++++++++++++++++++++++++++- test/unit/queue.js | 38 +++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 30 deletions(-) diff --git a/src/effects.js b/src/effects.js index 3edb96fae..ee535e3cd 100644 --- a/src/effects.js +++ b/src/effects.js @@ -200,6 +200,7 @@ jQuery.fn.extend({ val = prop[ p ]; if ( rfxtypes.test( val ) ) { + // Tracks whether to show or hide based on private // data attached to the element method = jQuery._data( this, "toggle" + p ) || (val === "toggle" ? hidden ? "show" : "hide" : 0); @@ -244,42 +245,62 @@ jQuery.fn.extend({ return optall.queue === false ? this.each( doAnimation ) : - this.queue( optall.queue || "fx", doAnimation ); + this.queue( optall.queue, doAnimation ); }, - stop: function( clearQueue, gotoEnd ) { - if ( clearQueue ) { - this.queue([]); + stop: function( clearQueue, gotoEnd, type ) { + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); } - this.each(function() { - var timers = jQuery.timers, - i = timers.length; + return this.each(function() { + var i, + hadTimers = false, + timers = jQuery.timers, + data = jQuery._data( this ); // clear marker counters if we know they won't be if ( !gotoEnd ) { jQuery._unmark( true, this ); } - while ( i-- ) { - if ( timers[ i ].elem === this ) { + + function stopQueue( elem, data, i ) { + var runner = data[ i ]; + jQuery.removeData( elem, i, true ); + runner.stop( gotoEnd ); + } + + if ( type == null ) { + for ( i in data ) { + if ( data[ i ].stop && i.indexOf(".run") === i.length - 4 ) { + stopQueue( this, data, i ); + } + } + } else if ( data[ i = type + ".run" ] && data[ i ].stop ){ + stopQueue( this, data, i ); + } + + for ( i = timers.length; i--; ) { + if ( timers[ i ].elem === this && (type == null || timers[ i ].queue === type) ) { if ( gotoEnd ) { + // force the next step to be the last timers[ i ]( true ); } else { timers[ i ].saveState(); } - + hadTimers = true; timers.splice( i, 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 ) ) { + jQuery.dequeue( this, type ); + } }); - - // start the next in the queue if the last step wasn't forced - if ( !gotoEnd ) { - this.dequeue(); - } - - return this; } }); @@ -331,15 +352,21 @@ jQuery.extend({ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + // if undefined, set to fx + if ( opt.queue == null ) { + opt.queue = "fx"; + } + // Queueing opt.old = opt.complete; + opt.complete = function( noUnmark ) { if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } - if ( opt.queue !== false ) { - jQuery.dequeue( this, opt.queue || "fx" ); + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); } else if ( noUnmark !== false ) { jQuery._unmark( this ); } @@ -408,6 +435,7 @@ jQuery.fx.prototype = { return self.step( gotoEnd ); } + t.queue = this.options.queue; t.elem = this.elem; t.saveState = function() { if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { diff --git a/src/queue.js b/src/queue.js index 0c678064e..b2ee8de08 100644 --- a/src/queue.js +++ b/src/queue.js @@ -70,7 +70,8 @@ jQuery.extend({ type = type || "fx"; var queue = jQuery.queue( elem, type ), - fn = queue.shift(); + fn = queue.shift(), + runner = {}; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -81,16 +82,17 @@ jQuery.extend({ // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { - queue.unshift("inprogress"); + queue.unshift( "inprogress" ); } - fn.call(elem, function() { + jQuery._data( elem, type + ".run", runner ); + fn.call( elem, function() { jQuery.dequeue( elem, type ); - }); + }, runner ); } if ( !queue.length ) { - jQuery.removeData( elem, type + "queue", true ); + jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } } @@ -125,11 +127,11 @@ jQuery.fn.extend({ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; - return this.queue( type, function() { - var elem = this; - setTimeout(function() { - jQuery.dequeue( elem, type ); - }, time ); + return this.queue( type, function( next, runner ) { + var timeout = setTimeout( next, time ); + runner.stop = function() { + clearTimeout( timeout ); + }; }); }, clearQueue: function( type ) { diff --git a/test/unit/effects.js b/test/unit/effects.js index d13bd587c..da1dd0a62 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -357,9 +357,26 @@ test("animate option (queue === false)", function () { }); */ +asyncTest( "animate option { queue: false }", function() { + expect( 2 ); + var foo = jQuery( "#foo" ); + + foo.animate({ + fontSize: "2em" + }, { + queue: false, + duration: 10, + complete: function() { + ok( true, "Animation Completed" ); + start(); + } + }); + + equals( foo.queue().length, 0, "Queue is empty" ); +}); + asyncTest( "animate option { queue: 'name' }", function() { expect( 5 ); - var foo = jQuery( "#foo" ), origWidth = foo.width(), order = []; @@ -608,6 +625,41 @@ test("stop(clearQueue, gotoEnd)", function() { }, 100); }); +asyncTest( "stop( ..., ..., queue ) - Stop single queues", function() { + expect( 3 ); + var foo = jQuery( "#foo" ), + saved; + + foo.width( 200 ).height( 200 ); + foo.animate({ + width: 400 + },{ + duration: 1000, + complete: function() { + equals( foo.width(), 400, "Animation completed for standard queue" ); + equals( foo.height(), saved, "Height was not changed after the second stop") + start(); + } + }); + + foo.animate({ + height: 400 + },{ + duration: 1000, + queue: "height" + }).dequeue( "height" ).stop( false, true, "height" ); + + equals( foo.height(), 400, "Height was stopped with gotoEnd" ); + + foo.animate({ + height: 200 + },{ + duration: 1000, + queue: "height" + }).dequeue( "height" ).stop( false, false, "height" ); + saved = foo.height(); +}) + test("toggle()", function() { expect(6); var x = jQuery("#foo"); diff --git a/test/unit/queue.js b/test/unit/queue.js index b5c058caa..95bbfc97e 100644 --- a/test/unit/queue.js +++ b/test/unit/queue.js @@ -130,6 +130,44 @@ test("delay()", function() { equals( run, 0, "The delay delayed the next function from running." ); }); +test("delay() can be stopped", function() { + expect( 3 ); + stop(); + + var foo = jQuery({}), run = 0; + + foo + .queue( "alternate", function( next ) { + run++; + ok( true, "This first function was dequeued" ); + next(); + }) + .delay( 100, "alternate" ) + .queue( "alternate", function() { + run++; + ok( true, "The function was dequeued immediately, the delay was stopped" ); + }) + .dequeue( "alternate" ) + + // stop( false ) will NOT clear the queue, so it should automatically dequeue the next + .stop( false, false, "alternate" ) + + // this test + .delay( 100 ) + .queue(function() { + run++; + ok( false, "This queue should never run" ); + }) + + // stop( clearQueue ) should clear the queue + .stop( true, false ); + + equal( run, 2, "Queue ran the proper functions" ); + + setTimeout( start, 200 ); +}); + + test("clearQueue(name) clears the queue", function() { expect(2);