How to make loop not to continue until an event happens? - javascript

I am writing a function of a game:
function Game(){
while(true){
***
for(var i = 0; i < level; i++){
var color;
$(".btn").on("click", function(event) {
ButtonClickResponse(this.id);
color = this.id;
});
if(colorsOrder[i] != color){
GameOver();
return;
}
}
***
}
}
the "if statement" in the loop of function runs and increments "i" immediately many times when loop is started and doesnt wait for an above event to finish.
I searched for solving with "async await" and "promise" in google and stackoverflow, but didn't really understand how it worked so couldn't implemet it in my code.

This should work, although I didn't test it and you do things not in javascript way
async function Game() {
while (true) {
var level = 1;
$("#level-title").text("Level " + level);
var colorsOrder = [];
var randColor = GenerateRandomSquareColor();
colorsOrder.push(randColor);
ButtonClickResponse(randColor);
for (var i = 0; i < level; i++) {
var color;
// await for the click event
const event = await waitForClick($(".btn"));
// do stuff with the result
ButtonClickResponse(event.target.id);
color = event.target.id;
if (colorsOrder[i] != color) {
GameOver();
return;
}
level++;
}
}
}
function waitForClick(element) {
// create new promise
return new Promise((resolve) => {
const handler = function (event) {
// when button is clicked remove listener
element.off("click", handler);
// and resolve the promise
resolve(event);
};
// listen for click
element.on("click", handler);
});
}

Related

How to break loop in javascript?

const arrow = document.querySelector("#arrow");
const callAllPie = document.querySelector(".allPie");
eventList();
function eventList(e) {
arrow.addEventListener("click", showSkills);
}
function showSkills() {
let element = callAllPie;
for (let i = 0, n = 4; i < n; i++) {
const pie = document.createElement("div");
pie.classList.add("pie1");
callAllPie.appendChild(pie);
const rightDiv = document.createElement("div");
rightDiv.classList.add("slice-right1");
const leftDiv = document.createElement("div");
leftDiv.classList.add("slice-left1");
const percentDiv = document.createElement("div");
percentDiv.classList.add("percent1");
const numberDiv = document.createElement("div");
numberDiv.classList.add("number1");
numberDiv.innerHTML = "%99";
const nameDiv = document.createElement("div");
nameDiv.classList.add("name1");
nameDiv.innerHTML = "HTML";
pie.appendChild(rightDiv);
pie.appendChild(leftDiv);
pie.appendChild(percentDiv);
percentDiv.appendChild(numberDiv);
percentDiv.appendChild(nameDiv);
callAllPie.appendChild(pie);
}
}
I want to break the loop after the click event runs once, how do I do it? When run the click event, added pie div to my page but when I clicked again, it being created again.
You could simply remove the click listener at the beginning of your showSkills() function so no further clicks trigger an action:
function showSkills() {
arrow.removeEventListener('click', showSkills);
let element = callAllPie;
....
}
Or as Teemu points out, a much cleaner approach is to set once: true in the options parameter:
arrow.addEventListener("click", showSkills, {once: true});
You can make a boolean isLoopRunning and use it to see if the loop Is already running
const arrow = document.querySelector("#arrow");
const callAllPie = document.querySelector(".allPie");
eventList();
let isLoopRunning = false;
function eventList(e) {
arrow.addEventListener("click", showSkills);
}
function showSkills() {
let element = callAllPie;
isLoopRunning = !isLoopRunning;
for (let i = 0, n = 4; i < n; i++) {
if (isLoopRunning) {
// your code here
} else {
break;
}
}
}
The break statement terminates the current loop.
if condition satisfies requirement just use break keyword inside loop
for(let i=0; i<10; i++){
if(i==6){
console.log(i)
break;
}
console.log("hi")
}
"hi" will not print after 6 times
When using a status variable outside the function you will have the option to reset the status elsewhere in the code and have the loop run once again.
var showSkills = true;
// Runs once upon user click
function showSkills() {
if (showSkills) {
for {
/* Do the loop */
};
showSkills = false;
};
}
// If you need to run it again upon user click.
// E.g. attach it to some 'reset' button.
function reset() {
showSkills = true;
}

