Restarting a setInterval() in Javascript/jQuery (without clearInterval) - javascript

I'm working on ui tabs built using jQuery. Everything works except for one issue - I did a setInterval that runs a function that does a trigger("click") so that it goes to the next tab after 5000 miliseconds. It runs through each tab fine, the issue is that if the user manually clicks on a tab, the timer for the setInterval does not restart back at 0. For example if a user were to start on tab1 at 0 miliseconds and clicks on tab2 at 2000 miliseconds, the setInterval doesn't go back to 0, it would start at 2000 and run to 5000 miliseconds and would subsequently goto tab3. I understand why it's happening, I just wonder if there were a way to restart the setInterval timing without having to do a clearInterval() and creating an entirely new setInterval(). Any insight would be appreciated.
Update
Thanks for the replies - The reason I was trying to avoid using clearInterval was because I was having issues of how to write the code in a way where the clearInterval would stop the setInterval completely. The code is setup to track whenever a user has clicked a tab. The problem is the auto change function utilizes trigger('click'), so it runs the clearInterval function I wrote also when the tabs auto-change. It seems to run fairly fine on its own, but once the user starts clicking on tabs, the setInterval behaves unusually and switches tabs unpredictably. I suspect what is happening is that several setIntervals are running at once... Here's the code (If you haven't guessed it already, I'm pretty new at javascript/jquery). I've commented out parts so that it's functional, but it still doesn't function as I intended (from first post).
// auto change tabs
if( options.interval ) {
function timerCom() {
if( !$(".controller").hasClass('paused') ) {
var i = $(".tab-current > a").attr("rel");
//alert(i);
if( i == 3 ) {i = 0};
$container
.find('a')
.eq(i)
.trigger('click');
}
}
//$("#promo-items > li > a").click(function () {
//var timer;
//if( timer != null ) {clearInterval(timer);}
timer = setInterval(timerCom, options.interval);
//});
}

No, there is no way to restart a timer set by setInterval without clearing the timer.

You can't really alter intervals or timeouts, only clear them. That said it should be a simple thing to create a function that clears the interval, and then starts a new but identical one immediately with a fresh time value.
var intervalID;
var resetTimer = function() {
if (intervalID) { clearInterval(intervalID) };
intervalID = setInterval(function() {
console.log('doing stuff!');
}, 5000);
};

timer = setInterval(function() {
timerCom();
}, options.interval);

I know this post is well over 2 years old, but I ran into a similar problem just now, and I found a solution.
I was writing an image scroller that would automatically shift to the next image after a set amount of time, and whenever I clicked the navigation buttons, the transitions moved double-time.
Here's my solution:
Make the interval variable (timer in your case) somewhat global.
i.e. in the options section (assuming it was defined earlier, and then later assigned), add a null timer variable.
var options = {
'interval',
//Other variables
'timer',
};
Then, call clearInterval twice when you handle the click event.
$("#promo-items > li > a").click(function () {
if( options.timer != null ) {
clearInterval(options.timer);
clearInterval(options.timer);
}
options.timer = setInterval(timerCom, options.interval);
});
Worked like a charm for me.
Again, sorry if this is wayyyy too late.

Related

Call function when <audio> is at specific time

