removeEventListener only removing single instance - javascript

I've been having issues where when I try to remove an event from the buttons it seems to only be removing the event for the one-button even though I have looped through the buttons and removed the event.
thank you.
function ChangeQuestions() {
let currentQuestion = getQuestion(); //another function to get the question from an array - returns an object with questions, answers and correctAnswer
const correctAnswer = currentQuestion.correct;
console.log(currentQuestion);
if (questionsArray.length === 0) {
//If the array is empty push the questions again
questionsArray.push(firstQuestion, secondQuestion, thirdQuestion);
}
document.querySelector('.question-header').textContent = currentQuestion.questions;
for (let i = 1; i < 4; i++) {
document.querySelector('.btn-Ans-' + i).textContent = currentQuestion.answers[i - 1];
document.querySelector('.btn-Ans-' + i).addEventListener('click', function checkAns(e) {
if (e.target.innerHTML === correctAnswer) {
score++;
console.log(score);
removeEvent('click', checkAns);
ChangeQuestions();
} else {
console.log(score);
removeEvent('click', checkAns);
ChangeQuestions();
}
});
}
}
function removeEvent(event, func) {
for (let i = 1; i < 4; i++) {
document.querySelector('.btn-Ans-' + i).removeEventListener(event, func);
}
}

With
for (let i = 1; i < 4; i++) {
document.querySelector('.btn-Ans-' + i).addEventListener('click', function checkAns(e) {
A new checkAns function is created inside every iteration of the loop, and removeEventListener must be passed the exact same function that addEventListener was called with. Since the different loop iterations have different functions passed into their respective addEventListener calls, the removeEvent function appears to only affect the element that was clicked, and none of the rest.
Here's a more minimal example:
const fns = [];
for (let i = 0; i < 2; i++) {
const foo = () => console.log('foo');
fns.push(foo);
window.addEventListener('click', foo);
}
// Not the same function:
console.log(fns[0] === fns[1]);
I'd add just a single listener to the container instead, and use event delegation to check which element was clicked on:
btnContainer.addEventListener('click', function handler(e) {
if (!e.target.matches('[class^="btn-Ans"]')) {
return;
}
btnContainer.removeEventListener('click', handler);
if (e.target.innerHTML === correctAnswer) {
score++;
}
console.log(score);
ChangeQuestions();
});
where btnContainer is a container for your btn-Ans-s.

Related

Using an EventListener on the Element that calls a counter function - Javascript

Goal: Write a function that is called by the eventListener click event of an IMG. The function will use a conditional to see on which ElementID called the function, then initialize the on accumulator to that value, then add 1 and change the innerHTML to show that new value. Key goal is to make this occur with out have 3 similar identical functions
Code Thus Far
let likeCount = !NaN;
var classname = document.getElementsByClassName("like-heart");
function likeCounter() {
/*Note the elementID is very similar to the ELementID of the accumulator element - but not the same.*/
if (this.getElementById === "ft-recipe-like") {
likeCount = Number(document.getElementById('featured-likes').innerText);
likeCount += 1;
document.getElementById('featured-likes').innerText = likeCount;
} else if (this.getElementById === "macaroon-like") {
likeCount = Number(document.getElementById('macaroon-likes').innerText);
likeCount += 1;
document.getElementById('macaroon-likes').innerText = likeCount;
} else if (this.getElementById === 'brulee-like') {
likeCount = Number(document.getElementById('brulee-likes').innerText);
likeCount += 1;
document.getElementById('brulee-likes').innerText = likeCount;
}
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', likeCounter, false);
}
}
}
}
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', likeCounter, false);
}
}
}
You can have a map of acc id to Dom node id, and write a generic function to increment the likeCount.
let likeCount = !NaN;
var classname = document.getElementsByClassName("like-heart");
const accIdToDomIdMap = {
'ft-recipe-like': 'featured-likes',
'macaroon-like': 'macaroon-likes',
'brulee-likes': 'brulee-likes'
}
function likeCounter() {
/*Note the elementID is very similar to the ELementID of the accumulator element - but not the same.*/
if(accIdToDomIdMap.hasOwnProperty(this. getElementById)) {
const domId = document.getElementById(accIdToDomIdMap[this.getElementById]);
likeCount = (+domId.innerText || 0) + 1;
domId.innerText = likeCount;
}
}
function newFunction() {
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', likeCounter, false);
}
}
}

