How can I loop jQuery code? - javascript

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

Related

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

clearInterval Not being set

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)

stopping dynamically generated setInterval

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

jquery each() with setInterval

I have an object filled with various elements that I wish to iterate through using each() and then perform an action on the element whose turn it is. So:
var arts = $("#press-sqs > article");
shuffle(arts);
$(arts).each(function(){
setInterval(function() {
// in here perform an action on the current element in 'arts'
}, 2000);
});
( shuffle() is a basic shuffle function )
What I can't figure out is how to access the current element as a selector and perform an action on it. $(this) is $(window).
Finally I would then need the function to start the iteration again once it reaches the end of art and keep on looping ad infinitum.
If you're using setInterval, you'd get identical results swapping the order:
setInterval(function() {
$(arts).each(function(){
doSomethingWith(this);
});
}, 2000);
I don't think you want what you think you do here. I reckon you want:
var i = 0;
setInterval(function() {
var art = arts[i++];
doSomethingWith(art)
if(i >= arts.length) i = 0;
}, 2000);
jQuery's .each(...) method passes the "current" element (and its index) into the callback. this is just a convenience for when you don't need to do anything too complicated.
$(arts).each(function(i, current){
setInterval(function() {
// in here perform an action on the current element in 'arts'
}, 2000);
});
Above, the current element is available within the setInterval callback as current, for example. Note that this element is passed in its "raw" form, as this is, so if you want to call jQuery methods on it, you'll need to wrap it in the same way, ie: $(current).
Use that.
$(arts).each(function(){
var that = this;
setInterval(function() {
// in here perform an action on the current element in 'arts'
doSomethingWith(that)
}, 2000);
});

Confused by this - getting error "this.myfuntion() is not a function"

Background: I am trying to edit a zen cart horizontal pop out menu to make the popout open inline within the menu. The problem I am having is that I am struggling to get my head around the javascript/jquery that came with it.
Without posting the whole thing the structure of the code is something like this:
(declare some vars)
//some functions like this:
function funcname(obj) {
//do something
}
//then one big master function like this:
function bigfunc(arg1, arg2, arg3, arg4, arg5) {
//declare some vars based on this
this.varname1=varname1;
this.varname2=varname2;
//declare some functions inside the big function
this.innerfunc1= function() {
//do stuff
}
this.innerfunc2= function() {
//do stuff
}
}//end of big function
//then goes on to declare init function
function initfunc(){
//this creates new bigfunc(arg1 arg2 arg3...) for each main menu item
}
//finally calls init function with
window.onload = initfunc();
Now on to my confusion -
1) firstly for clarification, am I correct in thinking based on all the this's floating about in bigfunc() and the fact that it is called with new bigfunc() that this is creating an object?
2)My current problem is with one of the functions inside bigfunc() which looks like this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
var maxwidth = this.children[0].width;
var nextWidth;
if (this.isMouseOnMe || this.isMouseOnChild()) {
nextWidth = divref.offsetWidth + slideSpeed_out;
if (nextWidth >= maxwidth) {
this.finishOpeningChild(divref, ulref, maxwidth);
} else {
ulref.style.left = nextWidth - maxwidth + "px";
divref.style.width = nextWidth + "px";
setTimeout("slideChildMenu('" + this.getId() + "')", slideTimeout_out);
}
}
Now my plan is to alter this to use jquery show to open the element so I tried this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
if (this.isMouseOnMe || this.isMouseOnChild()) {
$(divref).show(function(){
this.finishOpeningChild(divref, ulref);
});
}
}
But I am getting this-> TypeError: this.finishOpeningChild is not a function
Now, there is a lot of other stuff going on in this js so I wouldnt dream of asking someone on here to do my work for me, but I am hoping that if someone can explain to me why this function is not a function I may be able to work the rest out.
NOTE: I thought this was to do with the scope of "this" but the value of this appears to be exactly the same in both versions of the code.
I know this is a long one but your help is greatly appreciated.
The value of this in a function is called the "context" in which the function runs. In general, whenever you pass a callback function as an argument (as you do with $(divref).show(function() {...})), the function can run the callback in whatever context it wants. In this case, the jQuery show function chooses to run its callback in the context of the element being animated.
However, you want access to the value of this at the time the anonymous callback function is defined, rather than when it is run. The solution here is to store the outer value of this in a variable (traditionally called self) which is included in the scope of the newly-defined function:
this.slideChildMenu = function() {
//...
var self = this;
$(divref).show(function(){
self.finishOpeningChild(divref, ulref);
});
}
I am thinking that the jQuery selector has changed the scope of this.
In your example $(this); would refer to object being animated per jQuery api docs:
If supplied, the callback is fired once the animation is complete. This can be useful for stringing different animations together in sequence. The callback is not sent any arguments, but this is set to the DOM element being animated. If multiple elements are animated, it is important to note that the callback is executed once per matched element, not once for the animation as a whole.
If the object in question is instantiated you can call it with dot notation without using this like bigFunc.finishOpeningChild(divref, ulref);
You're probably a little confused about scope, it's not always easy keeping track, but doing something more like this:
var site = {
init: function(elm) {
self=site;
self.master.funcname2(self.varname1, elm); //call function in master
},
funcname: function(obj) {
//do something
},
varname1: 'some string',
varname2: 3+4,
master: function() {
this.varname3 = sin(30);
this.funcname2 = function(stuff, element) {
site.funcname(element); //call function in 'site'
var sinus = site.master.varname3; //get variable
}
}
}
window.onload = function() {
var elm = document.getElementById('elementID');
site.init(elm); //call init function
}
usually makes it a little easier to keep track.

Categories

Resources