Why does this setTimeout callback give me an error? - javascript

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.

Related

wrapping a function in a pollSever function

I am using an already defined function and now want to add a pollServer function to it so that this functions runs over and over. I keep getting errors when I try to wrap the existing function in another. Is there a better way to do this?
function callD(id) {
jQuery('document').ready(function pollServer(){
window.setTimeout(function () {
var ab = document.getElementById('a')
console.log(ab);
var bod = document.getElementById(+id)
if (ab == null) {
bod.style.background='green'
} else {
bod.style.background='blue'
}
}, 1200);
})
}
callD();
pollServer();
pollServer isn't defined where you're calling it. Also id isn't being passed to callD, and you also have a +id which doesn't make sense in a document.getElementByid, since if there's any non-number in the ID, that would be NaN. You're also not polling a server, you're setting a timeout once and doing some work that doesn't involve a server. You would want setInterval for regular polling, or to call the function again on some condition like a failure.
$(document).ready(function () {
var intervalId;
function callD(id) {
function pollServer() {
intervalId = window.setInterval(function () {
var ab = document.getElementById('a')
console.log(ab);
var bod = document.getElementById(id)
if (ab == null) {
bod.style.background='green'
} else {
bod.style.background='blue'
}
}, 1200);
}
pollServer();
}
callD('some-id');
// on some condtion eventually:
clearInterval(intervalId);
})
Yeah, jQuery can make things pretty gnarly with all the nested callbacks. To make the code cleaner and easier to understand, I like to split my functions up and define them all at the top-most level of the script, then compose them together like so:
/**
* this function will check for the existing elements
* and update styles
*/
function setBodyStyle(id) {
var ab = document.getElementById('a');
console.log(ab);
var bod = document.getElementById(+id);
if (ab == null) {
bod.style.background='green';
} else {
bod.style.background='blue';
}
}
/**
* this function will create a timeout or interval
* which will in turn run setBodyStyle()
*/
function pollServer() {
// I think you want setInterval here if you're polling?
// setInterval will run _every_ 1200ms,
// setTimeout only runs once after 1200ms
window.setInterval(function() {
// not sure where you're getting id from,
// but you'll want to pass it here
setBodyStyle();
}, 1200);
}
// when the document is ready, run pollServer()
jQuery(document).ready(pollServer);
Having small functions that do one thing is just best-practice for the reasons I mentioned above. This will help your script be more understandable, which will help you find bugs.
For example, two things I don't understand about your code above:
where does the id variable come from? I don't see you passing it to your function from anywhere
how does your script poll the server? I don't see the code for that anywhere either.
Seemed you mean run the function pollServer every 1.2 sec. If so, you'd need to do two things
Use setInterval rather than setTimeout
Delete the last line for the pollServer function, because it is not accessible from outside the ready function block.

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.

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);
}

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

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);
}
});

Categories