How do I iterate over an async function without completely skipping over all the steps in the function?

Here is the link to my repo's github page, so you can properly see what I mean.
I am currently having an issue with my triviaGame function when trying to make it recursive, but it's sort of "backfiring" on me in a sense.
You'll notice after you answer the first question, everything seems fine. It goes to the next question fine. After that though, it seems like the iterations of it double? The next answer it skips 2. After that, 4. And finally the remaining 2 (adding up to 10, due to how I am iterating over them).
How might I be able to correctly iterate over a recursive function, so it correctly calls all 10 times, and then returns when it is done?
Been struggling with this for hours, and just can't seem to get it to work. My javascript code is below, sorry for any headaches that it may give you. I know I make some questionable programming decisions. Ignore some of the commented out stuff, it's not finished code yet. I'm a beginner, and hope that once I learn what's going on here it will stick with me, and I don't make a stupid mistake like this again.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function triviaGame() {
const trivia = await getTrivia();
async function appendData() {
let totalAnswers = [
...trivia.results[0].incorrect_answers,
trivia.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
trivia.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(trivia.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
async function checkAnswer() {
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === trivia.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
triviaGame();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
triviaGame();
}, 3500);
}
});
});
}
checkAnswer();
appendData();
}
triviaGame();
Any/All responses are much appreciated and repsected. I could use any help y'all are willing to give me. The past 6 hours have been a living hell for me lol.
It's skipping questions once an answer is clicked because every time a button is clicked, another event listener is added to the button, while the original one is active:
On initial load: triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 1.
Answer button is clicked, triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 2.
Answer button is clicked, triviaGame() runs twice (from the 2 listeners attached) which makes checkAnswer() run twice where both invocations adds event listeners to each of the buttons.
Event listeners on buttons: 4.
etc.
To fix this, I moved the content of checkAnswer() outside of any functions so it only ever runs once. However, doing this, it loses reference to the upper scope variable trivia. To resolve this, I used the triviaData variable instead which checkAnswer() would have access to and I change references in appendData() to match this. Now, triviaGame() function only exists to call appendData() function inside it; there is little point in this so I merge the two functions together into one function, instead of two nested inside each other.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function appendData() {
triviaData = await getTrivia();
let totalAnswers = [
...triviaData.results[0].incorrect_answers,
triviaData.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
triviaData.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(triviaData.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === triviaData.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
appendData();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
appendData();
}, 3500);
}
});
});
appendData();
<div id="amount-correct"></div>
<h1 id="question"></h1>
<button id="answer-1"></button>
<button id="answer-2"></button>
<button id="answer-3"></button>
<button id="answer-4"></button>

"await" like similar function in JavaScript?

