Int from for loop not working in function - JS - javascript

I am pretty new to Javascript and have noticed this odd issue come up.
var dispatchMouseEvent = function(target, var_args) {
var e = document.createEvent("MouseEvents");
e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));
target.dispatchEvent(e);
};
var Level1Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry "); //GETTING LEVEL 1 CATS
var Level1CatsLen = Level1Cats.length; //GETTING LEVEL 1 CAT LEN
for (i = 0; i <= Level1CatsLen-1; i++) {
var ID1 = Level1Cats[i].id;
var temp1 = Level1Cats[i].innerHTML;
temp1.replace(/&/gi, "&").replace(/<[^>]*>/gi, "");
function GoToLevel2(callback) { //GO TO NEXT LEVEL!
dispatchMouseEvent(Level1Cats[i], "mouseover", true, true);
dispatchMouseEvent(Level1Cats[i], "click", true, true);
}
function GetLevel2() { //GET NEXT LEVEL
var Level2Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry");
return Level2Cats.length;
}
setTimeout(function() { //RUN IT WITH TIMING
GoToLevel2();
}, 100);
var Level2CatsLen = GetLevel2();
}
When the code is executed it gives me an error (Cannot read property 'dispatchEvent' of undefined)
I know this is because the i in the function does not seem to work. If I simply replace it with an int value of 1 it will execute and click cat 1, 16 times, as expected..
I would have thought this should work, any ideas how I can work around it?

Inside the loop, a closure GoToLevel2 is created, closing over the variable i, when i is 1. Then the loop runs through, i is incremented to 2, and the loop is terminated.
Then your setTimeout fires after 100ms, and invokes your closure. It still remembers that there was once a variable i, but it now contains 2. Level1Cats[2] is undefined, and you get an error.
The standard solution is to enclose the contents of the loop into another self-evaluating function that will not close over i:
for (i = 0; i <= Level1CatsLen-1; i++) {
(function(i) {
// ...
})(i);
}
(Note also that setTimeout(function() { GoToLevel2(); }, 200) is identical to, but less efficient than setTimeout(GoToLevel2, 200).)

Related

setTimeout in a for loop with array as an argument misbehaving

