I am trying to create a button that will toggle setInterval/clearInterval. The setInterval will function correctly, but when the button is clicked again, clearInterval is not done. Is this a variable scope issue or a problem with how the functions are setup?
http://jsfiddle.net/BxLps/1/
$(function () {
var int;
var onrepeat;
$('button[id^=temp]').click(function () {
window.id = $(this).attr("value");
var int = setInterval(doAjax, 3000);
if (onrepeat == false) {
$(this).find('i').addClass("fa-spin");
doAjax();
int;
onrepeat = true;
} else {
clearInterval(int);
$(this).find('i').addClass("fa-spin");
onrepeat = false;
}
});
});
function doAjax() {
$.ajax({
type: "GET",
url: "ajax.php",
data: "a=cur-temp&id=" + id,
success: function (msg) {
$("#cur-temp").html(msg);
}
})
};
Is this a variable scope issue?
Yes. You've used var int twice, with the second one introducing a local variable where you did want to access to outer one.
However, you still might get problems with having a single int variable for all the elements with that selector. I have now created an object which stores the interval ids per id of the element on an object, you might as well use an each loop to create an extra variable per element.
Also, your global variable id is horrible, better use a parameter for the doAjax function.
$(function () {
var ints = {};
$('button[id^=temp]').click(function () {
var id = $(this).attr("value");
if (id in ints) {
$(this).find('i').removeClass("fa-spin");
clearInterval(ints[id]);
delete ints[id];
} else {
$(this).find('i').addClass("fa-spin");
doAjax(id);
ints[id] = setInterval(function() {
doAjax(id);
}, 3000);
}
});
});
The real issue is it's creating new intervals each time. Think about it, every "click" is running that code (so it's doing a setInterval).
Solution is to declare int once (and only once) outside the click. Then move the setInterval inside the condition
var int;
var onrepeat;
$('button[id^=temp]').click(function () {
window.id = $(this).attr("value");
if (onrepeat == false) {
$(this).find('i').addClass("fa-spin");
doAjax();
int = setInterval(doAjax, 3000);
onrepeat = true;
} else {
clearInterval(int);
$(this).find('i').addClass("fa-spin");
onrepeat = false;
}
});
Just remove the second declaration of int.
$(function () {
var int;
$('button[id^=temp]').click(function () {
window.id = $(this).attr("value");
int = setInterval(doAjax, 3000); //remove var to prevent new declaration
if (onrepeat == false) {
$(this).find('i').addClass("fa-spin");
doAjax();
int;
onrepeat = true;
} else {
clearInterval(int);
$(this).find('i').addClass("fa-spin");
onrepeat = false;
}
});
});
JS Fiddle: http://jsfiddle.net/9tkU2/
Your problem is the scope of your variable int. You are declaring it inside the function and by the time you think are clearing the interval the original variable int has been destroyed.
so just remove the var from the var int =... you have inside the function
if the problem persists continue to read below.
Ok, I have suffered this same problem too many time and usually I just let it be,
But most times I realize that after clearing interval the interval continues to run, and this might affect the performance of the device (it's like having an infinite loop).
So I did a little bit of research and I found out what the problem was and I wrote a simple code to solve it.
Now when you start an interval (most likely triggered by an event) in most cases, more than one instance of that interval is declared (for whatever reason)...
So when you clear the interval later, you only clear the *top-level interval, and the next level interval sets in.
(top-level might not be the correct word)
So to truly clear the interval I used the method below:
Setting the interval:
if(!timer)
timer =setInterval(myFunction, 1000);
Clearing the interval:
clearInterval(timer);
timer=null;
while (timer!== null){
timer=null;
}
you might decide to clear the interval inside the while loop, but I found that this works for me and it's quite efficient than that.
Make sure you check the scope of the interval variable (i.e timer in the case above)
Related
I am attempting to make a project which does two way data binding on two specified variables. However, when I tried it out, the project did not seem to be working.
I'm pretty sure that what I did wrong is that I specified a constructor, and then inside, I created a variable using "this," a function using "this," and then I tried to use the first variable inside the function using "this." Is this allowed?
The code for my project is in the snippet below.
function glue(varOne, varTwo, interval = 60) {
this.varOne = varOne;
this.varTwo = varTwo;
this.varOneClone = this.varOne;
this.varTwoClone = this.varTwo;
this.interval = interval;
this.onChange = function(changedVar) {
if (changedVar == this.varOne) {
this.varTwo = this.varOne;
} else if (changedVar == this.varTwo) {
this.varOne = this.varTwo;
}
this.varOneClone = this.varOne;
this.varTwoClone = this.varTwo;
};
this.intervalID = setInterval(function() {
if (this.varOne != this.varTwo) {
if (this.varOne != this.varOneClone) {
this.onChange(this.varOne);
} else if (this.varTwo != this.varTwoClone) {
this.onChange(this.varTwo);
}
}
}, this.interval);
this.clearUpdate = function() {
clearInterval(intervalID);
};
this.changeUpdate = function(newInterval) {
this.interval = newInterval;
clearInterval(intervalID);
this.intervalID = setInterval(function() {
if (this.varOne != this.varTwo) {
if (this.varOne != this.varOneClone) {
this.onChange(this.varOne);
} else if (this.varTwo != this.varTwoClone) {
this.onChange(this.varTwo);
}
}
}, this.interval);
};
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Glue</title>
</head>
<body>
<input id="input" type="text"></input>
<p id="output"></p>
<script>
var input = document.getElementById("input");
var output = document.getElementById("ouput");
var glue = new glue(input, output, 60);
</script>
</body>
</html>
Thank you!
Edit:
I've tried using the var self = this; method which two people recommended, but it still refuses to work. The error from the console is TypeError: glue is not a constructor, but I'm not sure why this happens. I want glue to be a constructor. Please help! The new code is below.
function glue(varOne, varTwo, interval = 60) {
var self = this;
self.varOne = varOne;
self.varTwo = varTwo;
self.varOneClone = self.varOne;
self.varTwoClone = self.varTwo;
self.interval = interval;
self.onChange = function(changedVar) {
if (changedVar == self.varOne) {
self.varTwo = self.varOne;
} else if (changedVar == self.varTwo) {
self.varOne = self.varTwo;
}
self.varOneClone = self.varOne;
self.varTwoClone = self.varTwo;
};
self.intervalID = setInterval(function() {
if (self.varOne != self.varTwo) {
if (self.varOne != self.varOneClone) {
self.onChange(self.varOne);
} else if (self.varTwo != self.varTwoClone) {
self.onChange(self.varTwo);
}
}
}, self.interval);
self.clearUpdate = function() {
clearInterval(intervalID);
};
self.changeUpdate = function(newInterval) {
self.interval = newInterval;
clearInterval(intervalID);
self.intervalID = setInterval(function() {
if (self.varOne != self.varTwo) {
if (self.varOne != self.varOneClone) {
self.onChange(self.varOne);
} else if (self.varTwo != self.varTwoClone) {
self.onChange(self.varTwo);
}
}
}, self.interval);
};
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Glue</title>
</head>
<body>
<input id="input" type="text"></input>
<p id="output"></p>
<script>
var input = document.getElementById("input");
var output = document.getElementById("ouput");
var glue = new glue(input, output, 60);
</script>
</body>
</html>
Thanks for your help!
There are few problems that you could fix, as you said, you're using this everywhere. this might not always point to the same object in some context so if you want to use the same object, you could use a variable accessible from the scope.
Usually, people use a variable named self:
var self = this
Then the self variable should be used everywhere where you want to access specifically that object. So in your onChange and setInterval, it would be a better idea to access this by using the self object.
One other way, would be to instead bind a bounded function. In this case, the this object will be the one you bound the function to.
this.onChange = (function () {
// here this will be equal to context
}).bind(context)
In this case, you can set context to this and within the function you can still use this without worrying which object will be this.
You can also make sure to call method using this.func.call(this, params...) or this.func.apply(this, paramArray).
There are a lot of ways to fix this, but if you search about bind, apply and call. It will be enough to understand how to make more complex construction for your code.
To give you an idea of how bind works, take this example:
function bind(method, obj) {
// Create a callable
return function () {
// Pass the arguments to the method but use obj as this instead
return method.apply(obj, arguments);
}
}
function func(a, b, c) {
console.log(this, a, b, c)
}
var bound1 = bind(func, {a: 1})
var bound2 = bind(func, {a: 2})
func(1,2,3)
bound1(3,4,5)
bound2(6,7,8)
You'll see that when func isn't called, this defaults to window. But in case of bound1 and bound2, it will be using the second parameter of bind.
All that to say that you can control which object will be used as this but if you don't specify which object is going to be used, then you might end up using window as this like in a setInterval. In other words, the keyword this isn't scoped like other variables. It depends of the context, not of its place in the code.
The problem is that the setInterval callback is made in a different context, so this will no longer be your glue object. Since you're running within a browser, it will be the window object. I'm pretty sure that all the other times you use this, it is referencing the correct object.
There are a few options to handle this. The first is to use the .bind() method. It is definitely the cleanest, and requires the least amount of "tweaking". However, it's not supported by IE8. Hopefully you don't have to support that browser, considering MS has dropped support for it, except for in embedded systems. Here's how it would work:
this.intervalID = setInterval(function(self) {
if (this.varOne != this.varTwo) {
if (this.varOne != this.varOneClone) {
this.onChange(this.varOne);
} else if (this.varTwo != this.varTwoClone) {
this.onChange(this.varTwo);
}
}
}.bind(this), this.interval);
Another option is to create a variable that holds the value of this before you call setInterval and then rely on the closure to give you access to that variable:
function glue(varOne, varTwo, interval = 60) {
var self = this;
//...
this.intervalID = setInterval(function() {
if (self.varOne != self.varTwo) {
if (self.varOne != self.varOneClone) {
self.onChange(self.varOne);
} else if (self.varTwo != self.varTwoClone) {
self.onChange(self.varTwo);
}
}
}, this.interval);
//...
}
Finally, you could also use an immediately-invoked function to pass in the value of this without creating a closure:
this.intervalID = setInterval((function(self) {
return function() {
if (self.varOne != self.varTwo) {
if (self.varOne != self.varOneClone) {
self.onChange(self.varOne);
} else if (self.varTwo != self.varTwoClone) {
self.onChange(self.varTwo);
}
}
}
})(this), this.interval);
EDIT:
Everything said above is a problem and the options given for solving it should fix that problem. However, it's one of only a few problems in your code sample.
There's also a problem with the line var glue = new glue(input, output, 60);. You can't have a variable with the same name as a function. If the function is declared first, as it should be in this case, then the variable overwrites it, so the function essentially no longer exists by the time you call new glue(). This is why you are getting the glue is not a constructor error.
I see that in your jsbin you've changed the variable name to tape. That fixes the problem. However, jsbin puts all the code from the JavaScript pane at the bottom of your body. You need the function to be declared before the var tape = new glue(input, output, 60); line, since that line is calling the function. You can tell jsbin where to put the code from the JS pane, by putting %code% where you want it in in the HTML pane. So, if you put a line like <script>%code%</script> before your existing script block, it should fix that. This is totally just a quirk of using jsbin, and won't apply to code you have running in a standalone website (although, even on your standalone site, you need to make sure that the code declaring the glue function comes before the code calling it).
Now, that gets rid of all the errors that are being thrown, but still, the code isn't actually DOING anything. This is because near the beginning of the constructor you have:
this.varOneClone = this.varOne;
this.varTwoClone = this.varTwo;
So varOne starts out equal to varOneClone, and varTwo starts out equal to varTwoClone. The only other place you set them is inside the onChange method, but you only call onChange if varOne != varOneClone or varTwo != varTwoClone. It's like you're saying "Make these 2 values the same, then if they're different, call onChange." Obviously, in that case, onChange is never going to be called.
I realize that it's possible you have more code than you've included here, that IS changing those properties, but I think your goal is to check if the text WITHIN varOne or varTwo has changed, and if so, update the other one, rather than checking if the elements themselves have changed. Since the text can be changed by the user (at least for varOne, since that's an input), it can be changed outside of code. If assumption is correct, you need something like this:
function glue(varOne, varTwo, interval = 60) {
this.varOne = varOne;
this.varTwo = varTwo;
this.varOneCurrentText = this.varOne.value;
this.varTwoCurrentText = this.varTwo.textContent;
this.interval = interval;
this.onChange = function(changedVar) {
if (changedVar == this.varOne) {
this.varTwo.textContent = this.varOneCurrentText = this.varTwoCurrentText = this.varOne.value;
} else if (changedVar == this.varTwo) {
this.varOne.value = this.varOneCurrentText = this.varTwoCurrentText = this.varTwo.textContent;
}
};
this.intervalID = setInterval(function() {
if (this.varOne.value != this.varTwo.textContent) {
if (this.varOne.value != this.varOneCurrentText) {
this.onChange(this.varOne);
} else if (this.varTwo.textContent != this.varTwoCurrentText) {
this.onChange(this.varTwo);
}
}
}.bind(this), this.interval);
//...
}
A couple things to note about this. First, notice that I'm using .value for varOne but .textContent for varTwo. This is because you're passing in a form element (an input) for varOne and a non-form element (a paragraph) for varTwo. These types of elements have different ways of getting their current text. If you can design it so that only form elements will ever be passed in, it will make things easier. But if not, since you probably won't know in advance what type of elements are passed in, you'd need to add a check at the beginning, so you can use the correct property.
Also, while this should work, it would really be better to use events rather than having a continuous loop in the setInterval looking to see if the text has changed. The input has a change event that will be fired anytime its value is changed. You could just update varTwo whenever that event is fired. I don't think there's a similar event for the paragraph element, but you could create a custom event for it. I'm assuming that you're planning on having some other code that will update the text inside the paragraph, since that's not something the user can do directly. If so, then you could fire your custom event at the same time that you update its text. Then when that event is fired, you could update varOne.
I just noticed a typo in a line you have, as well:
var output = document.getElementById("ouput");
The element ID should of course be "output".
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
Basically, what I have is a setInterval inside a function. What I want to do is, control it's behavior from outside.
Here's what I have -
function wheee() {
var i = 1;
slideee = setInterval(function() {
sliderContent.style.marginLeft = margin(i);
if(i < imagesNoOneLess) {
i++;
} else {
i = 0;
}
}, 5000); }
Now, I want to clear the interval from outside the wheee() function. How can I do that?
I also want to run the interval again, from outside. How?
Global variables are not dangerous, but a pretty way of coding it if you only have one slider is to use an object literal to simulate a singleton object.
var Slider= {
slider: null,
Start: function(i) {
this.slider = setInterval(function() {
// Slider code
console.log('running: ' + i);
i++;
}, 1000);
},
Stop: function() {
window.clearTimeout(this.slider);
}
};
Slider.Start(1); // Start the slider
Slider.Stop(); // Stop the slider
Well the way you've got the code now, it'll probably just work, because you didn't declare "slideee" with var.
As long as you somehow export the return value from setInterval() you're OK. You can either make that variable explicitly global (better than having it be implicit), or else have your "wheee" function return the value to its caller.
Set the scope of slideee to be out of wheee.
Use objects in order to keep the global scope clean.
I need to know what I am doing wrong because I cannot call the internal functions show or hide?
(function()
{
var Fresh = {
notify:function()
{
var timeout = 20000;
$("#notify-container div").get(0).id.substr(7,1) == "1" && (show(),setTimeout(hide(),timeout));
var show = function ()
{
$("body").animate({marginTop: "2.5em"}, "fast", "linear");
$("#notify-container div:eq(0)").fadeIn("slow");
},
hide = function()
{
$("#notify-container div").hide();
}
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
thanks, Richard
UPDATE
If you wanted to be able to do something like: Fresh.notify.showMessage(), all you need to do is assign a property to the function notify:
var Fresh = {notify:function(){return 'notify called';}};
Fresh.notify.showMessage = function () { return this() + ' and showMessage, too!';};
Fresh.notify();//notify called
Fresh.notify.showMessage();//notify called and showMessage, too!
This will point to the function object here, and can be called as such (this() === Fresh.notify();). That's all there is too it.
There's a number of issues with this code. First of all: it's great that you're trying to use closures. But you're not using them to the fullest, if you don't mind my saying. For example: the notify method is packed with function declarations and jQuery selectors. This means that each time the method is invoked, new function objects will be created and the selectors will cause the dom to be searched time and time again. It's better to just keep the functions and the dom elements referenced in the closure scope:
(function()
{
var body = $("body");
var notifyDiv = $("#notify-container div")[0];
var notifyDivEq0 = $("#notify-container div:eq(0)");
var show = function ()
{
body.animate({marginTop: "2.5em"}, "fast", "linear");
notifyDivEq0.fadeIn("slow");
};
var hide = function()
{//notifyDiv is not a jQ object, just pass it to jQ again:
$(notifyDiv).hide();
};
var timeout = 20000;
var Fresh = {
notify:function()
{
//this doesn't really make sense to me...
//notifyDiv.id.substr(7,1) == "1" && (show(),setTimeout(hide,timeout));
//I think this is what you want:
if (notifyDiv.id.charAt(6) === '1')
{
show();
setTimeout(hide,timeout);//pass function reference
//setTimeout(hide(),timeout); calls return value of hide, which is undefined here
}
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
It's hard to make suggestions in this case, though because, on its own, this code doesn't really make much sense. I'd suggest you set up a fiddle so we can see the code at work (or see the code fail :P)
First, you're trying to use show value when it's not defined yet (though show variable does exist in that scope):
function test() {
show(); // TypeError: show is not a function
var show = function() { console.log(42); };
}
It's easily fixable with moving var show line above the point where it'll be called:
function test() {
var show = function() { console.log(42); };
show();
}
test(); // 42
... or if you define functions in more 'traditional' way (with function show() { ... } notation).
function test() {
show();
function show() { console.log(42); };
}
test(); // 42
Second, you should use this instead:
... && (show(), setTimeout(hide, timeout) );
... as it's the function name, and not the function result, that should be passed to setTimeout as the first argument.
You have to define show and hide before, also change the hide() as they said.
The result will be something like this:
(function()
{
var Fresh = {
notify:function()
{
var show = function()
{
$("body").animate({marginTop: "2.5em"}, "fast", "linear");
$("#notify-container div:eq(0)").fadeIn("slow");
},
hide = function()
{
$("#notify-container div").hide();
},
timeout = 20000;
$("#notify-container div").get(0).id.substr(7,1) == "1" && ( show(), setTimeout(hide,timeout) );
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
I think order of calling show , hide is the matter . I have modified your code . It works fine . Please visit the link
http://jsfiddle.net/dzZe3/1/
the
(show(),setTimeout(hide(),timeout));
needs to at least be
(show(),setTimeout(function() {hide()},timeout));
or
(show(),setTimeout(hide,timeout));
I have a recursive function which contains some drawing code inside. I was advised to use setTimeout as my drawing was not being displayed until the end of exection. First I put just the drawing code inside setTimeout but this did not help, however when I put the main recursive loop inside setTimeout the drawing worked perfectly, as shown below.
However I need to use the return value of setTimeout (i.e. state as shown below). How can I get this return value when using setTimeout, or solve this problem in another way.
var doLearning = function(time, observedData, state, domain, sampleAction, selectModel, numSamples, depth, discount, stateQueries) {
if(stateQueries[0](time, state) === true) {
console.log("New Round");
var currentModel = selectModel(observedData, 10, stateQueries);
var bestAction = sparseSampleMcmc(depth, numSamples, discount, currentModel, state, sampleAction, stateQueries);
var newStateReward = domain.executeAction(bestAction, stateQueries);
observedData.push(bestAction, newStateReward[1], newStateReward[0]);
console.log(time);
setTimeout(doLearning, 100, time + 1, observedData, newStateReward[0], domain, sampleAction, selectModel, numSamples, depth, discount, stateQueries);
} else {
console.log("Game Over");
return state;
}
}
Make an object with all your vars, like:
var game = {
time: ... ,
observedData: ....,
state: .... etc
}
In doLearning get and modify this object's properties when necessary:
var doLearning = function(obj) {
if(obj.state == ....)
obj.currentModel = whatever...
obj.bestAction = whatever...
setTimeout(function() { doLearning(obj) }, 100)
else
game over
}
This gives me the willies to say, but what if you had a global variable where the return value would go? Set it at the end of doLearning, Then when you detect that the timeout/drawing is done, check the global.
Without all the code I'm having a hard time understanding, but perhaps it would be better to use setInterval here is a simple example.
function draw(){
var state=//whatever
var num = setInerval(doLearning,100)
function doLearning(){
//You have access and can modify state and do not need to return its value
if(){}
else{
clearInterval(num);
console.log('Game over');
}
}
}
By calling as follows, the current execution context (ie. the environment of the current instance of doLearning) forms a closure in which time, observedData etc. remain available to the anonymous function defined inside the setTimeout() statement.
Thus, the following should work:
setTimeout(function(){
doLearning(time + 1, observedData, newStateReward[0], domain, sampleAction, selectModel, numSamples, depth, discount, stateQueries);
}, 100);