I'm having the function which is wait for sync after that it will load the content. Below function is working perfectly in firefox but not working in IE11
//Working in other browser and inserting the multiple records but not in IE
async function setup()
{
await Word.run(async(context)=> {
for (var i=0; i < 5; i++)
{
var controler = context.document.contentControls.getByTag("myTag"+i);
controler.load();
await context.sync();
controler.items[0].insertPargraph("Adding paragraph "+i);
}
}
)};
}
For IE11, below function is working perfectly for inserting only one record
//Working in IE for the only one record
function setUp()
{
Word.run(function (context){
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph 0")
});
})
}
Now problem is I want to iterate the loop for the contents, I have written the return function inside the forloop that the reason it is not working
//Below function is not working
function setUp()
{
Word.run(function (context){
for (var i=0; i < 5; i++)
{
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph 0")
});
}
})
}
How to write the await function for IE11 browsers. I have tried the goto Lable function but that also not working.
Your async version uses i with getTag and when adding the paragraph, but your subsequent code example doesn't. It matters for the solution.
Common Ground
You can create a promise chain, similar to my answer here but different enough it might be hard to apply that to your case. Basically, you start with a resolved promise (p), then use p = p.then(...) to build the chain.
If you don't need to use i's value
...then you can do it like this:
function setUp()
{
Word.run(function (context){
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(function() {
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph 0")
});
});
}
})
}
If you do need to use i's value
...then we need to bake it into the code since you have to use var (IE11 has let, but it doesn't have ES2015 semantics for for loops):
function setUp()
{
Word.run(function (context){
function doOne(index) {
// We use `index` below
var selectedTag = context.document.contentControls.getByTag("myTag" + index);
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph " + index)
});
}
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(doOne.bind(null, i));
}
})
}
Giving setUp a return value
Your async version assumes that Word.run returns a promise and that it expects its callback to return a promise. I can't find any documentation to support that, but then, the web documentation for this stuff appears to be truly amazingly bad.
If both of those assumptions are true, then to haev setUp return a promise, we'd only need to make small changes: return before Word.run and return p; at the end of the callback (see *** comments);
function setUp()
{
return Word.run(function (context){ // ***
function doOne(index) {
// We use `index` below
var selectedTag = context.document.contentControls.getByTag("myTag" + index);
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph " + index)
});
}
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(doOne.bind(null, i));
}
return p; // ***
})
}
But if Word.run doesn't return a promise, or doesn't expect a promise from its callback, that won't work and we have to create our own:
function setUp()
{
return new Promise(function(resolve, reject) { // ***
Word.run(function (context) {
function doOne(index) {
// We use `index` below
var selectedTag = context.document.contentControls.getByTag("myTag" + index);
context.load(selectedTag,'text');
return context.sync().then(function()
{
controler.items[0].insertPargraph("Adding paragraph " + index)
});
}
var p = Promise.resolve();
for (var i = 0; i < 5; i++)
{
p = p.then(doOne.bind(null, i));
}
p.then(resolve).catch(reject); // ***
})
});
}
I think what you are trying to achieve is to chain the sync() calls together so that the callback in Word.run only resolves when everything is syncd. You can do this using Promise.all() to generate a promise resolving when all provided promises have resolved.
function setUp() {
Word.run(function(context) {
const promises = [];
for (var i = 0; i < 5; i++) {
var selectedTag = context.document.contentControls.getByTag("myTag");
context.load(selectedTag, 'text');
let p = context.sync().then(function() {
controler.items[0].insertPargraph("Adding paragraph 0")
});
promises.push(p);
}
return Promise.all(promises);
})
}

Event handler function out of scope in for