I have been working on my second game, which works as follows:
There is a button and its position changes randomly every x seconds
The x seconds is controlled by setTimeout
Whenever the user clicks on the button their score increments by 10
When the score reaches 30 startGame calls Initialize, which creates a new button using JavaScript and pushes the button to an array called numOfBox
The control passes back to startGame with the updated numOfBox, which now has two buttons and changes the top/left coordinates every x seconds
The function is being called by setTimeout multiple times every x seconds instead of once every x seconds. How do I call the function only once every x seconds?
var box=document.getElementById('boxId');
var main=document.getElementById('Maincont');
var timeout=[];
var cnt=0;
var scr=0;
var levelCnt=30;
var divcnt=0;
var numOfbox=[];
var fcnt=0;
function createDiv(Name,Width,Height,Background,Margin,Padding) {
var t=t+divcnt;
divcnt+=1;
var Name=Name+divcnt;
var Nameid='boxId'+divcnt;
Name=document.createElement('button');
Name.id=Nameid;
console.log('IN CREATEDIV :-'+ Nameid+':::'+Name+' cnt '+ cnt);
Name.style.width=Width;
Name.style.height=Height;
Name.style.background=Background;
Name.style.margin=Margin;
Name.style.padding=Padding;
Name.style.boxSizing='border-box';
Name.style.position='absolute';
Name.style.top='10px';
Name.style.left='10px';
Name.style.color='white';
Name.style.textAlign='center';
Name.style.fontSize='15px';
Name.style.textDecoration='none';
Name.style.cursor='pointer';
Name.disabled=true;
t=document.createTextNode('HIT ME');
Name.appendChild(t);
Name.addEventListener('click', function() {
this.style.background='black';
scr+=10;
cnt+=10;
this.innerHTML= 'SCORE = ' +String(scr);
});
var b=document.getElementById('Maincont');
// FIRST HIT ME BOX IS READY NOW....
b.appendChild(Name);
// Array numOfbox loaded with all the Box id's.
numOfbox.push(Name.id);
console.log('PUSHING DIV:-'+ Name.id + " IN ARRAY :=" +numOfbox);
// creatediv is called when score is scr%30==0.
if (numOfbox.length > 1) {
return Name.id;
}
}
function Initialize() {
if (main.childNodes.length < 5) {
if ((cnt > 0) && (cnt %30 ==0)) {
var id='targetCont'+divcnt;
divcnt+=1;
boxId=createDiv(id,'130px','50px','black','0px','0px');
console.log('Inside Initialize:-'+id+' cnt '+ cnt);
cnt=0;
}
startGame();
}
}
function startGame() {
var d=new Date();
var t=d.getMinutes()+':'+d.getSeconds();
fcnt+=1;
console.log('TIME IS:-'+ t + " cnt:-" +fcnt+' INSIDE STARTGAME:-'+numOfbox+':'+numOfbox.length+ ' CNT'+cnt);
for (var i=0 ; i < numOfbox.length ; i++) {
if ((cnt > 0) && (cnt %30 ==0)) {
Initialize();
} else {
console.log('STARTING GAME FOR DIV:='+numOfbox[i]);
var box=document.getElementById(numOfbox[i]);
console.log(box);
box.disabled=false;
var max=500;
var min=10;
var topRand=(Math.floor(Math.random() * (max-min+1) + min));
var max=1200;
var min=10;
var leftRand=(Math.floor(Math.random() * (max-min+1) + min));
box.style.background='black';
box.style.top=topRand+'px';
box.style.left=leftRand+'px';
console.log('CNT:='+cnt);
timeout=setTimeout(startGame,10000,numOfbox);
}
}
}
function stopGame() {
clearTimeout(timeout);
console.log('IN STOPGAME:-'+timeout+' length '+timeout.length);
for (var i=0 ; i < numOfbox.length ; i++) {
console.log(timeout[i]);
clearTimeout(timeout[i]);
var box=document.getElementById(numOfbox[i]);
box.style.background='red';
box.style.top='10px';
box.style.left='10px';
}
timeout=[];
}
I would prefer to do this in JavaScript and not in ECMA5/6 or jQuery, or using arrow functions. I can provide the rest of the code if needed.
I noticed a couple of issues with your code.
You are calling setTimeout in a loop. So if you have, say, numOfbox.length === 15, setTimeout will be called 15 times around 10,000 ms from when you set it. Could this be the reason you're seeing the more calls to startGame than you thought?
I see the variable numOfbox, but since it's not declared in the startGame function I'll have to assume that it's in the parent scope. So, in the line where you do
timeout[i] = setTimeout(startGame, 10000, numOfbox);
Realize that since numOfbox is in a higher level scope and startGame does not take any parameters, the numOfbox parameter (the 3rd argument in setTimeout) is really not going anywhere: the numOfbox variable actually uses is coming from the parent scope. This may be ok, but you should consider what is happening here.

setTimeout within 'while' causes a browser crash. How can I avoid it?

