stopping dynamically generated setInterval - javascript

I am generating multiple charts each with their own setInterval to refresh the data. I have it set to clearInterval when the dynamically generated container is removed - but if I reload and it has the same id the old setInterval continues to run. Is there a way to set a dynamically named setInterval that can be stopped when the replacement is generated?
Right now I'm using:
function generateChart(data, location){
var chart = new Highcharts.Chart({
// blah blah blah
}, function(chart){
setInterval(function(){
if($('#'+location).length){
// I'm doing stuff every minute
}else{
clearInterval();
}
},60000);
});
}
What happens is, the location is a randomly generated string that becomes the element ID for the container for the Highchart and if they user saves the chart it becomes the unique identifier. If the user updates the chart that's saved and reloads the chart, the old one gets .removed() and the new one generated in its place. Now the new one has the same element ID as the old one and since the old interval finds the container it wants it attempts to continue updating - which is can't since its chart went poof.
is there a way to set a dynamic variable I can use for setInterval so that I can clearInterval on it?
var blob+location = setInterval(function(){ ...
and then
clearInterval(blob+location);

You can just use an object:
var myObj = {};
var location = "somevalue";
myObj[location] = setInterval(...
clearInterval(myObj[location]);

ok - since I couldn't seem to wrap my head around some of your answers I decided to go low tech.
function genToken(){
var num = Math.floor(Math.random() * 10000);
var token = 't-' + num;
return token;
}
function genLocation(){
var chartToken = genToken();
var newChart = '<div id="'+location+'" data-token="'+chartToken+'"></div>';
$('#chartHome').append(newChart);
}
// inside my chart function
var token = $('#'+location).data('token');
setInterval(function(){
if( $('[data-token="'+token+'"]').length ){
// still there - keep going
}else{
// all gone - time to stop
clearInterval();
}
},60000);
now when I do:
$('#'+location).remove();
the token also vanishes and won't be the same if I generate a new chart with the same location id.

Stop using setInterval, use setTimeout instead (How do I execute a piece of code no more than every X minutes?):
function generateChart(data, location) {
var element = $('#'+location);
var chart = new Highcharts.Chart({
// blah blah blah
}, foo);
var foo = function() {
if(element){
// I'm doing stuff every minute
setTimeout(foo, 6000);
}
};
}
To stop it, just avoid the setTimeout or make element = null.
Maybe my code is a little bit wrong (I'm getting sleep right now), but the thing is to use setTimeout and closures.
If inside foo, something longs more than 6 seconds you will be in troubles since setTimeinterval will call it again, please watch http://www.youtube.com/watch?feature=player_detailpage&v=i_qE1iAmjFg#t=462s , so, this way you ensure that this will run 6 seconds after the last completed stuff.
I'll let this example here to posterity:
http://jsfiddle.net/coma/vECyv/2/
var closure = function(id) {
var n = 0;
var go = true;
$('#' + id).one('click', function(event) {
go = false;
});
var foo = function() {
if(go) {
console.log(id, n++);
setTimeout(foo, 1000);
}
};
foo();
};
closure('a');
closure('b');

Not sure if anyone is still looking for this solution but I ran into this problem and chose the following approach.
For anyone dynamically creating private/anonymous intervals that need to be stopped based on some event. You can simply save the interval in a variable, then transfer that variable into a data property in your html element.
// Outer scope
let pos = 1
let interval = setInterval(() => {
if (pos < 700) {
pos++;
}
htmlEl.style.top = pos + "px";
});
htmlEl.setAttribute("data-interval", interval)
This will save the numeric identifier of your interval, providing that html element is somewhere in your DOM.
Then, later you can simply extract this data attribute and use it to cancel an interval.
let intervalId = document.querySelector("#someElement").dataset.interval;
clearInterval(intervalId);

Related

How can I loop jQuery code?

I've got a jQuery code which is supposed to change images after some amount of time and it works well, but it obviously stops as soon as the code ends. How can I make it run over and over again? I tried using javascript "if" loop but it didn't do anything.. or maybe I did it wrong?
(w4s and w5s are img's IDs)
Also I'm quite new to jQuery so if you have any comments about any errors I've made, I'd be glad to hear them!
Here's the code
$(function () {
$("#w4s").hide();
$("#w5s").hide();
$(document).ready(function () {
$(function () {
$("#w3s").delay("4000").fadeOut("slow", function () {
$("#w4s").fadeIn("slow",function () {
$(this).delay("4000").fadeOut("slow",function () {
$("#w5s").fadeIn("slow");
});
});
});
});
});
});
I guess you need something like this
window.setInterval(function() {
alert('I happen every 8 seconds');
}, 8000);
First of all:
$(document).ready(function() {...
is equivalent to
$(function() {...
so keep the latter and drop the usage of the former.
Second, understand what this invocation actually does: it tells jQuery to fire the callback (function() {...) once the DOM's ready. Therefore, you generally only need a single invocation of this pattern for all your code (unless you want different scopes, that is).
So, start your code like this in the outer most scope:
<script type="text/javascript">
$(function() {
// Your code goes here !!!
});
</script>
Now, since we've covered the basics, let's take care of your problem.
$(function(){
var looplength = 8000;
// You can combine selectors!!!
$("#w4s, #w5s").hide();
// Let's drop all these nested `domready` callbacks and
// in their stead set up an interval
window.setInterval(function() {
$("#w3s").delay("4000").fadeOut("slow", function(){
$("#w4s").fadeIn("slow",function(){
$(this).delay("4000").fadeOut("slow",function(){
$("#w5s").fadeIn("slow");
});
});
});
}, looplength);
});
I would use a timeout for this - its probably just a personal preference, but I find them much more efficient than intervals, and it gives me more control over the continuation of the loop.
Something like this would do it:
//store the timer in your outer scope
var timer = null;
//create an empty elements var in your outer scope for use later
var elements;
//create a loop function do do most of your work for you
function loop(duration, index){
//set the defaults if none are passed in
duration = typeof duration == "number" ? duration : 8000;
index = typeof index == "number" ? index : 0;
//your function made a bit more generic
//by selecting from the store elements list
$(elements[index]).fadeOut("slow", function(){
//Increase the index by 1 to select the next element,
//or reset to 0 if it is greater than the number of elements you have
index = index + 1 < elements.length ? index + 1 : 0;
$(elements[index]).fadeIn("slow");
});
//clear the timeout in case it hasn't been called already
clearTimeout(timer);
//set the timeout function to call this function again
// passing it back the index and duration
timer = setTimeout(function() {
loop(duration, index)
}, duration);
};
//Instantiate the loop function for the first time on document.ready
//It should continue on its own after that
$(document).ready(function() {
//set the elements after the DOM is loaded
elements = $("#w3s, #w4s, #w5s");
loop(4000);
});
Hope this helps. It a fairly robust approach, so you could reuse this function elsewhere as well. If you need to cancel the loop at any point, you have it stored as timer so you can just call clearTimeout('timer') so long as you are in the same scope.
Fiddle available here - https://jsfiddle.net/heuw8dt0/2/
EDIT:
Moved element selection inside the DOM ready function

Understanding JavaScript setTimeout and setInterval

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.

Send setTimeout name, element id, and time as parameters of a function?

This is probably something really obvious, but I've searched around and tried a few things, and can't get it to work, so maybe someone can point out my error here.
I have a setTimeout that I will end up using over and over (and I know there is the setinterval, but I actually need to control when the timer starts and stops, and whether it starts again each time). Anyway, I figured if I'm writing it over and over, I should be able to use a function and pass it the parameters needed.
if ($('#selectRole').val() === 'Dispatch') {
//show Add Notes button
var funcAddNotesTimer = function(timerName,buttonName, timeToHide) {
console.log(timerName);
console.log(timeToHide / 1000);
timerName = setTimeout(function() {
$('buttonName').show();
}, timeToHide);
};
funcAddNotesTimer('addNotesTimer', '#disAddNotes', 30000);
I'm trying to set the timer function name to 'addNotesTimer', and when the timer is up I want to show the button with id #disAddNotes, and I want the timer to run for 30000 msec.
To me, what I have looks right, but I never get anything in my console log, so I don't think it's even getting into the function.
What am I doing wrong here?
I dont think its possible to use a string argument as the name of setTimeOut
Heres how you could approach it
// var timer = null; // dont really need that
var funcAddNotesTimer = function(buttonName, timeToHide) {
var timerName = setTimeout(function() {
//$('buttonName').show();
$(buttonName).show(); // buttonName is already a string so no need to add quotes around it.
}, timeToHide);
return timerName;
};
if ($('#selectRole').val() === 'Dispatch') {
var timer = funcAddNotesTimer('#disAddNotes', 30000);
// do something with timer
}
when you don't see any output in the console the reason must be something else(e.g. there is a bracket missing at the end of the code)
To set a variable with a dynamic name use the subscript-notation:
window[timerName] = setTimeout(/**/);
it will set a global variable named addNotesTimer.
As you currently do it you're simply overwriting the argument passed to the function.
Summary:
if ($('#selectRole').val() === 'Dispatch') {
//show Add Notes button
var funcAddNotesTimer = function(timerName,buttonName, timeToHide) {
console.log(timerName);
console.log(timeToHide / 1000);
window[timerName] = setTimeout(function() {
$(buttonName).show();
}, timeToHide);
};
funcAddNotesTimer('addNotesTimer', '#disAddNotes', 5000);
}

Self-created fadeIn() function not working correctly

I am trying to create the fadeIn() function using Javascript. I am having trouble, when I click the fadeIn button, it does not perform a fadeIn animation, instead I have to click it several times to fadeIn. Would anyone know how I can fix this issue?
jsFiddle
// Created a jQuery like reference
function $(selector) {
if (!(this instanceof $)) return new $(selector); // if new object is not defined, return new object
this.selector = selector; // setting selector attribute
this.node = document.querySelector(this.selector); // finds single element from the DOM
};
var fInFrom = 0, fOutFrom = 10;
$.prototype.fadeIn = function() {
var target = this.node,
newSetting = fInFrom / 10;
// Set Default styles for opacity
target.style.display = 'block';
target.style.opacity = newSetting;
// fadeInFrom will increment by 1
fInFrom++;
var loopTimer = setTimeout('this.fadeIn', 50);
if (fInFrom === 10) {
target.style.opacity = 1;
clearTimeout(loopTimer);
fInFrom = 0;
return false;
}
return this;
}
$('#fadeIn').node.addEventListener('click', function() {
$('#box').fadeIn();
});
This line is your problem:
setTimeout('this.fadeIn', 50)
That will set a timeout to evaluate the expression this.fadeIn in the global scope in approximately 50 milliseconds from the current time. There's two problems with that:
It's in the global scope; this is window, not an instance of $, so this.fadeIn is undefined.
Even if it were resolved correctly, you're only evaluating this.fadeIn; you're not calling it. You would need to use this.fadeIn() for it to do anything. (If you do that with the current code, this will reveal your first problem.)
To solve this, pass not a string but a function that does what you want it to do. You might naïvely do this:
setTimeout(function() {
this.fadeIn();
}, 50);
Unfortunately, while we now have lexical scoping for variables, this in JavaScript is dynamic; we have to work around that. Since we do have lexical scoping for variables, we can utilize that: [try it]
var me = this; // store the current value of this in a variable
var loopTimer = setTimeout(function() {
me.fadeIn();
}, 50);
After that's solved, you might want to look into:
Not using global variables to hold the fade state. Even after that fix, running two fade animations at once on different elements won't work as expected. (Try it.)
Only setting the timeout if you need to; right now, you always set it and then clear it if you don't need it. You might want to only set it if you need it in the first place.

Generate random number every x seconds and display it

Inside my App I have a sort of bottom bar Always shown, where I have a div with an h1, and a button. Everytime I click the button I coded and animation that do change the text of the h1 with a random number. Till here everything works fine.
My goal is to make this thing WITHOUT pressing a button, but just every x seconds during all the App execution. I tried to use "setInterval(func, ms);" I didn't get any error, but it just did it one time.
I think that my error is "where to put" the code. I don't understand this. So, I tried to put the code on the event handler of my button just to see if it works, but it did it one time anyway. I need that this function continues to be executed while the users do something else... It's like a clock inside an app: it should work continuosly while you do something else.
My function code:
function fraseCasuale(sel) {
var i = 1 + Math.floor(Math.random() * (groupedItems.length-1));
var oggetto = groupedItems.getAt(i);
if (sel === 1) {
document.getElementById("frasecasuale").textContent = oggetto.frase;
document.getElementById("autore").textContent = oggetto.nome;
document.getElementById("genere").textContent = oggetto.genere;
} else {
document.getElementById("frasecasuale2").textContent = oggetto.frase;
document.getElementById("autore2").textContent = oggetto.nome;
document.getElementById("genere2").textContent = oggetto.genere;
}
}
And then how I call it, for example:
setInterval(fraseCasuale(1), 5000);
My application is based on the grid template of Visual Studio 2012 for Windows Store aps. I added the bar I was talking above into "default.html", which is the container of the other pages.
Are you sure something like this doesn't work?
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var ms = 2000;
var func = function () {
var randNum = getRandomInt(1, 253); // Gets random number between 1 and 253
document.getElementById("element_id").innerHTML = randNum;
};
setInterval(func, ms);
http://jsfiddle.net/FQSAH/1/
The problem in your code is that you aren't passing a function to setInterval, you are passing the ONE TIME result of the function call fraseCasuale(1), so the result of that function is what is being passed to setInterval.
Since you want to pass a parameter to it, setTimeout lets you specify arguments after the function pointer and timeout interval, such as:
setInterval(fraseCasuale,5000,1);
If you are doing this in a defined 'Page' you'd do something like this in home.js
WinJS.UI.Pages.define("/pages/home/home.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
setInterval(this.fraseCasuale, 1000, 656);
},
fraseCasuale: function (sel) {
console.log(sel);
var i = 1 + Math.floor(Math.random() * (4 - 1));
var element = document.getElementById("updateMe");
element.textContent = i;
}
});
or if just using a default.js then you can throw your code there in app.ready, right above for ex. app.oncheckpoint (order doesn't actually matter above or below any function, just providing a place to show you an example)
So in default.js:
app.onready = function (args) {
//1 is a placeholder for your parameter
setInterval(fraseCasuale,5000,1);
};
This should work...
var id = window.setInterval(function(){randomNumber()},1000);
function randomNumber()
{
var rand = Math.floor(Math.random()*6)
//Do whatever you want with that number
}
Quick JSBin : http://jsbin.com/egajog/1/

Categories

Resources