The handler inside of my for loop might be out of scope and only prints "Last Event added" in console but doesn't loop through each element in the array. No sure where I'm going wrong here, but I need help attaching the event listener to each.
(function () {
if (document.addEventListener) {
this.addEvent = function (elem, type, fn) {
elem.addEventListener(type, fn, false);
};
} else if (document.attachEvent) {
this.addEvent = function (elem, type, fn) {
var bound = function () {
return fn.apply(elem, arguments);
};
elem.attachEvent("on" + type, bound);
return bound;
};
}
if (document.getElementsByClassName) {
this.getClass = function (className) {
return document.getElementsByClassName(className);
};
} else if (document.querySelectorAll) {
this.getClass = function (className) {
return document.querySelectorAll("." + className);
};
}
var elem = getClass("images"),
display = getClass("display_box"),
rolloverImage = function (e) {
console.log("Event 'rolloverImage' triggered");
};
console.log(display);
console.log(elem);
console.log(elem.length);
for (var i = 0; i < elem.length; i++) {
document.addEvent(elem[i], "mouseover", rolloverImage);
if (i = elem.length) {
console.log("Last event added");
} else {
console.log("Event added to " + elem[i]);
}
};
})();
A fiddle is available here: http://jsfiddle.net/bNL5C/
if you are wanting your addEvent function to be on the document object use document.addEvent = not this.addEvent = as that is putting it on the global object window
Also it doesnt loop through all of them because you are assigning i to the elem array length instead of comparing.
if (i = elem.length) {
should be
if (i == elem.length) {
Because of this on the first iteration through the loop causes i to be the value of elem.length and since i is now not < elem.length your loop exits.
One problem is that in your for loop, the if (i = elem.length) will always be true, as you are using a single assignment =, and changing the value of i. This needs to be changed to if (i == elem.length) or perhaps you'd prefer to use ===.

Some what simple script. Can see what's wrong

Here is the link to the page with the script.
http://signsourceak.com/index1.html
Here is my script and for some reasons all the functions fire with out mouse over. Can anyone tell me what is wrong with my script
window.onload = sliding;
var tags = new Array('tag1','tag2','tag3','tag4','tag5','tag6','tag7','tag8');// List of headings
var pics = new Array('popout1','popout2','popout3','popout4','popout5','popout6','popout7','popout8');// list of images that slide out
function sliding(){ // assing event
for(var i=0; i< tags.length; i++){
document.getElementById(tags[i]).onmouseover = slideout(tags[i],pics[i]); // <-- The Problem is Here Function runs with out the actual event
document.getElementById(tags[i]).onmouseout = slidein(tags[i],pics[i]);
//alert('this worked,'+ tags[i] + pics[i]);
}
}
function slideout(hid,picid){
document.images[picid].style.visibility = "visible";
document.images[picid].style.MozOpacity = 0.7;// need browser compatability
moveout(hid,picid);
}
function moveout(hid,picid){
if(currpos(picid) > 0){
document.images[picid].style.top = currpos(picid) - 1 + "px";
setTimeout(moveout,10);
}else{
clearTimeout(moveout);
}
function currpos(element){
return document.getElementById(element).offsetTop;
}
}
function slidein(hid,picid){
document.images[picid].style.MozOpacity = 0.5;// need browser compatability
movein(hid,picid);
}
function movein(hid,picid){
if(currpos(picid) < 210){
document.images[picid].style.top = currpos(picid) + 1 + "px";
setTimeout(movein,10);
}else{
clearTimeout(movein);
document.images[picid].style.visibility = "hidden";
}
function currpos(element){
return document.getElementById(element).offsetTop;
}
}
that is not how to use clearTimeOut.
setTimeout returns a timer id that have to be passed to clearTimeOut:
var timer = setTimeout( fn, 10 );
clearTimeout( timer);
You are assigning the result of slideout() and slidein() as the handlers. You also need to isolate the closure variables; self calling functions will make sure the the i looping variable is not shared by all the event handlers
function sliding(){ // assing event
for(var i=0; i< tags.length; i++){
document.getElementById(tags[i]).onmouseover =(function(index){
return function() {
slideout(tags[index],pics[index]);
}
})(i);
document.getElementById(tags[i]).onmouseout = (function(index){
return function() {
slidein(tags[index],pics[index]);
}
})(i);
}
}
One problem is here:
function sliding(){ // assing event
for(var i=0; i< tags.length; i++){
document.getElementById(tags[i]).onmouseover = slideout(tags[i],pics[i]); // <-- The Problem is Here Function runs with out the actual event
document.getElementById(tags[i]).onmouseout = slidein(tags[i],pics[i]);
//alert('this worked,'+ tags[i] + pics[i]);
}
}
In that loop, you're calling the "slideout" and "slidein" functions, though it's obvious that that's not what you want to do. What you want is to assign a function that calls "slideout" or "slidein" the appropriate way. To do that, you'll need another layer of function:
function makeHandlers(index) {
return {
'out': function() { slideout(tags[index], pics[index]; },
'in': function() { slidein(tags[index], pics[index]; }
};
}
function sliding() {
for (var i = 0; i < tags.length; ++i) {
var handlers = makeHandlers(i), tag = document.getElementById(tags[i]);
tag.onmouseover = handlers.in;
tag.onmouseout = handlers.out;
}
}
As #BiAiB notes, your calls to "setTimeout" and "clearTimeout" need some attention too.

Categories