I am new to coding and I want to built a Text Adeventure Game with HTML , CSS and Javascript. I want to show many peaces of text from an array with a certain time between each text. I tried different methods to avoid an infinite loop because of setTimeout, but I didn't figure out how to apply it on my code.
Here's my code, that causes the crash:
var iCounterText = 0;
var verzog = function() {
document.getElementById('toggleText').insertAdjacentHTML('beforeBegin', '<br>--------------<br>');
document.getElementById('toggleText').insertAdjacentHTML('beforeBegin', part1[iCounterText]);
iCounterText = iCounterText + 1;
playaudio();
}
function forwardingLinks() {
while (iCounterText < part1.length - 1) {
setTimeout(verzog, 500); // Here is the problem //
}
document.getElementById('buttonLinks').innerHTML = part1[part1.length - 1];
}
The following code works fine, but then there is no timeout between the text:
function forwardingLinks() {
while (iCounterText < part1.length - 1) {
verzog();
}
document.getElementById('buttonLinks').innerHTML = part1[part1.length - 1];
}
Edit:
This is my new code with "setInterval". Problem: Value is added by 1, but the function is not using the part2 Array. Instead it is using part1 Array again, although the partvalue already contains part2 array.
var part1 = [ //Texte und Antworten
'Hallo?',
'Test?',
'What',
'hello',
'--------------',
'Was?'
];
var part2 = [ //Texte und Antworten
'part2 goes on....',
'bla bla',
'blablabla'
];
var iCounterText = 0;
var value = 1;
var partvalue = eval("part" + value);
function forwardingLinks() {
var verzog = setInterval(function(){
if(iCounterText < partvalue.length-2){
++iCounterText;
toggleText.insertAdjacentHTML('beforeBegin', '<br>--------------<br>');
toggleText.insertAdjacentHTML('beforeBegin', partvalue[iCounterText]);
playaudio();
}else{
buttonLinks.innerHTML = partvalue[partvalue.length-1];
++value;
iCounterText=0;
clearInterval(verzog);
}
},500);
}
You are misunderstanding the flow of your while loop. JavaScript runs in a single-threaded environment. That means that your calls to the verzog function won't run until the forwardingLinks function completes, but your forwardingLinks function won't ever complete because you have a while loop that is dependent on a counter that is never increased, because verzog hasn't run yet.
Change your while loop, so that the iCounterText variable gets incremented from within the loop, so the loop can end and then the calls to verzog that have stacked up in the event queue can start to run.
Additionally, since you are using a numeric counter, a regular for loop would be better than an while loop because the loop's step value (++iCounterText) is required:
for(var iCounterText = 0; iCounterText < part1.length; ++iCounterText) {
setTimeout(verzog, 500);
}
As an aside from your main problem, it is very inefficient to repeatedly scan the DOM for the same element over and over, as you are doing in your verzog function. Instead, just get the DOM reference once and store it in a variable that can be reused:
// Declare a variable in a scope that is accessible throughout your code
var toggleText = null, buttonLinks = null;
// Set up a callback that runs after the DOM is ready
window.addEventListener("DOMContentLoaded", function(){
// Scan the DOM for the element(s) you'll be needing
toggleText = document.getElementById('toggleText');
buttonLinks = document.getElementById('buttonLinks');
});
function verzog() {
// Now, you can just refer to the DOM element you've already found:
toggleText .insertAdjacentHTML('beforeBegin', '<br>--------------<br>');
toggleText .insertAdjacentHTML('beforeBegin', part1[iCounterText]);
playaudio();
}
function forwardingLinks() {
for(var iCounterText = 0; iCounterText < part1.length; ++iCounterText) {
setTimeout(verzog, 500);
}
// Now, you can just refer to the DOM element you've already found:
buttonLinks.innerHTML = part1[part1.length - 1];
}

How to slow down a loop with setTimeout or setInterval

I have an array called RotatorNames. It contains random things but let's just say that it contains ["rotatorA","rotatorB","rotatorC"].
I want to loop through the array, and for each item i want to trigger a click event. I have got some of this working, except that the everything get's triggered instantly. How can i force the loop to wait a few seconds before it continues looping.
Here's what i have.
function Rotator() {
var RotatorNames = ["rotatorA","rotatorB","rotatorC"];
RotatorNames.forEach(function(entry){
window.setTimeout(function() {
//Trigger that elements button.
var elemntBtn = $('#btn_' + entry);
elemntBtn.trigger('click');
}, 5000);
});
}
You can run this to see what my issue is. http://jsfiddle.net/BxDtp/
Also, sometimes the alerts do A,C,B instead of A,B,C.
While I'm sure the other answers work, I would prefer using this setup:
function rotator(arr) {
var iterator = function (index) {
if (index >= arr.length) {
index = 0;
}
console.log(arr[index]);
setTimeout(function () {
iterator(++index);
}, 1500);
};
iterator(0);
};
rotator(["rotatorA", "rotatorB", "rotatorC"]);
DEMO: http://jsfiddle.net/BxDtp/4/
It just seems more logical to me than trying to get the iterations to line up correctly by passing the "correct" value to setTimeout.
This allows for the array to be continually iterated over, in order. If you want it to stop after going through it once, change index = 0; to return;.
You can increase the timeout based on the current index:
RotatorNames.forEach(function(entry, i) {
window.setTimeout(function() {
//Trigger that elements button.
var elemntBtn = $('#btn_' + entry);
elemntBtn.trigger('click');
}, 5000 + (i * 1000)); // wait for 5 seconds + 1 more per element
});
Try:
var idx = 0;
function Rotator() {
var RotatorNames = ["rotatorA", "rotatorB", "rotatorC"];
setTimeout(function () {
console.log(RotatorNames[idx]);
idx = (idx<RotatorNames.length-1) ? idx+1:idx=0;
Rotator();
}, 5000);
}
Rotator();
jsFiddle example
(note that I used console.log instead of alert)
Something like this should do what you're after:
function Rotator(index){
var RotatorNames = ["rotatorA","rotatorB","rotatorC"];
index = (index === undefined ? 0 : index);
var $btn = $("#btn_"+RotatorNames[index]);
$btn.click();
if(RotatorNames[index+1]!==undefined){
window.setTimeout(function(){
Rotator(index+1);
}, 500);
}
}

