Javascript object to execute delayed loops - javascript

I want to have multiple numbers on my web page "spin up" as the page loads, giving an impression like a fruit machine.
This involves a simple function with a delayed loop. The way to do this seems to be to use setTimeout recursively. This works fine for just one number on the page.
However for multiple numbers spinning at the same time, each needs its own spinner object. I used prototypes like this:
var Spinner = function(id){
this.element = $('#' + id);
this.target_value = this.element.text()
this.initial_value = this.target_value - 30;
};
Spinner.prototype.spinUp = function() {
loop(this.element);
function loop(element) {
element.html(this.initial_value += 1);
if (this.initial_value == this.target_value) {
return;
};
clr = setTimeout(loop(element), 30); // 30 millisecond delay
};
};
var Spinner1 = new Spinner('number')
Spinner1.spinUp();
However putting a recursive function inside the prototype method causes a big crash. Can you see a way around this?
Many thanks!
Derek.

A couple of issues:
loop() is not how you pass a function, it's how you invoke a function.
You are not calling the function as a method of the object
Try this:
Spinner.prototype.spinUp = function() {
var loop = function() {
this.element.html(this.initial_value += 1);
if (this.initial_value == this.target_value) {
return;
};
setTimeout(loop, 30); // 30 millisecond delay
}.bind(this); //Just flat out bind the function to this instance so we don't need to worry about it
loop();
};
Demo http://jsfiddle.net/KAZpJ/

When you say:
clr = setTimeout(loop(element), 30);
you are "calling" the function (then and there), and passing the value it returns as the first parameter to setTimeout(..).
You would want an anonymous function doing that job:
setTimeout(function(){loop(element);}, 30);

Related

Array.pop() Isn't killing my setInterval

I've written some code that when you click a button it adds an instance of a function to an array,
var objects = [];
$(document).on("click", ".addButton", function(){
objects.push(new newObject(1));
});
function newObject(amount){
setInterval(function(){
addValue(amount);
}, 1000);
}
So then every second each new object created keeps running the addValue function every second adding the amount.
The problem is when I try and destroy that function with objects.pop() it deletes the object but the setInterval doesn't stop running.
How do I make it destroy everything in that function and stop it from running?
There is nothing quite like that in JS for setInterval. I would suggesting declaring a method to handle clean up.
// "Class" declaration
function newObject(amount) {
var id = setInterval(function() {
addValue(amount);
}, 1000);
this.kill = function() {
clearInterval(id);
}
}
// "Public" api for the data structure
var objects = [];
function addNewObject() {
objects.push(new newObject(1));
}
function destroyLastObject() {
objects.pop().kill();
}
// Event bindings
$(document).on("click", ".addButton", addNewObject);
$(document).on("click", ".removeButton", destroyLastObject);
Completely untested, but along these lines should work.
EDIT
This, imo, is a great resource for learning about different patterns within javascript - long but well well worth the read: https://addyosmani.com/resources/essentialjsdesignpatterns/book/
You got to find something to check against to clear the interval. I am clearing based on array length. It only executes once.
// you got to find something to check against to clear the interval
var objects = [];
document.addEventListener("click", function(){
console.log('click');
objects.push(new newObject(1));
});
function newObject(amount){
var interval= setInterval(function(){
if(objects.length !==0){
clearInterval(interval);
}
}, 1000);
}

Replace JavaScript variable from outside of function

