How to break loop in javascript? - 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;
}

Related

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

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);
});
}

I converted this forEach code to for loop and the code is not working

Clicking the red exit button removes the player from the forEach code, but not for loop.
You click the blue button, next you click the red exit button to remove the player.
How would I get the for loop code to work the same as the forEach code?
This code is working.
https://jsfiddle.net/n1t3kjdw/
function removePlayerHandler(evt) {
const el = evt.target;
const container = el.closest(".container");
const wrapper = container.querySelectorAll(".wrap");
wrapper.forEach(function(wrapper) {
if (wrapper.player) {
return removePlayer(wrapper);
}
});
}
What did I do wrong here? https://jsfiddle.net/rbwsL8hf/
Why is this code not working, what needs to be fixed?
function removePlayerHandler(evt) {
const el = evt.target;
const container = el.closest(".container");
const wrappers = container.querySelectorAll(".wrap"); {
for (let i = 0; i < wrappers[i].length; i++) {
if (wrappers[i].player) {
return removePlayer(wrappers[i]);
}
}
}
}
In your changed code you have made the iteration condition check for wrappers[i].length instead of what I believe was intended wrappers.length. Otherwise, you are for each iteration checking if i is smaller than the i-th wrapper's length which may or may not be defined.
function removePlayerHandler(evt) {
const el = evt.target;
const container = el.closest(".container");
const wrappers = container.querySelectorAll(".wrap"); {
for (let i = 0; i < wrappers[i].length; i++) { // HERE
if (wrappers[i].player) {
return removePlayer(wrappers[i]);
}
}
}
}
Complementing #Salim answer, there is also an extra bracket in your code, try this:
function removePlayerHandler(evt) {
const el = evt.target;
const container = el.closest(".container");
const wrappers = container.querySelectorAll(".wrap");
for (let i = 0; i < wrappers.length; i++) {
if (wrappers[i].player) {
return removePlayer(wrappers[i]);
}
}
}

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>

Search bar remains open with empty result

When i type something and then delete it the search bar remains open with no result, even if i click on something else in the same page, how can i make it to auto-close itself after i click somewere else on the screen?
SearchBx.onkeyup = (e)=>{
let userData = e.target.value;
let emptyArray = [];
if(userData){
emptyArray = suggestions.filter((data)=>{
return data.toLocaleLowerCase().startsWith(userData.toLocaleLowerCase());
});
emptyArray = emptyArray.map((data)=>{
return data = '<li>'+ data + '</li>';
});
console.log(emptyArray);
Main.classList.add("show");
}else{
}
showSuggestions(emptyArray);
let allList = AutoBox.querySelectorAll("li");
// for (let i = 0; i < allList.length; i++) {
// allList[i].setAttribute("onclick", "select(this)");
// }
for(const el of allList){ // use for..of instead of indexed for
el.addEventListener('click', (e) => { // attach an event listener instead of using the onclick attribute
SearchBx.value = e.target.textContent; // can be a separate function like 'select', but whatever
ouvrirPage(); // trigger page opening from click event
});
}
}
// function select(element){
// let selectUserData = element.textContent;
// SearchBx.value = selectUserData;
// }
function showSuggestions(list){
let listData;
if(!list.length){
userValue = SearchBx.value;
listData = '<li>'+ userValue +'</li>'
}else{
listData = list.join('');
}
AutoBox.innerHTML = listData;
}
https://codepen.io/galusk0149007/pen/NWXEVmO
Add one onblur event to your search bar and remove the .show class from the search-bar
something like the below:
SearchBx.onblur = () => {
Main.classList.remove('show');
}
Since the above solution closes the dropdown on selecting, here is another working solution -
Select your wrapper element and add an event to remove the show class when clicking on it.
let wrapper = document.querySelector('.wrapper');
wrapper.onclick = () => {
Main.classList.remove('show');
}
the solution to my problem was adding this
SearchBx.onclick = () => {
Main.classList.remove('show');
}

JavaScript: Event listener for multiple buttons

I'am building a page with a few procede and back button ,i need to add a multiple event listener on all button with same class, how i can achive?
I tried with querySelectorAll all, and called the methods inside handleNextStepButton () in a forEach but what would be the best method?
class Wizard {
constructor(obj) {
this.wizard = obj;
this.panels = new Panels(this.wizard);
this.steps = new Steps(this.wizard);
this.stepsQuantity = this.steps.getStepsQuantity();
this.currentStep = this.steps.currentStep;
this.concludeControlMoveStepMethod = this.steps.handleConcludeStep.bind(this.steps);
this.wizardConclusionMethod = this.handleWizardConclusion.bind(this);
}
updateButtonsStatus() {
if (this.currentStep === 0) this.previousControl.classList.add("disabled");
else this.previousControl.classList.remove("disabled");
}
updtadeCurrentStep(movement) {
this.currentStep += movement;
this.steps.setCurrentStep(this.currentStep);
this.panels.setCurrentStep(this.currentStep);
this.handleNextStepButton();
this.updateButtonsStatus();
// this.currentStep.classList.add("current");
}
handleNextStepButton() {
if (this.currentStep === this.stepsQuantity - 1) {
this.nextControl.innerHTML = "Conclude!";
this.nextControl.removeEventListener("click", this.nextControlMoveStepMethod);
this.nextControl.addEventListener("click", this.concludeControlMoveStepMethod);
this.nextControl.addEventListener("click", this.wizardConclusionMethod);
} else {
this.nextControl.innerHTML = "Next";
this.nextControl.addEventListener("click", this.nextControlMoveStepMethod);
this.nextControl.removeEventListener("click", this.concludeControlMoveStepMethod);
this.nextControl.removeEventListener("click", this.wizardConclusionMethod);
}
}
addControls(previousControl, nextControl) {
this.previousControl = previousControl;
this.nextControl = nextControl;
this.previousControlMoveStepMethod = this.moveStep.bind(this, -1);
this.nextControlMoveStepMethod = this.moveStep.bind(this, 1);
previousControl.addEventListener("click", this.previousControlMoveStepMethod);
nextControl.addEventListener("click", this.nextControlMoveStepMethod);
this.updateButtonsStatus();
}
}
let wizardElement = document.getElementById("wizard");
let wizard = new Wizard(wizardElement);
let buttonNext = document.querySelector(".next");
let buttonPrevious = document.querySelector(".previous");
wizard.addControls(buttonPrevious, buttonNext);
Just have in mind that you'll get a list of elements. So you just need to iterate throught the list of elements and append your listerner:
let buttons = document.querySelectorAll(".next");
for(let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", yourFunction);
}

Categories