Update happens only on the last row, instead of first

function createTextFields(obj) {
for (var i = 0; i < obj.length; i++) {
var dataDump = {};
for (var key in obj[i]) {
var textField = Ti.UI.createTextField(pm.combine($$.labelBrown, {
left: 200,
height:35,
value:obj[i][key],
width:550,
keyboardType:Ti.UI.KEYBOARD_NUMBER_PAD,
layout:'horizontal',
backgroundColor:'transparent',
id:i
}));
dataDump[key] = textField.value;
var callback = function (vbKey) {
return function (e) {
dataDump[vbKey] = e.source.value;
};
}(key);
}
globalData.push(dataDump);
}
}
I am using the simlar code for Adding the data and it works fine. I posted the problem yesterday and it got resolved...
Last Object is always getting updated?
Now when i go to edit page, it shows me four text fields or number of text fields added... now when i edit something and click on save... the value get's updated on the fourth or the last TextFields Object...
Don't define functions inside loops. Computationally expensive and leads to problems, like this one. Here's a fix that should solve it:
function createTextFields(obj) {
var callback = function (vbKey, localDump) {
return function (e) {
localDump[vbKey] = e.source.value;
};
}
var i;
var max = obj.length;
for (i = 0; i < max; i++) {
var dataDump = {};
for (var key in obj[i]) {
dataDump[key] = textField.value;
var callBackInstance = function(keyn, dataDump);
}
globalData.push(dataDump);
}
}
JavaScript does not have block level scope, so your variables dataDump and callback, though "declared" inside for-loops actually belong to the function. As in, you're saving a value to dataDump, then you're overwriting it, each time you go through the loop. Which is why finally only the code that operated on the last value remains.
Take a look at What is the scope of variables in JavaScript? too.

How to stop all timeouts and intervals using javascript? [duplicate]