I'm not sure if what i am trying to do is possible, or if there's an easier way to do what I'm trying to do.
I have the following code:
<script>
function TitleSwitch() {
var counter = 0,
fn = function () {
var array = ['Value1','Value2','Value3'];
$(document).prop('title', array[counter]);
counter++;
counter %= array.length;
};
fn();
return fn;
}
setInterval(TitleSwitch(), 5000);
</script>
It rotates the page title between the three variables, Value1, Value2, and Value3 every 5 seconds. This is working fine.
However, on the same page there is some ajax script that is polling for other information related to the app.
What I am trying to do is use some of the data returned from the polling script to change the values in the title switching function.
So, as an example, the poll data may return Value4, Value5, and Value6 instead.
So in the code above, is there any way to replace the values in
var array = ['Value1','Value2','Value3'];
from another function, outside of the title switching function?
So, say I have a function called pollingDone() that is called each time the polling data is returned, how can I change the values of "array" in TitleSwitch() from within pollingDone() after TitleSwitch() is already running using setInterval?
basically, what I was trying to do is keep TitleSwitch running, but just replace the values used.
The reason I was trying to do it this way is because the titles are switched between the three values every 5 seconds, however the polling script runs every 10 seconds. So if I started the TitleSwitch() function over each time the polling script completes, the third value would never be shown in the title. The first two would show, the polling script would run, and then the titles would start over. So I was hoping to keep the TitleSwitch() function running as-is, and just replace the values it is using.
You can do that by exposing the array in the fn function to the outside context.
Here is an example:
function TitleSwitch() {
var counter = 0;
this.array = ['Value1','Value2','Value3'];
var self = this;
this.fn = function () {
$(document).prop('title', self.array[counter]);
console.log(self.array[counter]);
counter++;
counter %= self.array.length;
};
this.fn();
}
var switcher = new TitleSwitch()
setInterval(switcher.fn, 500);
function asyncFn(){
switcher.array[0] = "changed title1";
}
setTimeout(asyncFn, 1000)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Pass it in the constructor so you can control the access level from outside.
In the example:
myArray is defined outside the closure that TitleSwitch creates.
When editing its values, the next iteration will use the updated contents.
Like so:
function TitleSwitch(array) {
var counter = -1,
fn = function () {
counter++;
counter %= array.length;
// Move to bottom to prevent errors when using a shorter array
console.log(array[counter]);
};
fn();
return fn;
}
var myArray = ['Value1','Value2','Value3'];
setInterval(TitleSwitch(myArray), 1000);
myArray[1] = "TEST";
myArray[2] = "TEST2";
I think you will have to get your variable out of your function scope, something like this:
var titles = ['Value1', 'Value2', 'Value3'];
function TitleSwitch() {
var counter = 0,
fn = function () {
$(document).prop('title', titles[counter]);
counter++;
counter %= titles.length;
};
fn();
return fn;
}
setInterval(TitleSwitch(), 5000);
// Here, you can modify your titles in an ajax call
There is no way to replace array that is defined as a local variable inside fn. If you pull it out to outside of TitleSwitch, you can just give it a new value. Alternately, you can use a property on fn, or construct a more complex object, to avoid polluting the environment.
You also want to raise the modulo line to the start of fn: e.g. if you have a 5-element list with counter being 4 and you replace array with a 2-element list, your code would break.
var array = ['Value1','Value2','Value3'];
function TitleSwitch() {
var counter = 0,
fn = function () {
$(document).prop('title', array[counter]);
console.log(array[counter]);
counter++;
counter %= array.length;
};
fn();
return fn;
}
setInterval(TitleSwitch(), 5000);
function pollingDoneCallback(data){
if(data){
array=[];
for(var i=0;i<data.length;i++)
array.push(data[i]);
}
}
pollingDoneCallback(['val5','val6']);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Class variables in JavaScript and setInterval

