Having some problems with the following block of code:
$('.merge').each(function(index) {
var mergeEl = $(this);
setTimeout(function() {
self.mergeOne(mergeEl, self, index - (length - 1));
}, 500);
});
I'm trying to apply a .500 second delay between each call of mergeOne, but this code only applies a .500 second delay before calling mergeOne on all the elements in the array simultaneously.
If someone could explain why this code doesn't work and possibly a working solution that would be awesome, thanks!
Here's a general function you can use to iterate through a jQuery object's collection, with a delay between each iteration:
function delayedEach($els, timeout, callback, continuous) {
var iterator;
iterator = function (index) {
var cur;
if (index >= $els.length) {
if (!continuous) {
return;
}
index = 0;
}
cur = $els[index];
callback.call(cur, index, cur);
setTimeout(function () {
iterator(++index);
}, timeout);
};
iterator(0);
}
DEMO: http://jsfiddle.net/7Ra9K/ (loop through once)
DEMO: http://jsfiddle.net/42tXp/ (continuous looping)
The context and arguments passed to your callback should be the same as how .each() does it.
If you want to make it a jQuery plugin, so it can be called like $("selector").delayedEach(5000, func..., then you could use this:
$.fn.delayedEach = function (timeout, callback, continuous) {
var $els, iterator;
$els = this;
iterator = function (index) {
var cur;
if (index >= $els.length) {
if (!continuous) {
return;
}
index = 0;
}
cur = $els[index];
callback.call(cur, index, cur);
setTimeout(function () {
iterator(++index);
}, timeout);
};
iterator(0);
};
DEMO: http://jsfiddle.net/VGH25/ (loop through once)
DEMO: http://jsfiddle.net/NYdp7/ (continuous looping)
UPDATE
I added the ability to continuously loop through the elements, as an extra parameter. Passing true will continuously loop, while passing false or nothing (or something falsey) will only loop over the elements once. The code and fiddles include the changes.
Related
Background (You might want to skip this)
I'm working on a web app that animates the articulation of English phonemes, while playing the sound. It's based on the Interactive Sagittal Section by Daniel Currie Hall, and a first attempt can be found here.
For the next version, I want each phoneme to have it's own animation timings, which are defined in an array, which in turn, is included in an object variable.
For the sake of simplicity for this post, I have moved the timing array variable from the object into the function.
Problem
I set up a for loop that I thought would reference the index i and array t to set the milliseconds for each setTimeout.
function animateSam() {
var t = [0, 1000, 2000, 3000, 4000];
var key = "key_0";
for (var i = 0; i < t.length; i++) {
setTimeout(function() {
console.log(i);
key = "key_" + i.toString();
console.log(key);
//do stuff here
}, t[i]);
}
}
animateSam()
However, it seems the milliseconds are set by whatever i happens to be when the function gets to the top of the stack.
Question: Is there a reliable way to set the milliseconds from the array?
The for ends before the setTimeout function has finished, so you have to set the timeout inside a closure:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
for (var i = 0; i < t.length; i++) {
(function(index) {
setTimeout(function() {
alert (index);
key = "key_" + index.toString();
alert (key);
//do stuff here
}, t[index]);
})(i);
}
}
Here you have the explanation of why is this happening:
https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b
The for loop will loop all elements before the first setTimeout is triggered because of its asynchronous nature. By the time your loop runs, i will be equal to 5. Therefore, you get the same output five times.
You could use a method from the Array class, for example .forEach:
This ensures that the function is enclosed.
[0, 1000, 2000, 3000, 4000].forEach((t, i) => {
setTimeout(function() {
console.log(i);
console.log(`key_${i}`);
//do stuff here
}, t)
});
Side note: I would advise you not to use alert while working/debugging as it is honestly quite confusing and annoying to work with. Best is to use a simple console.log.
Some more clarifications on the code:
.forEach takes in as primary argument the callback function to run on each of element. This callback can itself take two arguments (in our previous code t was the current element's value and i the current element's index in the array):
Array.forEach(function(value, index) {
});
But you can use the arrow function syntax, instead of defining the callback with function(e,i) { ... } you define it with: (e,i) => { ... }. That's all! Then the code will look like:
Array.forEach((value,index) => {
});
This syntax is a shorter way of defining your callback. There are some differences though.
I would suggest using a function closure as follows:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
var handleAnimation = function (idx) {
return function() {
alert(idx);
key = "key_" + idx.toString();
alert(key);
//do stuff here
};
}
for (var i = 0; i < t.length; i++) {
setTimeout(handleAnimation(i), t[i]);
}
}
I this example you wrap the actual function in a wrapper function which captures the variable and passes on the value.
I got this code, it's supposed to toggle elements, following a repetitive patron, that will grow up randomly, my start function executes my runp() function at simultaneous, and it got all messy. i would need to wait until runp() finishes to continue executing. Thanks
function runp(patron){
var x = 0;
var intervalID = setInterval(function () {
$("#container"+patron[x]).toggle(1).delay(1000).toggle(1).delay(1000);
if (++x === 20) {
window.clearInterval(intervalID);
}
}, 2000);
}
function start(patron, patronl){
while (patron.length<20){
patron.push(rand(1,4));
runp(patron);
}
}
You can use .queue()
// alternatively pass randomly shuffled array of elements to `$.map()`
$({}).queue("toggle", $.map($("[id^=container]"), function(el) {
return function(next) {
return $(el).toggle(1).delay(1000).toggle(1).delay(1000)
.promise().then(next)
}
})).dequeue("toggle")
For example, there is for loop that I want to sleep for some seconds.
$.each(para.res, function (index, item) {
Sleep(100);
});
I know I can use setTimeout or setInterval but both of them are asychronous, the loop will continue, just the function in the setTimeout will run in a few seconds if I do it this way.
$.each(para.res, function (index, item) {
setTimeOut(function(){do something},1000);
});
You can define a function.
var i = 0;
function recursive() {
setTimeout(function(){
var item = para.res[i];
// do something
i++;
if (i < para.res.length) recursive()
}, 100)
}
No, there is no built in method for that. You could use a busy loop, but that will freeze the browser in the mean time, and you can't do it for too long because then the browser will stop the script.
If you want the different pieces of code spread out over time, just set different times for setTimeout:
$.each(para.res, function (index, item) {
setTimeOut(function(){do something},1000 * index);
});
This will start the code for the first item after one second, the code for the second item after two seconds, and so on.
Or use setInterval:
var index = 0, timer = setInterval(function(){
if (index < para.res.length) {
var item = para.res[index];
// do something
index++;
} else {
clearInterval(timer);
}
}, 1000);
I have a JQuery's .each loop that calls a function with a parameter per iteration, is there a way to delay this function call? I have tried setTimeout as in the following but this does not work as the function gets executed immediately.
$.each(myArray, function (j, dataitem)
{
setTimeout(function () { showDetails(dataitem) }, 300);
});
function showDetails(dataitem)
{
...
}
Array size is roughly 20, What I'm trying to do is to distribute function calls over a certain time frame instead of immediately, any idea how to achieve this? I'm prepared to rewrite and restructure how functions get called to get this done, any help would be appreciated.
You could use the index of the array to calculate the interval dynamically:
$.each(myArray, function (j, dataitem) {
window.setTimeout(function () {
showDetails(dataitem)
}, (j + 1) * 300);
});
You execute them all after 300 milliseconds. Instead, try something like this:
window.setTimeout(function () { showDetails(dataitem) }, (j + 1) * 300);
Edit: instead of creating 20 timers at once I think it's better to do it one by one. Function should be:
function showDetails(index)
{
if (index >= myArray.length)
return false;
var dataItem = myArray[index];
//code here......
//code here......
//code here......
windows.setTimeout(function() { showDetails(index + 1); }, 300);
}
And first call can be:
$(document).ready(function() {
{
showDetails(0);
});
This assume myArray is plain global array, and will handle one item and only then call the next item with delay.
Take a look at jQuery.queue([ queueName ], callback( next )). This allows you to queue functions up to be called and is what jQuery's animation effects use internally.
It sounds like you would like to implement a queue, although it is not entirely clear you intentions for doing so.
EDIT: re-reading your question, I think other answers better match what you are after, however I thought that I would show you an example of how to achieve delayed function execution with a custom queue.
An example of how you could use a queue.
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
output = $('#output');
// set the queue up
$.each(myArray, function (j, dataitem) {
output.queue('queue', function(next) {
var that = this;
showDetails(dataitem);
window.setTimeout(next,300);
});
});
// start the queue running.
output.dequeue('queue');
function showDetails(dataitem) {
output.append('<div>' + dataitem + '</div>');
}
Just don't use $.each, but something like:
var data = [1, 2, 3, 4, 5];
function showDetails(values, delay) {
console.log(values.shift()); //show the value
if (values.length) {
setTimeout(function() {showDetails(values, delay); }, delay); //schedule next elem
}
}
showDetails(data.slice(0), 300); //dont forget the slice, call-by-reference
The issue was my array was [[][][]] instead of [[]] : /
This is my Script
function loopobject(array) {
var me = this;
this.array = array;
this.loop = function() {
counter = 0;
while(array.length > counter) {
window[array[counter]]('arg1', 'arg2');
counter++;
}
setTimeout(function(){ me.loop() }, 100);
}
}
var loopinstant = new loopobject(array);
window.onload = loopinstant.loop();
The problem arises after the first iteration. I don't know exactly the problem but I'm wondering if its due to the fact this is inside an object, and once the function is recreated it doesn't remember the array?
Don't pass a string to setTimeout.
Passing a string to setTimeout causes it to be evaled in global scope.
In addition to being needlessly slow, that means that it won't see your local variables, including the loop variable.
Instead, you should pass a function itself to setTimeout:
setTimeout(function() { loop(array); }, 100);
Also, the loopobject doesn't actually have a loop property that you can call later.
To make a property, change it to this.loop = function(...) { ... }.
Note that the setTimeout callback won't be called with the correct this.
You'll also need to save a copy of this in a local variable.
Finally, your window.onload code will call loop, then assign the result to onload.
Correcting these issues, your code turns into
function loopobject(){
var me = this;
this.loop = function(array){
counter = 0;
while(array.length > counter){
window[array[counter]]('arg1', 'arg2');
counter++;
}
setTimeout(function() { me.loop(array); }, 100);
};
}
var loopinstant = new loopobject();
window.onload = function() { loopinstant.loop(array); };
Replace
setTimeout("loop()", 100);
with
setTimeout(function() { loop(array); }, 100);