Guess the winning button by clicking on one of them - Vanilla JS

You have 3 buttons. On of them, randomly chosen, is a winner. Display if the winning button has been clicked or not. Bonus: Generate n buttons, from which one of them is winner.
My code so far:
let n = 3, buton
for (let i = 0; i < n; ++i) {
buton = document.createElement("button")
document.querySelector("body").appendChild(buton)
buton.id = i
buton.innerText = i
}
let winningButton = 1 // Math.floor(Math.random() * n)
let onClick = function() {
if (winningButton == buton.id) {
alert("Congratulations! You've guessed the button!")
}
}
buton.addEventListener("click", onClick)
Can you help me solve this problem?
In the onClick function, you are not checking against the clicked button, you can use the this keyword to access the button in the event handler
let onClick = function() {
if (winningButton == this.id) {
alert("Congratulations! You've guessed the button!")
}
}
If you use arrow functions this will not be work, so instead, you can use the event object to get the target element
let onClick = (event) => {
if (winningButton == event.target.id) {
alert("Congratulations! You've guessed the button!")
}
}
You can generate a random number when the user clicks.
let n = 3;
let winningButton;
const onClick = function() {
winningButton = winningButton ? winningButton : Math.floor(Math.random() * n);
if (winningButton == this.id) {
alert("Congratulations! You've guessed the button!")
}
}
for (let i = 0; i < n; ++i) {
const buton = document.createElement("button")
document.querySelector("body").appendChild(buton)
buton.id = i
buton.innerText = i;
buton.addEventListener("click", onClick);
}

Getting 'For Loop' to work the same as 'forEach'

This one uses For Loop
After you click on another button they don’t change back to the play button, and instead they stay on pause. The audio pauses without an issue, it’s just the buttons that don’t change back for some reason.
https://jsfiddle.net/pezuLqvo/85/
function hideAllButtons(button) {
const buttons = button.querySelectorAll(".play, .pause, .speaker");
for (let i = 0; i < buttons.length; i += 1) {
hide(buttons[i]);
}
}
function pauseAllButtons(buttons) {
for (let i = 0; i < buttons.length; i += 1) {
if (isPlaying(buttons[i])) {
showPlayButton(buttons[i]);
}
}
}
function showPauseButton(button) {
const pause = getPause(button);
pauseAllButtons(button);
hideAllButtons(button);
show(pause);
button.classList.add("active");
}
Looking at how this one was set up, are you able to determine what I would change in the above code to fix that issue?
This one uses forEach
https://jsfiddle.net/pezuLqvo/84/
function hideAllButtons(button) {
button.querySelectorAll(".play, .pause, .speaker").forEach(hide);
}
function pauseAllButtons() {
const buttons = document.querySelectorAll(".playButton");
buttons.forEach(function hidePause(button) {
if (isPlaying(button)) {
showPlayButton(button);
}
});
}
function showPauseButton(button) {
const pause = getPause(button);
pauseAllButtons();
hideAllButtons(button);
show(pause);
button.classList.add("active");
}
This was the answer:
https://jsfiddle.net/pezuLqvo/93/
function pauseAllButtons() {
const buttons = document.querySelectorAll(".playButton");
for (let i = 0; i < buttons.length; i += 1) {
if (isPlaying(buttons[i])) {
showPlayButton(buttons[i]);
}
}
}

Dot pagination with pure JavaScript