I am currently doing some fun website, which requires audio cues (I assume that's the name?). I want the site to do something, when the song has been played for exactly X amount of time.
I can easily get the current time using element.currentTime, but I have no clue how to say: when element.currentTime == 5.2, runFunction() - If you know what I mean. Is there some kind of way this could be done? My current test code:
<----AUDIO WILL START PLAYING---->
http://jsfiddle.net/jfL4mcnh/
$("<audio id='audioElement'>").appendTo("body");
$("#audioElement").attr("src", "http://mp3ornot.com/songs/1B.mp3").attr("autoplay", "autoplay");
setInterval(function() {
//for some reason, $("#audioElement").currentTime won't work, so we're going old fashion
time = document.getElementById("audioElement").currentTime;
console.log(time);
}, 1000);
Also, I forgot to say this, I cannot do a setTimeout() and hit at the exact moment I want in milliseconds, because the audio can take some extra time to load, while the actual code runs exactly when it has been "seen", if you know what I mean. So no countdown. I need to be exact here.
If you need greater resolution than ontimeupdate provides, you can use a setInterval instead.
Live Demo (sound and alert box only!):
$("<audio id='audioElement'>").appendTo("body");
$("#audioElement").attr("src", "http://mp3ornot.com/songs/1B.mp3").attr("autoplay", "autoplay");
var triggered = false;
var ael = document.getElementById("audioElement");
var interval = setInterval(function(){
console.log(ael.currentTime);
if (!triggered && ael.currentTime >= 5.2) {
triggered = true;
alert("5.2 seconds reached");
}
if (ael.ended) clearInterval(interval);
}, 50);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
JSFiddle Version: http://jsfiddle.net/jfL4mcnh/15/
Well, I have done it myself.. It seems.
http://jsfiddle.net/jfL4mcnh/13/
$("#audioElement").bind("timeupdate", function() {
var currentTime = parseInt(this.currentTime, 10);
if(currentTime == 2) {
console.log("2 seconds in");
$(this).unbind("timeupdate");
}
});
You can bind timeupdate to it, then unbind it (apparently it runs the code 4 times, so I have to unbind it).
EDIT: Nope, it doesn't update fast enough to make it perfect on point. It increments each ~300ms it seems.
See this jsfiddle here
I've added the following line to the JavaScript setInterval() function:
if (time > 5.2) {
myFunction();
}
myFunction() does a console.log, which you'll see in the console.
The reason I used > rather than === is that the time reported is never precise due to fluctuations in processing. A Boolean in the condition would solve this problem:
triggered = false;
if (time > 5.2 && !triggered) {
triggered = true;
myFunction();
}

JavaScript setTimeout() duplicates

I'm fairly new to JavaScript/jQuery, but have made a script to change the background picture.
First Script
The first script version works fine and does the following:
creates a setInterval timer that calls function backgroundChange() to run every 7 seconds
decides the next picture URL
sets the background picture
This works great, but the problem is when the website is live, on a slow connection the background picture doesn't load in time for the next timer change.
New Script
So the new version:
creates a setTimeout timer that calls function backgroundChange() to run after 7 seconds
var theTimer = setTimeout(backgroundChange, 7000);
clearsTimeout (surely I shouldn't have to run this?)
window.clearTimeout(theTimer);
decides the next picture URL
waits until the picture is loaded:
then sets the background picture
then adds a new setTimeout timer
$('#testImage').attr('src', imageText).load(function()
{
$('#backgroundTop').fadeIn(timeIn,function()
{
theTimer = setTimeout(backgroundTimer, 7000);
});
});
The problem is that the timer now seems to be called double the amount of times whenever the timer runs and exists in the .load function.
I havent purposely not posted my code yet, as I want to make sure my understanding is correct first, rather than someone just fixing my code.
Ta very much.
Instead of unbinding, you could use a JavaScript closure for the timer function. This will maintain a single timer that is reset every time it is called.
var slideTimer = (function(){
var timer = 0;
// Because the inner function is bound to the slideTimer variable,
// it will remain in score and will allow the timer variable to be manipulated.
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
Then in your code:
$('#testImage').attr('src', imageText).load(function() {
$('#backgroundTop').fadeIn(timeIn,function()
{
slideTimer(backgroundTimer, 7000);
});
});
There should be no need to clear or set the timer anywhere else in your code.
You need to unbind the load handler before you add the next one, since they keep piling up as your code stands. With every iteration, you add an extra handler that does the exact same thing. Use unbind to remove the old handler before you reattach:
$('#testImage').unbind('load');

jQuery typing script

Simple piece of jQuery that adds the class .typing to the <html> tag while the user is currently typing. I've tried several different things that all haven't worked, not sure how else to do this.
$('html').keypress(function(){
$(this).addClass('typing');
if ( /* No key is pressed again in the next 0.5 seconds */ ) {
$('.typing').removeClass('typing');
}
});
Create a function which executes certain code after 0.5 seconds, using setTimeout (in conjunction with clearTimeout, to prevent multiple timeouts from be active at a time).
var timer; //Local variable to hold time-out reference
function refreshPress(){
clearTimeout(timer); //Prevent stacked time-outs, only one is allowed
timer = setTimeout(function(){
$('.typing').removeClass('typing');
}, 500); //0.5 seconds
}
$('html').keypress(function(){
$(this).addClass('typing'); //This line can also be merged with refreshPress
refreshPress(); // Call refreshPress
});
Use setTimeout() and clearTimeout() instead of the if.

Watching setTimeout loops so that only one is running at a time

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.
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.

How do I prevent the concurrent execution of a javascript function?

I am making a ticker similar to the "From the AP" one at The Huffington Post, using jQuery. The ticker rotates through a ul, either by user command (clicking an arrow) or by an auto-scroll.
Each list-item is display:none by default. It is revealed by the addition of a "showHeadline" class which is display:list-item. HTML for the UL Looks like this:
<ul class="news" id="news">
<li class="tickerTitle showHeadline">Test Entry</li>
<li class="tickerTitle">Test Entry2</li>
<li class="tickerTitle">Test Entry3</li>
</ul>
When the user clicks the right arrow, or the auto-scroll setTimeout goes off, it runs a tickForward() function:
function tickForward(){
var $active = $('#news li.showHeadline');
var $next = $active.next();
if($next.length==0) $next = $('#news li:first');
$active.stop(true, true);
$active.fadeOut('slow', function() {$active.removeClass('showHeadline');});
setTimeout(function(){$next.fadeIn('slow', function(){$next.addClass('showHeadline');})}, 1000);
if(isPaused == true){
}
else{
startScroll()
}
};
This is heavily inspired by Jon Raasch's A Simple jQuery Slideshow.
Basically, find what's visible, what should be visible next, make the visible thing fade and remove the class that marks it as visible, then fade in the next thing and add the class that makes it visible.
Now, everything is hunky-dory if the auto-scroll is running, kicking off tickForward() once every three seconds. But if the user clicks the arrow button repeatedly, it creates two negative conditions:
Rather than advance quickly through the list for just the number of clicks made, it continues scrolling at a faster-than-normal rate indefinitely.
It can produce a situation where two (or more) list items are given the .showHeadline class, so there's overlap on the list.
I can see these happening (especially #2) because the tickForward() function can run concurrently with itself, producing different sets of $active and $next.
So I think my question is:
What would be the best way to prevent concurrent execution of the tickForward() method?
Some things I have tried or considered:
Setting a Flag: When tickForward() runs, it sets an isRunning flag to true, and sets it back to false right before it ends. The logic for the event handler is set to only call tickForward() if isRunning is false. I tried a simple implementation of this, and isRunning never appeared to be changed.
The jQuery queue(): I think it would be useful to queue up the tickForward() commands, so if you clicked it five times quickly, it would still run as commanded but wouldn't run concurrently. However, in my cursory reading on the subject, it appears that a queue has to be attached to the object its queue applies to, and since my tickForward() method affects multiple lis, I don't know where I'd attach it.
You can't have concurrent executions of a function in javascript. You just have several calls waiting to execute in order on the pile of execution. So setting a flag when the function runs cannot work. When the event handler runs, it cannot run concurrently with a tickHandler() execution (javascript is threadless).
Now you have to define precisely what you want, because it doesn't appear in your question. What you happen when the user clicks, say, 3 times in rapid succession on the arrow? And how do the clicks interfere with the auto-scroll?
I'd say the easiest way would be to process a click only when the ticker is idle, so 3 clicks in a row will only tick once. And you make clicks replace auto-scroll and reset its timer. So I use a flag ticksInQueue that is raised when a tick is queue by a click and only lowered when the fadeIn has completed:
var ticksInQueue = 0,
timerId = setInterval(tickForward, 5000),
isPaused = false;
function tickForward() {
var $active = $('#news li.showHeadline');
var $next = $active.next();
if($next.length==0) $next = $('#news li:first');
$active.stop(true, true);
$active.fadeOut('slow', function() {
$active.removeClass('showHeadline');
});
setTimeout(function(){
$next.fadeIn('slow', function(){
$next.addClass('showHeadline');
if(ticksInQueue) ticksInQueue--; // only change
})}, 1000);
if(isPaused == true){
} else {
startScroll()
}
}
$('#arrow').click(function () {
if(ticksInQueue) return;
ticksInQueue++;
clearInterval(timerId);
timerId = setInterval(tickForward, 5000);
tickForward();
});
You can try a demo here : http://jsfiddle.net/mhaCF/
You can set up a bool variable and check it's value when the function executes. If it's already being run you can either wait for it to end or just skip it (don't know which is desirable)
var active = false;
function tickForward() {
if (!active) {
active = true;
// your function code goes here
// remember to change active to false once you're done
active = false;
} else {
// if you want to skip if active = true you don't need this else
// if you want to call your method later use something like this
setTimeout(tickForward, 1000);
}
}
This implementation using "new Date()" and "setTimeout" to avoid some race condition (concurrent execution) for some resource. That way each call to "NoConcurrExecFunc" is executed with at least 750 milliseconds between each one!
var last_call = new Date();
function NoConcurrExecFunc(param_a, param_b) {
var new_date = new Date();
var min_interval = 750;
if ((last_call - new_date) < 0) {
last_call = new Date(new_date.getTime() + min_interval);
// Do your stuff!
} else {
setTimeout(function () {
NoConcurrExecFunc(param_a, param_b);
}, min_interval);
}
}

Categories