I would like to know why, in the following code, the elements of coupleFound array are deleted when the function setInterval dies. The idea is, get into the a element and check her tag name "ai".
When he accept this tag he makes a copy of the entire element starting from his parent, and put into the coupleFound array. It works, but only inside of set Interval function! I don't understand because I declared the array outside of the function! I believe this is happening because "set Interval" is not synchronized, but I don't know how fix this problem!
var clicked = 0,
totalClicks = 3,
index = 0,
listIds = new Array("289657", "2680235", "1597254", "269621"),
coupleFound = new Array( ),
videos = document.getElementById( "videos_list" );
var interval = setInterval(function( ) {
coupleList = videos.getElementsByTagName( "a" );
for(var i = coupleList.length; i--;) {
for(j=0; j < listIds.length; j++) {
if(coupleList[i].getAttribute( "ai" ) == listIds[j]) {
coupleFound[index] = coupleList[i].parentNode;
index++;
break;
}
}
videos.removeChild( videos.lastChild );
}
document.getElementById('btnMoreVideos').click();
clicked++;
if(clicked >= totalClicks) {
clearInterval( interval );
alert("I'm inside of the function. The length is:" + coupleFound.length)
}
}, 1000);
alert("The length of the array is:" + coupleFound.length);
What can I do to solve this problem?
You're more or less right. setInterval is asynchronous. Your last line of code will be ran immediately, before setInterval fires for the 3rd time.
There's no good way to sleep/block in JavaScript. You have to restructure your code so that it runs in the order you want. Normally this means using callbacks. i.e., call a function from inside your if(clicked >= totalClicks) block that does what you want, rather than putting it after setInterval.
e.g.,
var clicked = 0,
totalClicks = 3,
index = 0,
listIds = new Array("289657", "2680235", "1597254", "269621"),
coupleFound = new Array( ),
videos = document.getElementById( "videos_list" );
function allDone() {
alert("The length of the array is:" + coupleFound.length);
}
var interval = setInterval(function( ) {
coupleList = videos.getElementsByTagName( "a" );
for(var i = coupleList.length; i--;) {
for(j=0; j < listIds.length; j++) {
if(coupleList[i].getAttribute( "ai" ) == listIds[j]) {
coupleFound[index] = coupleList[i].parentNode;
index++;
break;
}
}
videos.removeChild( videos.lastChild );
}
document.getElementById('btnMoreVideos').click();
clicked++;
if(clicked >= totalClicks) {
clearInterval( interval );
alert("I'm inside of the function. The length is:" + coupleFound.length);
allDone();
}
}, 1000);
Although you should avoid global variables as much as possible. Maybe pass coupleFound or its length into allDone instead if you don't need access to too many vars.
Related
I'm curious how to implement progressive/dynamic placeholder like the below example in CSS/Javascript.
PS. I know we need to show some kind of work/effort before posting to SO. but I'm kinda confuse what to search to get relevant information
Set the <input placeholder="" attribute using setTimeout or setInterval for an animation loop.
A simpler version just spams a bunch of future updates using setTimeout instead of an animation loop as I think it's simpler - though this approach does not scale.
var finalPlaceholderText = "foo bar baz";
var input = document.getElementById( 'idOfTextBox' );
var placeholderIndex = 0;
for( var i = 0; i < finalPlaceholderText.length; i++ ) {
setTimeout(
function( length ) {
input.placeholder = finalPlaceholderText.substring( 0, i );
}
, i * 500, i );
}
Note you need to pass length (i) as a parameter into the setTimeout callback because JavaScript's closures will use the last value after the for loop finishes instead of the value used when setTimeout was called each time.
Using a setTimeout-loop, it would look like this (note the lack of a for loop):
var finalPlaceholderText = "foo bar baz";
var input = document.getElementById( 'idOfTextBox' );
function incrementPlaceholderText( i ) {
input.placeholder = finalPlaceholderText.substring( 0, i );
if( i < finalPlaceholderText.length ) {
setTimeout( incrementPlaceholderText, 500, i + 1 );
}
}
incrementPlaceholderText( 0 );
Or more generally (in a way that supports multiple input elements and different placeholder texts):
function incrementPlaceholderText( input, finalText, i ) {
input.placeholder = finalText.substring( 0, i );
if( i < finalText.length ) {
setTimeout( incrementPlaceholderText, 500, input, finalText, i + 1 );
}
}
incrementPlaceholderText( document.getElementById( 'idOfTextBox1' ), "foo bar baz", 0 );
incrementPlaceholderText( document.getElementById( 'idOfTextBox2' ), "foo bar baz qux", 0 );
You could use setInterval function:
var finalPlaceholderText = "foo bar bazaazz";
var input = document.getElementById('idOfTextBox');
var len = finalPlaceholderText.length;
var timerID;
var counter = 0;
if (counter <= len) {
timerID = setInterval(function() {
counter = counter + 1;
typewriter(counter)
}, 100);
}
function typewriter(i) {
input.placeholder = finalPlaceholderText.substring(0, i);
if (i === len) {
counter = 0;
//comment out below if you want it to stop
//clearInterval(timerID)
}
}
<input id="idOfTextBox" placeholder="" />
Hopefully this snippet will be useful. Have added comments for clarification
//create a variable. This will be used to create substring
var initialChar = 0;
//get the element and placeholder
let getElement = document.getElementById('inputElem');
let getPlaceHolderText = getElement.getAttribute('placeholder');
//create IIFE and this will be called as long as placeholder is not
//completly created
(function setPlaceholder() {
//settime out function to input one text at a time
let clearNow = setTimeout(function() {
// increase the count
initialChar++;
//create a substring and set this value as placeholder
let getChar = getPlaceHolderText.substring(0, initialChar);
getElement.setAttribute('placeholder', getChar + '|')
// when the variable value and length of string
// is equal it mean all the placeholder text has been created
// if not equal then add next character to placeholder
if (initialChar !== getPlaceHolderText.length) {
// calling the IIFE
setPlaceholder()
} else {
// equal so remove the pipe(pipe to create cursor effect)
getElement.setAttribute('placeholder', getElement.getAttribute('placeholder').slice(0, -1));
clearTimeout(clearNow);
}
}, Math.ceil(Math.random() * 150)) // any random number
}())
<input type="text" placeholder="Add you text here" id="inputElem">
After inspecting the sources found out that there's an awesome library already built for this.
https://github.com/chinchang/superplaceholder.js
I have the following code :
var i = 0, count = 10, randomId;
function f(myArray) {
// this will get a random value from myArray
randomId = myArray[Math.floor( Math.random()*myArray.length )];
// displays the random value
alert (randomId);
i++;
if( i < count ){
setTimeout( f, 3000 );
}
}
f(myArray);
The above code works but gives only one alert and then it stops.
However it works properly (10 loops) with basic alerts such as alert("hi"), and remove the randomId line.
It's as if anything complex within this function will block the loop, it will only handle basic alerts..
Any help is appreciated, thanks :)
In your setTimeout you are not passing the array:
Try this:
if( i < count ){
setTimeout(() => f(myArray), 3000 );
}
^ that creates a lambda function so that you can pass a value to your callback in the timeout.
var i = 0, count = 10, randomId;
function f(myArray) {
// this will get a random value from myArray
randomId = myArray[Math.floor( Math.random()*myArray.length )];
// displays the random value
alert (randomId);
i++;
if( i < count ){
setTimeout(() => f(myArray), 3000 );
}
}
f([1,2,3,4,5,6,7,8]);
I want to set 8 timeouts from a for loop. i have jquery ids: ir1,ir2...,ir8. Why is the following not working:
var i = 1;
for (i; i<9; i++ ){
setTimeout( $('#ir'+i).hide(),100*i);
}
http://jsbin.com/OrUVoRi/1/edit
var i = 0, elements = [];
//save jquery calls for performance reasons
for (i = 0; i<9; i++){
elements[i] = $('#ir'+i);
}
//you can make this function custom
var hide = function(i){
elements[i].hide();
};
//the callback loop
for (i = 0; i<9; i++){
//bind i value to the hide function
setTimeout( hide.bind(this, i), 1000*i );
}
setTimeout() should be
setTimeout(function(){
$('#ir'+i).hide()
},100*i);
updated.
as #royi namir mentioned, the issue is because of closure(scope) , call a self invoking function , pass i as argument , increased the timeout value to 1000 ... just as an example
var i = 1;
for (i; i<9; i++){
(function(a){
setTimeout(function(){
$('#ir'+a).hide();
},1000*a);
})(i);
}
fiddle
You question is blurred : I'll explain :
in first glance you're having a closure problem :
solution :
var i = 1;
for (i; i<9; i++){
(function(a){
setTimeout(function(){
$('#ir'+a).hide();
},1000*a);
})(i);
}
Also , since all your timeouts execute in burst -you added an increased delay : 100*i;
Among other solutions - here is mine : http://jsbin.com/EpaceSaJ/5/edit
the difference is that in your case - the functions will be at exact 100*i regardless the execution of inner code
While - in my solution : the timeout will be start after each end of last operation.
(function g(i,max){
setTimeout(function ( )
{
console.log(i); // <-- put your stuff here
if (i==max) return false;
g((++i),max);
}, 1000);
})(1,3); // <--loop values from 1 to 3 (9 at your case)
I did some digging around on SO and could not find exactly what I am trying to achieve.
In simplistic terms I have a function like
function(){
for(i=0;i<10;i++){
setInterval(function(){ alert(i);), 1000)
}
}
What I would expect is 10 setIntervals that would alert 1 to 10 every 1 second, what happens is it would alert 10 always since 'i' is 10 at the end of for loop. How do I pass 'i' to setInterval anonymous function so that I can preserve the value of i in setInterval?
Above was a simplistic version of my actual problem. I am actually trying to do this
var timers = [];
function(obj){
//Clear any intervals
for(i=0;i<timer.length;i++){
clearInterval(timers[i]);
}
// Empty timers Array
timers = [];
for(i in obj){
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if(obj[i] === "Something"){
timers.push(setInterval(function(){
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000)
}
}
}
my_obj in the above code always refers to id = last 'i' in obj.
Do I make sense?
This should do the trick ;)
for(i = 1; i < 11; i++){
(function(local_i){
setInterval(function(){ console.log(local_i); }, 1000 * local_i)
})(i);
}
You must capture the variable in a closure. In your case this is
function capture(x) {
setInterval(function () {
console.log(x);
}, 1000);
}
for (var i = 0; i < 10; i++) {
capture(i);
}
or
function capture(my_obj) {
var id = setInterval(function() {
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000);
return id;
}
for (i in obj) {
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if (obj[i] === "Something") {
timers.push(capture(my_obj));
}
}
This is my code. What I want it to do is write 0, wait one sec, write 1, wait one sec, write 2, wait one sec, etc. Instead it writes 5 5 5 5 5
for(i = 0; i < 5; i++) {
setTimeout("document.write(i + ' ')", 1000);
}
http://jsfiddle.net/Xb7Eb/
1) You set all the timeouts to last 1 second at the same time. The loop doesn't wait for the timeout to occur. So you have 5 timeouts that all execute at the same time.
2) When the timeouts execute, the loop is long since complete and i has become 5. So once they execute, they all print "5"
3) document.write() writes somthing onto the page, in the same place it executes. I.e. if you have <script>document.write("xyz")</script> in the middle of a piece of text, it'll write "xyz" in the middle of the text. The timeouts, however, are not necessarily anywhere on the page. They exist only in code.
Here's a solution that's as close to yours as possible: http://jsfiddle.net/rvbtU/1/
var container = document.getElementById("counter");
for(i = 0; i < 5; i++) {
setTimeout("container.innerHTML += '" + i + " ';", 1000 * i);
}
However, that solution uses setTimeout's ability to evaluate a string as javascript, which is never a good idea.
Here's a solution that uses an anymous function instead: http://jsfiddle.net/YbPVX/1/
var container = document.getElementById("counter");
var writer = function(number) {
return function() { container.innerHTML += String(number) + " "; };
}
for(i = 0; i < 5; i++) {
setTimeout(writer(i), 1000 * i);
}
Edit: Forgot to save the 2nd fiddle. Whoops. Fixed now.
Most of the answers available are giving bad advice.* Specifically, you shouldn't be passing a string to setTimeout anymore (it still works, but it's discouraged), it's no longer 2000, there are better ways to do this.
setTimeout takes a function as the first parameter, and that's what you should do, however there are some issues when calling setTimeout in a loop.
This looks like it should work:
var i;
for ( i = 0; i < 5; i++ )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i + 1) );
}
But it doesn't. The issue is that by the time setTimeout executes the function, the loop will have incremented i to 5, so you'll get the same value repeated.
There are a few fixes. If you're willing to risk a with statement, you could try the following:
var i;
for ( i = 0; i < 5; i++ )
{
with( { i:i } )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
}
}
Note that with is typically discouraged just like passing string values to setTimeout, so I don't really suggest this method of doing things.
The better way is to use a closure:
var i;
for ( i = 0; i < 5; i++ )
{
(function(i){
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
})(i);
}
To explain what's going on, the anonymous function wrapper (function(i){...code...}) executes immediately because it's wrapped in parens and passed i as a value:
(function(i){...code...})(i);
This forces the i variable that document.write uses to be a different one than what's being used in the for loop. You could even change the parameter used in the anonymous function wrapper if the difference gets too confusing:
(function(a){document.write(a+' ')})(i);
* when I started writing this question there were a number of answers describing how to fix the string to work with setTimeout, although they would technically work, they didn't include why they would work (because 'document.write("' + i + ' ");' evaluates i at the time of calling due to string concatenation, versus evaluating i at runtime like the previous version did), and they most certainly didn't mention that it's the bad old way of calling setTimeout.
try
var i = 1;
function timeout(){
document.write(i + ' ');
i++;
if (i == 5) return;
setTimeout(timeout, 1000);
}
timeout();
http://jsfiddle.net/nnJcG/1/
You have a problem with clousures, you can try this:
var timeout = function(){
var i = 0;
return function(){
document.write(i+ ' ');
i++;
if(i!==5)
setTimeout(timeout,1000);
};
}();
setTimeout(timeout,1000);
Here is the example in jsBin http://jsbin.com/uloyuc/edit
First of all, NEVER pass a string to setTimeout. Use a function, it's much cleaner.
Second, you have to "close over" the loop value. I bet this is what you want.
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
document.write(i + ' ')
}, i * 1000);
}(i));
}
See more about you a self executing function to close over a loop value here http://www.mennovanslooten.nl/blog/post/62
And just cause I love it, here is the equivalent in CoffeeScript whihc has the do keyword to help out with just this case.
for i in [0..4]
do (i) ->
setTimeout ->
document.write "#{ i } "
, i * 1000
You can also work with setInterval and clearInterval:
var i = 0;
var f = setInterval(function() {
if(i == 4) clearInterval(f);
document.write(++i + ' ');
}, 1000);
I think this code is very readable.
You could try like this:
var tick_limit = 5; // Or any number you wish representing the number of ticks
var counter = 0; // Or any number you wish
var timer_interval = 1000; // Interval for the counter
var timer;
function timerTick()
{
if(counter < tick_limit)
{
// Execute code and increase current count
document.body.innerHTML+=(counter + ' '); // Append the counter value to the body of the HTML page
counter++;
timer = setTimeout(timerTick,timer_interval);
}
else
{
// Reset everything
clearTimeout(timer);
counter = 0;
}
}
function startCounter()
{
clearTimeout(timer); // Stop current timer
timer = setTimeout(timerTick,timer_interval); // Start timer with any interval you wish
}
...
// Start timer when required
startCounter();
...
This way, calling the startCounter a number of times will result in a single timer executing the code
You're triggering five timeouts at the same time.
I like Pindatjuh's answer, but here's another fun way to do it.
This way starts the next timeout when the previous one is finished:
// Wrap everything in a self executing anonymous function so we don't pollute
// the global namespace.
//
// Note: Always use "var" statments or you will pollute the global namespace!
// For example "for(i = 0; i < 5; i++)" will pollute the global namespace
// unless you have "var i; for(i = 0; i < 5; i++)" or
// "for(var i = 0; i < 5; i++)" & all of that is not in the global namespace.
//
(function() {
// "i" will be available within doThis()
// you could also pass "i" as an argument
var i = 0,
doThis = function() {
// setTimeout can take an anonymous function
// or a regular function. This is better than
// eval-ing a string.
setTimeout(function() {
document.write(i + ' ');
++i;
// Do the function again if necessary
if (i < 5) doThis();
}, 1000);
}
// Let's begin!
doThis();
})();
Working Example