This question already has answers here:
javascript: Clear all timeouts?
(13 answers)
Closed 6 years ago.
I'm working on an ajax web appliation which contains many running timeouts and intervals. And now I need to clear all running timeouts and intervals sometimes. Is there a simple way to stop everything without need to store every timeout and interval ID and iterate through them and clear them?
Sometimes it's possible to save the timer Id / Handle to clear it later which would be the best solution. So this is a second best. But I wanted to give a better understanding of what's going on. It basically grabs the highest timer id and clears everything less than that. But it's also possible to clear other timers that you do not want to clear!
It is a little hackish, so be warned!
// Set a fake timeout to get the highest timeout id
var highestTimeoutId = setTimeout(";");
for (var i = 0 ; i < highestTimeoutId ; i++) {
clearTimeout(i);
}
Updated answer after reading the duplicate I closed this question with -
It works and tested in Chrome on OSX
// run something
var id1 = setInterval(function() { console.log("interval", new Date())}, 1000);
var id2 = setTimeout(function() { console.log("timeout 1", new Date())}, 2000);
var id3 = setTimeout(function() { console.log("timeout 2", new Date())}, 5000); // not run
setTimeout(function() { console.log("timeout 3", new Date())}, 6000); // not run
// this will kill all intervals and timeouts too in 3 seconds.
// Change 3000 to anything larger than 10
var killId = setTimeout(function() {
for (var i = killId; i > 0; i--) clearInterval(i)
}, 3000);
console.log(id1, id2, id3, killId); // the IDs set by the function I used
NOTE: Looked at window objects that had a typeof number - funnily enough IE assigns an 8 digit number, FF a single digit starting with 2
Here is a workaround.
window.timeoutList = new Array();
window.intervalList = new Array();
window.oldSetTimeout = window.setTimeout;
window.oldSetInterval = window.setInterval;
window.oldClearTimeout = window.clearTimeout;
window.oldClearInterval = window.clearInterval;
window.setTimeout = function(code, delay) {
var retval = window.oldSetTimeout(code, delay);
window.timeoutList.push(retval);
return retval;
};
window.clearTimeout = function(id) {
var ind = window.timeoutList.indexOf(id);
if(ind >= 0) {
window.timeoutList.splice(ind, 1);
}
var retval = window.oldClearTimeout(id);
return retval;
};
window.setInterval = function(code, delay) {
var retval = window.oldSetInterval(code, delay);
window.intervalList.push(retval);
return retval;
};
window.clearInterval = function(id) {
var ind = window.intervalList.indexOf(id);
if(ind >= 0) {
window.intervalList.splice(ind, 1);
}
var retval = window.oldClearInterval(id);
return retval;
};
window.clearAllTimeouts = function() {
for(var i in window.timeoutList) {
window.oldClearTimeout(window.timeoutList[i]);
}
window.timeoutList = new Array();
};
window.clearAllIntervals = function() {
for(var i in window.intervalList) {
window.oldClearInterval(window.intervalList[i]);
}
window.intervalList = new Array();
};
It works for set/clear timeout/interval functions called after these lines are executed. Try and see it works:
setInterval('console.log(\'a\')', 1000);
setInterval('console.log(\'b\')', 500);
setInterval('console.log(\'c\')', 750);
setTimeout('clearAllIntervals()', 10000);
Proxying does the magic.
var noofTimeOuts = setTimeout('');
for (var i = 0 ; i < noofTimeOuts ; i++) clearTimeout(i);
var max = setTimeout(function(){ /* Empty function */ },1);
for (var i = 1; i <= max ; i++) {
window.clearInterval(i);
window.clearTimeout(i);
if(window.mozCancelAnimationFrame)window.mozCancelAnimationFrame(i); // Firefox
}
There's nothing built-in, but it's pretty easy to blast through all currently outstanding deferred execution functions by calling this clearAll() function:
function clearAll() {
for (var i = setTimeout(function() {}, 0); i > 0; i--) {
window.clearInterval(i);
window.clearTimeout(i);
if (window.cancelAnimationFrame) window.cancelAnimationFrame(i);
}
}
If you are in charge of the page you run, and can wrap the native deferred execution functions in wrappers that do the house keeping for of course equip each setter function with a corresponding .clearAll() too:
(function(deferFunctions) {
for (var setter in deferFunctions) (function(setter, clearer) {
var ids = [];
var startFn = window[setter];
var clearFn = window[clearer];
function clear(id) {
var index = ids.indexOf(id);
if (index !== -1) ids.splice(index, 1);
return clearFn.apply(window, arguments);
}
function set() {
var id = startFn.apply(window, arguments);
ids.push(id);
return id;
}
set.clearAll = function() { ids.slice(0).forEach(clear); };
if (startFn && clearFn) {
window[setter] = set;
window[clearer] = clear;
}
})(setter, deferFunctions[setter]);
})(
{ setTimeout: 'clearTimeout'
, setInterval: 'clearInterval'
, requestAnimationFrame: 'cancelAnimationFrame'
});
To try that it works, you could then try doing this, for instance, which will remain silent, as none of the callbacks end up firing before they're cancelled again:
// Some example timers of all types:
requestAnimationFrame(console.error);
setInterval(console.info, 1000, 'interval');
setTimeout(alert, 0, 'timeout');
// Now you can clear all deferred functions
// by execution type, whenever you want to:
window.setTimeout.clearAll();
window.setInterval.clearAll();
window.requestAnimationFrame.clearAll();
A little hack added to Gokhan Ozturk's answer
If you are using third party libraries which uses Timeouts and Intervals then they will also be cleared, so I added one parameter to notify function that this interval is to be push'ed or not to array.
window.setTimeout = function(code, delay, toBeAdded) {
var retval = window.oldSetTimeout(code, delay);
var toBeAdded = toBeAdded || false;
if(toBeAdded) {
window.timeoutList.push(retval);
}
return retval;
};
... // likewise for all functions.
You might be better off creating a scheduler. Take a look at this approach by Nader Zeid:
https://www.onsip.com/blog/avoiding-javascript-settimeout-and-setinterval-problems
It's an approach that help create some determinacy (because "the time interval argument of each of those functions really only establishes that the given function will execute after at least that amount of time. So a timed event can miss its target by literally any amount of time.").
Specifically, to the question you raise here, you can easily add and remove functions from the queue. While this response is long after the question was raised, hopefully it's helpful to any who find themselves struggling with Timeouts and Intervals.
You cannot clear any timeouts and intervals you don't know about.
You'd need something like getTimeoutList which isn't in the DOM3 spec, or even planned, AFAIK.
The previous proxying trick is nice, but if you have a lot of timeouts and intervals, I would not fill the arrays with consecutive numbers [1,2,3....], but with intervals. For example, instead of having [1,2,3,7,8,9], you would have maybe something like ['1-3','7-9'] or [[1,3],[7,9]], as a memory optimization. Of course this trick is only suited if you have a lot of timeouts and intervals and also if you would not stop arbitrary intervals that often.

Categories

Resources