views:

83

answers:

1

I'm creating a content rotator in jQuery. 5 items total. Item 1 fades in, pauses 10 seconds, fades out, then item 2 fades in. Repeat.

Simple enough. Using setTimeout I can call a set of functions that create a loop and will repeat the process indefinitely.

I now want to add the ability to interrupt this rotator at any time by clicking on a navigation element to jump directly to one of the content items.

I originally started going down the path of pinging a variable constantly (say every half second) that would check to see if a navigation element was clicked and, if so, abandon the loop, then restart the loop based on the item that was clicked.

The challenge I ran into was how to actually ping a variable via a timer. The solution is to dive into JavaScript closures...which are a little over my head but definitely something I need to delve into more.

However, in the process of that, I came up with an alternative option that actually seems to be better performance-wise (theoretically, at least). I have a sample running here:

http://jsbin.com/uxupi/14

(It's using console.log so have fireBug running)

Sample script:

$(document).ready(function(){

var loopCount = 0;

$('p#hello').click(function(){
  loopCount++;
  doThatThing(loopCount);
})

function doThatOtherThing(currentLoopCount) {  
console.log('doThatOtherThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
  setTimeout(function(){doThatThing(currentLoopCount)},5000)
 }
}  


function doThatThing(currentLoopCount) {
 console.log('doThatThing-'+currentLoopCount);
 if(currentLoopCount==loopCount){
   setTimeout(function(){doThatOtherThing(currentLoopCount)},5000);
 }
}

}) 

The logic being that every click of the trigger element will kick off the loop passing into itself a variable equal to the current value of the global variable. That variable gets passed back and forth between the functions in the loop.

Each click of the trigger also increments the global variable so that subsequent calls of the loop have a unique local variable.

Then, within the loop, before the next step of each loop is called, it checks to see if the variable it has still matches the global variable. If not, it knows that a new loop has already been activated so it just ends the existing loop.

Thoughts on this? Valid solution? Better options? Caveats? Dangers?

UPDATE:

I'm using John's suggestion below via the clearTimeout option.

However, I can't quite get it to work. The logic is as such:

var slideNumber = 0; 

var timeout = null;


function startLoop(slideNumber) {
    //... code is here to do stuff here to set up the slide based on slideNumber...
    slideFadeIn()
}

function continueCheck() {
    if (timeout != null) {
        // cancel the scheduled task.
        clearTimeout(timeout);
        timeout = null;
        return false;
    } else {
        return true;
    }
};

function slideFadeIn() {
    if (continueCheck){
        // a new loop hasn't been called yet so proceed...

        $mySlide.fadeIn(fade, function() {
            timeout = setTimeout(slideFadeOut,display);
        }); 
    }       
};


function slideFadeOut() {
    if (continueCheck){
        // a new loop hasn't been called yet so proceed...

        slideNumber=slideNumber+1;

        $mySlide.fadeOut(fade, function() {
            //... code is here to check if I'm on the last slide and reset to #1...
            timeout = setTimeout(function(){startLoop(slideNumber)},100);
        });

     }   
};


startLoop(slideNumber);

The above kicks of the looping.

I then have navigation items that, when clicked, I want the above loop to stop, then restart with a new beginning slide:

$(myNav).click(function(){
    clearTimeout(timeout);
    timeout = null;
    startLoop(thisItem);
})

If I comment out 'startLoop...' from the click event, it, indeed, stops the initial loop. However, if I leave that last line in, it doesn't actually stop the initial loop. Why? What happens is that both loops seem to run in parallel for a period.

So, when I click my navigation, clearTimeout is called, which clears it.

+4  A: 

What you should do is save the handle returned by setTimeout and clear it with clearTimeout to interrupt the rotator.

var timeout = null;

function doThatThing() {
    /* Do that thing. */

    // Schedule next call.
    timeout = setTimeout(doThatOtherThing, 5000);
}

function doThatOtherThing() {
    /* Do that other thing. */

    // Schedule next call.
    timeout = setTimeout(doThatThing, 5000);
}

function interruptThings() {
    if (timeout != null) {
        // Never mind, cancel the scheduled task.
        clearTimeout(timeout);
        timeout = null;
    }
}

When a navigation element is clicked simply call interruptThings(). The nice part is that it will take effect immediately and you don't need to do any polling or anything else complicated.

John Kugelman
As usual with my coding skills, there's a MUCH simpler way to go about it. Brilliant answer, John. Thanks for this!
DA
OK, I ran into a hiccup. I'm updating the above to reflect that.
DA
I've updated my post with an additional question. The issue. I believe, is some sort of conflict with me resetting the timeout variable before the existing loop has a chance to see if it was null.
DA
Get rid of the "continueCheck" idea. The fade in and fade out functions shouldn't have any logic about detecting when they've been cancelled. If they're cancelled they simply will not be called in the first place. It should work if you delete that function entirely.
John Kugelman
John...thanks. Still having issues though. With some more console.log()ing I've found that it's just not stopping the initial loop. So it compounds them on each click of my navigation items. If I click on 3 items, I end up with 3 loops running at once. Is there an issue with me clearing time out in the same click event that I restart the loop from?
DA
One thought...I'm using a mix of setTimeout calls (for 'pausing') and jQuery callbacks. So there are times when a setTimeout is done executing and now it's waiting on a jQuery event at which point I could clearTimeout, but then I immediate reset it. So the function that was waiting on jQuery now ALSO resets the timeout variable and continues on what it was doing.
DA