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];
}
Related
I thought making a simple function where if you click on a button a number will show up inside of a paragraph. And if you continue to click on the button the number inside the paragraph tag will increase. However, I'm getting an error message saying that getElementsByTagName is not a function. Here is the code on jsfiddle, I know there is something simple that I'm doing wrong but I don't know what it is.
HTML
<div class="resist" id="ex1"><h2>Sleep</h2><p></p><button>Resist</button></div>
<div class="resist" id="ex2"><h2>Eat</h2><p></p><button>Resist</button></div>
Javascript
var count = 0;
var resist = document.getElementsByClassName('resist') ;
for(var i = 0; i < resist.length; i++)
{ var a = resist[i];
a.querySelector('button').addEventListener('click', function(a){
count +=1;
a.getElementsByTagName('p')[0].innerHTML = count;
});
}
You are overwriting a variable with event object passed into event handler. Change the name to e maybe, or remove it altogether as you are not using it anyway:
a.querySelector('button').addEventListener('click', function(e /* <--- this guy */) {
count += 1;
a.getElementsByTagName('p')[0].innerHTML = count;
});
Another problem you are going to have is classical closure-in-loop issue. One of the solutions would be to use Array.prototype.forEach instead of for loop:
var count = 0;
var resist = Array.prototype.slice.call(document.getElementsByClassName('resist'));
// ES6: var resist = Array.from(document.getElementsByClassName('resist'));
resist.forEach(function(a) {
a.querySelector('button').addEventListener('click', function(e) {
count += 1;
a.getElementsByTagName('p')[0].innerHTML = count;
});
});
vars in Javascript are function scoped, so you must wrap your event listener binding in a closure function to ensure the variable you're trying to update is correctly set.
(Note: I've renamed a to div in the outer function and removed the arg from the inner click function).
var count = 0;
var resist = document.getElementsByClassName('resist') ;
var div;
for(var i = 0; i < resist.length; i++)
{
div = resist[i];
(function(div){
div.querySelector('button').addEventListener('click', function(){
count +=1;
div.getElementsByTagName('p')[0].innerHTML = count;
});
})(div);
}
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).)
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
var curValue = myElements[i].getAttribute('innerId')
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + curValue);
}, false);
}
when mouse over, every element, instead of showing a different value for curValue, a constant value (the last iteration value) is displayed.
what am i doing wrong here?
There is no different scope inside blocks like for in JavaScript, so when your mouseover event is triggered, it will alert the current variable value which was set in the last iteration.
You can just use this inside your callback function to get the attribute of the object which the event was triggered.
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + this.getAttribute('innerId'));
}, false);
}
The general issue here is the closure in Javascript. This happens when using variable (in this case curValue) not defined within the callback function.
I recommend reading answers about JS closures here.
I'm just starting out with AJAX and I'm trying to get a variable to be set inside a for loop. Then I want to call that variable later and use it's value.
Of course this would be synchronous, requiring the scripts to stop executing in order to run the loop before returning the new value of the function.
I'm hoping someone knows a better way to get the value from the for loop AFTER the for loop has run and use it in my code directly after that.
I would prefer not to use the setTimeout() hack to bypass this issue (it is a hack after all).
var getCount = function getCount(res) {
count = { active: 0, closed: 0 }; //Variable defined here
for(i=0; i<=res.length; i++) {
if(res[i].status == 'active') {
count.active ++;
} else { count.closed ++; }
}
return count; //And returned here
};
getCount(result);
console.log(count); //Here's where I need the result of the for loop
//Currently this outputs the count object with both properties set to 0;
I am not sure what AJAX has to do with your issue.
You are not assigning the result of the getCount function to the count variable (Unless you intended the count variable to be global, but in that case you need to define it before the getCount function definition).
Change this line:
getCount(result);
to this:
var count = getCount(result);
And you should be alright. :)
I would also suggest, when declaring variables, always declare them with var. In your case:
var count = { active: 0, closed: 0};
I don't know why you mention AJAX since there is nothing async about your code.
From what I see in your sample I don't see what all the difficulty is about.
Just use it as any other function.
function getCount(res) {
var count = { active: 0, closed: 0 }; //Variable defined here
for(i=0; i<=res.length; i++) {
if(res[i].status == 'active') {
count.active ++;
} else { count.closed ++; }
}
return count; //And returned here
};
console.log(getCount(result)); //Here's where I need the result of the for loop
First off, you had an extra = sign that was over-extending your for loop. I don't know if this answers your asynchronous issue, but here is how I would do it:
// sample object
var result = [
{status:"active"},
{status:"not-active"},
{status:"active"}
];
// kick off the function to get the count object back
var counts = getCount(result);
console.log(counts);
function getCount(res) {
var count = { active: 0, closed: 0 }; //Variable defined here, make sure you have var to keep it from going global scope
for(i=0; i<res.length; i++) { //here you had a wrong "="
if(res[i].status === 'active') {
count.active ++;
} else { count.closed ++; }
}
return count; //And returned here
}
Example here.
I have some code in jQuery that iterate through children in div using each().
Every text inside is splitted into words. Each word is processed with 'for' loop.
This function can take a long time and can freeze the browser so...
Is there a way to create asynchronous loop inside another asynchronous loop but one is waiting for other to finish?
Could anyone tell me the right direction?
I came up with something like this:
var queue = [];
var nlevel = 0;
function work() {
nlevel = queue.length-1;
var arr = queue[nlevel][0];
var process = queue[nlevel][1];
var cnt = queue[nlevel][2];
var item = arr[cnt];
process.apply(item);
cnt++;
queue[nlevel][2] = cnt;
if (cnt < arr.length) {
setTimeout(work, 1);
} else {
if (queue.length>1) {
queue.pop();
setTimeout(work, 1);
}
}
}
function each(arr, process) {
queue.push([arr, process, 0]);
setTimeout(work, 1);
}
each(['one', 'two', 'three'], function() {
alert(this);
each([1, 2, 3, 4], function() {
alert(this);
});
});
but It has some major bug and I couldn't fix it.
You can use Web Workers to run multiple scripts in background threads. But they are not supported in every browsers. See this article from Mozilla or simple ask Google: https://developer.mozilla.org/En/Using_web_workers
You can use SetTimeout(0,...) periodically to "yield" control to the browser to prevent freezing the browser (but it will not execute any faster, in fact it will probably be slightly slower).
See this answer for an example of the technique, I can't be more specific without seeing your code.
One way you could do this is to create a queue of work items to be processed:
var queue = [];
Place items to be processed in this queue instead of processing right away:
queue.push(item);
Then start a timer loop for processing items:
var delayBetweenItems = 10;
var processItemFromQueue = function() {
if (queue.length) {
item = queue.shift();
doStuff(item);
setTimeout(delayBetweenItems, processItemFromQueue);
}
};
setTimeout(processItemFromQueue, delayBetweenItems);
Assuming that your current code is something similar to this:
function processWord(word, i, j) {
// do something with the current word, where
// (if it's needed) i is the index into the elements
// and j is the index into the current element's words
}
$("#divId").children().each(function(i) {
var words = $(this).text().split(" "),
j;
for(j = 0; j < words.length; j++) {
processWord(words[j], i, j);
}
});
You can rewrite that to do both the outer (i) and inner (j) loops with setTimeout():
// assumes the same processWord() function as above
(function (){
var $items = $("#divId").children(),
words,
i, j,
iMax, jMax;
function doIteration() {
if (j === jMax) {
if (++i === iMax)
return;
// outer loop processing
words = $($items[i]).text().split(" ");
jMax = words.length;
j = 0;
}
if (j < jMax) {
// inner loop processing
processWord(words[j], i, j);
j++;
}
setTimeout(doIteration, 1);
}
iMax = $items.length;
i = j = jMax = -1;
doIteration();
})();
Working demo: http://jsfiddle.net/sp8Wr/
The doIteration() function simulates a nested loop by processing some counters appropriately and calling itself via setTimeout() to do the next iteration. The immediately-executed-anonymous-function wrapping the whole thing is not essential to the process, it is there just to restrict the scope of the variables.
Yes this could probably be done much better, but this is just what I came up with on the fly - obviously you'll modify it as appropriate for your own processing.
(If you don't care what order the words get processed in as long as the processWord() function gets the correct values of i and j associated with each word this can easily be made much tidier, but I don't have time to do a neater version that processes in order.)