Since I need to pass an anonymous function to setInterval if I want parameters, I tried using the below code. Originally I had it calling this.countUp, but as that returned NaN I did some reading and found the .call(this) solution on SO. However, when I combined that with the anonymous function (which I admit I'm a bit foggy on), I'm now getting TypeError: this.countUp is undefined in Firebug.
I suppose I don't need to make count accessible, nor the playBeep method, but let's pretend I wanted to so that I can understand what I'm doing wrong with this code.
function workout() {
var beep = new Audio("beep1.wav");
this.timerWorkout; //three timers in object scope so I can clear later from a different method
this.timerCounter;
this.timerCoolDown;
this.count = 0;
this.startWorkout = function() {
alert(this.count);
this.timerWorkout = setTimeout(this.playBeep, 30 * 1000); //workout beep - 30 seconds
this.timerCounter = setInterval(function() {this.countUp.call(this)}, 1000); //on screen timer - every second
}
this.startCoolDown = function() {
this.timerCoolDown = setTimeout(this.playBeep, 10 * 1000); //cooldown beep - 10 seconds
}
this.playBeep = function() {
beep.play(); //plays beep WAV
}
this.countUp = function() {
this.count++;
document.getElementById("counter").innerHTML = this.count;
}
}
var workout1 = new workout()
Inside startWorkout use bind(this) :
this.timerCounter = setInterval(function() {this.countUp()}.bind(this), 1000);
What happens is setInterval is changing the value of this inside the function you provide for it to call. You need to store this in a separate variable to prevent it from getting overridden.
function workout() {
var self = this;
// ...
this.startWorkout = function() {
alert(this.count);
this.timerWorkout = setTimeout(self.playBeep, 30 * 1000); // this method works
this.timerCounter = setInterval(function() {self.countUp}, 1000); // so does this one
}
}
The reason that the variable scope in js is limited on function. So when you are trying to use this inside a nested function, you get a link to another object. Create a variable var that = this; into a higher-level function, and then use it in any nested function that would refer you to the correct context.

setInterval exits after first iteration. Please help me rectify this snippet?

Can someone help me rectify the issue related to the setInterval? I'm fairly new to JavaScript, I'm not sure what's wrong here. I have this block in my page:
GlobalTicker.prototype.TickElements = function () {
this.timer = setInterval(this.initializeElement.apply(this) , 1000);
};
GlobalTicker.prototype.initializeElement = function () {
for (var i = 0; i < this.tickerElements.length; i++) {
var existingRun = this.tickerElements[i].secs;
var elementId = $('#' + this.tickerElements[i].id + ' .comment-editor').find('.ticker');
existingRun -= 1;
$(elementId).text(existingRun);
if (existingRun === 0) {
$(elementId).remove();
this.tickerElements.splice(i, 1);
if (this.tickerElements.length == 0) clearInterval(this.tickerElements.timer);
}
}
};
Then somewhere in the code, I have this call in a function
var objTicker = new GlobalTicker();
CommentManagement.prototype.createComment = function (domObject) {
objTicker.TickElements();
};
This function call actually invokes the setInterval function and runs the first iteration and jumps to the initialiseComment(); but once this block is executed, on the next interval, instead of executing the initialiseComment(); again, it jumps back to my function call CreateComment();. What am I doing wrong here?
setInterval() requires a function reference. You were calling the function itself and passing the return result from executing the function (which was undefined) to setInterval(). Since that return value is not a function, there was no callback function for setInterval() to call. Thus, your method was executed once when you first called it, but couldn't be called by the interval.
To fix it, you should change from this:
this.timer = setInterval(this.initializeElement.apply(this) , 1000);
to this:
var self = this;
this.timer = setInterval(function() {self.initializeElement()}, 1000);
Note, the value of this will also be different in the setInterval() callback than the value you want so the one you want is saved here in self so it can be referenced from that. There's also no need to use .apply() in this case because calling a method on an object will automatically set the this pointer as needed.

MVC: Telling the controller to stop

I'm experimenting with javascript and MVC models. I want to (simplified example) move an object across the screen a random number of pixels between 1 and 10 and then have it stop when it gets to, say, 400 pixels.
The view is set up to observe the model, which has a notifyObservers() function.
When the start button on the view is clicked it sends a startButtonClicked message to the controller.
controller.startButtonClicked = function () {
var animate = function () {
controller.getModel().shift(); // get the model and run the shift() function
setTimeout(animate, 20);
};
animate();
}
This runs the model's shift() function:
model.shift = function () {
if(model.x < 400) {
model.x += Math.floor(Math.random()*11); // Add up to 10 pixels
}
model.notifyObservers(); // Tells view to update,
};
This works fine, and the object stops at around 400 pixels as it should. However, the setTimeout loop in controller.startButtonClicked() is still whirring away.
[Edit: As I understand it, the traditional MVC model doesn't allow the model to communicate with the controller directly, so the model can't just tell the controller to stop the timer.]
So, finally to the question: How do I make the loop in the controller stop?
The possible solutions I've thought of:
Get the model to tell the view, which then tells the controller. But that seems very long-winded.
Get the controller to ask the model if it's done. But that seems to go against the MVC structure.
Get the shift() function to return false to the controller when it's done.
Anyone who's been doing MVC for a while know what the right way of doing it would be?
Thanks!
Something like this:
var t; // feel free to make this a non global variable
controller.startButtonClicked = function () {
var animate = function () {
controller.getModel().shift(); // get the model and run the shift() function
t = setTimeout(animate, 20);
};
animate();
}
model.shift = function () {
if(model.x < 400) {
model.x += Math.floor(Math.random()*11); // Add up to 10 pixels
}
else {
clearTimeout(t);
}
model.notifyObservers(); // Tells view to update,
};
You need to use clearTimeout(arg) where arg is the return value from a setTimeout call.
Also, be careful with low (< 50) values for setTimeout(), what you have coded calls animate 50 times per second.
controller.startButtonClicked = function () {
var animate = function () {
var m = controller.getModel();
m.shift(controller); // get the model and run the shift()
m.timerInterval = setTimeout(animate, 20);
};
animate();
}
model.shift = function () {
if(model.x < 400) {
model.x += Math.floor(Math.random()*11); // Add up to 10 pixels
}
else if (model.timerInterval)
{
clearTimeout(model.timerInterval);
}
model.notifyObservers(); // Tells view to update,
};

Categories