How to pass timeout by reference? Or a better way to implement? - javascript

I had this code working previously, but I am not so sure now that I have separated my HTML controls from my jQueryUI Widget.
Currently, the timer starts correctly, but I lose my reference to _refreshTimeout after one tick. That is, after the first tick, unchecking my PlanViewRefreshCheckbox does not stop my timer from running.
I have two JavaScript files, PlanView.js and PlanViewCanvas.js.
PlanView.js looks something like this:
(function ($) {
var _minZoom = -2.0;
var _maxZoom = 2.0;
var _stepZoom = (_maxZoom - _minZoom) / 100;
var _refreshTimeout = null;
var _refreshInterval = 60000; //One minute
$(document).ready(function () {
//Initialize Refresh combo box.
$('#PlanViewRefreshCheckbox').click(function () {
if ($(this).is(':checked')) {
var planViewCanvas = $('#PlanViewCanvas');
//Binding forces the scope to stay as 'this' instead of the domWindow (which calls setTimeout).
_refreshTimeout = setTimeout(function(){planViewCanvas.PlanViewCanvas('refresh', _refreshInterval, _refreshTimeout)}.bind(planViewCanvas), _refreshInterval)
}
else {
clearTimeout(_refreshTimeout);
}
});
}
})(jQuery);
and PlanViewCanvas.js houses a jQueryUI Widget:
(function ($) {
$.widget("ui.PlanViewCanvas", {
//other properties and methods not-relevant to problem declared here.
refresh: function (refreshInterval, refreshTimeout) {
var self = this;
_stage.removeChildren();
self.initialize();
//Binding forces the scope to stay as 'this' instead of the domWindow (which calls setTimeout).
refreshTimeout = setTimeout(function () { self.refresh(refreshInterval, refreshTimeout) }.bind(self), refreshInterval);
},
}
})(jQuery);
Does it seem like I am going about things incorrectly?
EDIT: I think the answer is probably to use setInterval and not setTimeout.

The first problem is that you forgot the underscore
refreshTimeout should be _refreshTimeout
second, your variable needs to be global to be accessible in both files, so declare it outside of the function:
var _minZoom = -2.0;
var _maxZoom = 2.0;
var _stepZoom = (_maxZoom - _minZoom) / 100;
var _refreshTimeout = null;
var _refreshInterval = 60000; //One minute
(function ($) {
....
})(jQuery)

You can't pass values by reference. I see two options:
pass an Object. If you have it referenced from two variables, you can access its properties in both scopes.
split up you functionality in two functions, where it belongs: One masters the interval loop and triggers the refresh function, and the other does things to refresh. The refreshTimeout variable only belongs to the scope of the first one. point. You may add the interval function to you widget if it is often needed.

The answer was very 'oh derp.'
//Initialize Refresh combo box.
$('#PlanViewRefreshCheckbox').click(function () {
if (this.checked) {
_refreshTimeout = setInterval(function(){$('#PlanViewCanvas').PlanViewCanvas('refresh')}, _refreshInterval)
}
else {
clearTimeout(_refreshTimeout);
}
});

Related

How can I end a requestanimationFrame with a function? [duplicate]

I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});

Is there a way to cancel requestAnimationFrame without a global variable?

I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});

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.

Autoscroll with setInterval/clearInterval

Im relatively new to JS coding, and can't get this little number to work. Can anyone tell me what I'm doing wrong?
My JavaScript is:
incrementScroll = function() {
window.scrollBy(0, 3) ;
}
startScrollLoop = function() {
scrollLoopId = setInterval( "incrementScroll", 5) ;
}
stopScrollLoop = function() {
clearInterval( scrollLoopId ) ;
}
And my HTML is:
<button onclick="startScrollLoop()">AUTO SCROLL</button>
<button onclick="stopScrollLoop ()">STOP SCROLL</button>
Again, many thanks for help. New to all of this and need to make a project work by morning.
Cheers.
The first argument to setInterval() should be a function reference, or non-ideally, a string to be eval()'d, which would be a complete function call with (). So remove the quotes:
// Pass the reference to incrementScroll,
// not a quoted string containing its name.
scrollLoopId = setInterval(incrementScroll, 5);
And to clear the scroll, you will need to define scrollLoopId at a higher scope with var.
// Define outside the function so it is available
// in scope of the stopScrollLoop() function when needed...
var scrollLoopId;
var startScrollLoop = function() {
scrollLoopId = setInterval( incrementScroll, 5) ;
}
Jsfiddle demo
(uses a slower scroll speed to give you a chance to click the stop button in the middle of the text)
Note that it is good practice to use the var keyword with each of these. even though they would end up at window scope anyway (assuming they're not being defined inside another function).
// Define with var
var incrementScroll = function() {
window.scrollBy(0, 3);
}

Why does this setTimeout callback give me an error?

I am attempting to collapse a div on request, but my setTimeout function will does not successfully call its callback function. Here is the code below:
function newsFeed() {
this.delete = function(listingID) {
var listing = document.getElementById(listingID);
var currHeight = listing.offsetHeight;
var confirmDelete = confirm("Are you sure you'd like to delete this listing forever?");
if (confirmDelete) {
this.collapse(listingID,currHeight,currHeight,100);
}
}
this.collapse = function(listingID,orig_height,curr_height,opacity) {
var listing = document.getElementById(listingID);
var reduceBy = 10;
if(curr_height > reduceBy) {
curr_height = curr_height-reduceBy;
listing.style.overflow = "hidden";
listing.style.height = (curr_height-40) + "px";
if(opacity > 0) {
opacity = opacity - 10;
var opaque = (opacity / 100);
listing.style.opacity=opaque;
listing.style.MozOpacity=opaque;
listing.style.filter='alpha(opacity='+opacity+')';
}
setTimeout("this.collapse('"+listingID+"',"+orig_height+","+curr_height+","+opacity+")",1);
}
}
}
var newsFeed = new newsFeed();
and I call it in the document as follows:
<div id="closeMe">
<a onclick="newsFeed.delete('closeMe');">close this div</a>
</div>
When it gets to the setTimeout function within this.collapse ... it errors "this.collapse is not a function".
When the timeout calls you've exited the function and "this" no longer refers to what you think it does.
You should use a closure, like this:
var self = this;
setTimeout(function()
{
self.collapse(listingID, orig_height, curr_height, opacity);
}, 1);
The behavior that you are seeing is because the scoping issue in JavaScript. JavaScript has just two scopes - function and global.
When you perform a setTimeout() call, you have to set variables in the global scope, if you wish to use that state in the code executed due to the setTimeout() call. That would be the fix to the issue; Greg has already suggested a way to do this.
You'll find more information in the Mozilla Developer Center in the pages about setTimeout and in the Core JavaScript Reference.
When the timeout is called, this is no longer what you want it to be. You'll need to refer to the DOM element you want by some other mechanism, perhaps ID-based retrieval.

Categories