i m very much new to programming with JavaScript and would love to gain more experience, my problem isn't actually a problem, it more like of optimizing a code, I've been working on making what so called a "pagination" a dot navigation. you can find my code example in this code pen https://codepen.io/Tarek-Chentouf/pen/ajqXpW . My code goes as follow:
"use strict";
var buttons = document.querySelectorAll('.button__outline');
function reset() {
for (let i = 0; i < buttons.length; i++) {
buttons[i].classList.remove('active');
}
}
function addActive() {
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
if (i == 0) {
reset();
buttons[0].classList.add('active');
}
if (i == 1) {
reset();
buttons[1].classList.add('active');
}
if (i == 2) {
reset();
buttons[2].classList.add('active');
}
if (i == 3) {
reset();
buttons[3].classList.add('active');
}
});
}
}
addActive();
my Question goes as follow is there a better way to achieve the same result without having to repeat the if statement?.
Thank you all in advance.
For the general case, you could simply access buttons[i] instead of if (i == 0) ... buttons[0] ... if (i == 1) ... buttons[1] ...:
function addActive() {
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
reset();
buttons[i].classList.add('active');
});
}
}
But you could make the code cleaner and DRY-er with a forEach - instead of accessing an index of the buttons collection, abstract the button being iterated over into a variable of its own:
function addActive() {
buttons.forEach(button => {
button.addEventListener('click', () => {
reset();
button.classList.add('active');
});
});
}
Or, as Patrick Roberts suggested, you might move all the classList changes into the reset function and use event delegation on the container (that way you only need one listener, rather than many):
document.querySelector('.container').addEventListener('click', ({ target }) => {
if (!target.matches('.button__outline')) return;
reset(target);
});
const buttons = document.querySelectorAll('.button__outline');
function reset(showButton) {
buttons.forEach(button => {
button.classList.remove('active');
})
showButton.classList.add('active');
}
reset(buttons[0]);

Running functions onclick. All buttons being clicked

I have many buttons that have class="clearSelect". I want these buttons the execute a function onclick. I am new to javascript and not quite sure why this is occurring but I think my functions are being executed instead of only executing onclick
The code below is what is calling all my other functions causing every button to be clicked.
code:
var buttons = document.getElementsByClassName("clearSelect"); // objects with class="clearSelect"
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
// button.addEventListener("onclick", clearOrSelectAll(button.id));
button.onclick = clearOrSelectAll(button.id);
}
These are the functions being called:
function clearOrSelectAll(btn) {
var cleartab = clearButtonSet[btn];
var selecttab = selectButtonSet[btn];
// console.log("clicked!");
if (cleartab != null) {
getOSList(cleartab, false);
} else {
getOSList(selecttab, true);
}
}
function getOSList(tabVal, fate) {
var configTab = document.getElementById(tabVal);
var browserList = configTab.getElementsByClassName("browser_list");
// var idObjs = browserList.getElementsByTagName("li");
for (var h = 0; h < browserList.length; h++) {
var idObjs = browserList[h].getElementsByTagName("li");
// console.log(h);
// console.log(idObjs);
// select all
if (fate) {
for (var i = 0; i < idObjs.length; i++) {
if (configs[idObjs[i].id] == null) {
idObjs[i].className = "selected";
configs[idObjs[i].id] = config_dictionary[idObjs[i].id];
}
}
// clear all
} else {
for (var j = 0; j < idObjs.length; j++) {
if (configs[idObjs[j].id] == null) {
idObjs[j].className = "unselected";
delete configs[idObjs[j].id];
}
}
}
}
}
#Christopher was very close, but button.id should be this.id.
button.onclick = function() {
clearOrSelectAll(this.id);
}
The reason button.id doesn't work can be demonstrated with this code:
var buttons= document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.onclick = function() {
alert(button.id);
}
}
<button id="B1">Button 1</button>
<button id="B2">Button 2</button>
<button id="B3">Button 3</button>
Each button returns "B3," because that's the last object that the variable button is assigned to.
In your for loop when you attach the event to all of the buttons, you are calling the clearOrSelectAll function. You probably want to wrap it in an anonymous function to make sure it's only called when the event is fired.
// Non-ideal solution: see edit
button.onclick = function() {
clearOrSelectAll(button.id);
}
EDIT: It has been pointed out that the 'this' context variable will point to the element in question when an event handler is attached by means of the onclick property, or the addEventListener method. As such it would probably be cleaner (and easier to read) if you were to reference that instead of using 'button' as a closure and count on javascript engines to not optimize your loop too heavily (as that would mess with the value of 'button' at the time that the event is called.
button.onclick = function() {
clearOrSelectAll(this.id);
};

Categories