Activating and Deactivation EventListener JS vanilla - javascript

I am creating a game with two players. these players are 0,1
var activePlayer = [0,1]
Depending who is active I would like to make available the event listener which activates the controlers of each player, only when the player is active.
I try the following but of course, is not working.
function controlGame(){
do{
document.querySelector('.btn-roll-0').addEventListener('click', play,);
document.querySelector('.btn-hold-0').addEventListener('click',hold);
}while(activePlayer === 0)
do{
document.querySelector('.btn-hold-1').addEventListener('click',hold);
document.querySelector('.btn-roll-1').addEventListener('click', play,);
}while(activePlayer === 1)
}

Ignore the scaffold code that completes the demo, and look at the event listeners.
We only activate if the active player is correct.
// This is just for the demo ignore
const play = (player) => {
let newNum = Date.now();
setOut(`Role ${newNum} for Player ${player}`);
};
// This is just for the demo ignore
const hold = (player) => {
setOut(`hold for ${player}`);
};
// This is just for the demo ignore
const setOut = (msg) => {
document.querySelector('#out').innerText = msg;
};
let isActivePlayer = false;
// Here we add event listeners that toggle based on active player.
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('.btn-roll-0').addEventListener('click', (event) => {
if (!isActivePlayer) play(0);
});
document.querySelector('.btn-hold-0').addEventListener('click', (event) => {
if (!isActivePlayer) hold(0);
});
document.querySelector('.btn-roll-1').addEventListener('click', (event) => {
if (isActivePlayer) play(1);
});
document.querySelector('.btn-hold-1').addEventListener('click', (event) => {
if (isActivePlayer) hold(1);
});
document.querySelector('.btn-switch-plyr').addEventListener('click', (event) => {
isActivePlayer = !isActivePlayer;
});
});
<button class="btn-roll-0">Player 1 Roll</button>
<button class="btn-hold-0">Player 1 Hold</button>
<button class="btn-roll-1">Player 2 Roll</button>
<button class="btn-hold-1">Player 2 Hold</button>
<button class="btn-switch-plyr">Switch Player</button>
<div id="out">
<div>

Related

Close button popup doesn't work (JAVASCRIPT)

i'm trying to create a custom pupop in javascript, this is my first time with this.
I have a problem with the close button, the "x" target correctly the div to close, but doesn't remove the "active" class at click.
https://demomadeingenesi.it/demo-cedolino/
HTML CODE
<div class="spot spot-2">
<div class="pin"></div>
<div class="contenuto-spot flex flex-col gap-3">
<img class="chiudi-popup" src="img/chiudi.svg" />
[---CONTENT---]
</div>
</div>
</div>
JAVASCRIPT CODE
const tooltips = function () {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
const chiudiPopup = document.querySelector(".chiudi-popup");
spot.classList.add("active");
contenuto.classList.add("show");
if (curActive && curActive !== spot) {
curActive.classList.toggle("active");
contActive.classList.toggle("show");
}
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
});
});
const chiudiPopup = document.querySelector(".chiudi-popup");
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
What the code above does is adding an click listener, but it's inside another click listener, so all it's doing is adding an click listener on the first .chiudi-popup that removes .active and .show from the last spot element.
It's hard to see if this is correct, because you haven't given us enough to reproduce the problem, but I moved the code above outside the spot.addEventListener("click", () => { and instead of searching the whole document with const chiudiPopup = document.querySelector(".chiudi-popup"); the code nows only targets the .chuidi-popup element within the spot: const chiudiPopup = spot.querySelector(".chiudi-popup");
const tooltips = function() {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
spot.classList.add("active");
contenuto.classList.add("show");
if (curActive && curActive !== spot) {
curActive.classList.toggle("active");
contActive.classList.toggle("show");
}
});
// MOVED FROM THE CLICK LISTENER
const chiudiPopup = spot.querySelector(".chiudi-popup");
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
});
EDIT: I missed that you have the img.chiudi-popup inside your container, which will trigger both event listeners. I would honestly just simplify the code and always hide the container when clicking on it again. You can still have the img.chiudi-popup (close image) to make it easier for the users to understand that they can click on it.
const tooltips = function() {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
if (curActive !== spot) {
spot.classList.add("active");
contenuto.classList.add("show");
}
if (curActive) {
curActive.classList.remove("active");
contActive.classList.remove("show");
}
});

how to stop function in js?

I have a form that counts when a button (button.clicked in the example below) is clicked. I want to make it operate in two modes: one keeps counting with every click, the other has a timer (started with the click of another button, button.start) that will disable the click-count button when the timer runs out. Each mode is chosen by clicking a button (button.menu-timer and button.menu-clicks). When the count mode is selected, one function (cc) is called. When switched to the timer mode, another function (tt) should be called and the first function should stop.
If I click one mode button, then everything works as it should, but if after that I click the other mode button, both functions continue to operate; each click of button.click adds two to the count. Moreover, if you click the mode buttons several times, clicking the count button will increase the counter many times, rather than only once.
I searched for solutions on the Internet and found one based on return; I tried to use return in various ways but couldn't get it to work.
I need that when choosing the right mode, only the desired function works. And so that when you click several times on one mode, the function will run once.
The following snippet is also available on CodePen.
let clicker = document.querySelector(".click");
let start = document.querySelector(".start");
let clickerValue = document.querySelector(".click").value;
const reset = document.querySelector(".reset");
const menuTimer = document.querySelector(".menu-timer");
const menuClicks = document.querySelector(".menu-clicks");
const times = document.querySelectorAll(".time");
let i = 0;
let y;
let tf;
let timer = 15;
function tt(tf) {
if (tf ===2) {
return;
}
start.addEventListener("click", () => {
start.style.zIndex = "-1";
y = setInterval(() => {
if (i === timer) {
clicker.setAttribute("disabled", "");
} else {
i++;
}
}, 1000);
});
clicker.addEventListener("click", () => {
clicker.textContent = clickerValue++;
});
reset.addEventListener("click", resetF);
}
function cc(tf) {
if (tf = 1) {
return;
}
start.addEventListener("click", () => {
console.log("111111");
start.style.zIndex = "-1";
});
clicker.addEventListener("click", () => {
clicker.textContent = `a ${clickerValue++}`;
});
reset.addEventListener("click", resetF);
}
function resetF() {
clearInterval(y);
i = 0;
start.style.zIndex = "2";
clickerValue = 0;
clicker.textContent = clickerValue;
clicker.removeAttribute("disabled", "");
}
menuTimer.addEventListener("click", function () {
menuTimer.classList.add("active");
menuClicks.classList.remove("active");
tt(1);
resetF();
});
menuClicks.addEventListener("click", function () {
menuClicks.classList.add("active");
menuTimer.classList.remove("active");
cc(2)
resetF();
});
<div class="menu">
<button type="button" onclick="tf = 1" class="menu-timer">TIMER</button>
<button type="button" onclick="tf = 2" class="menu-clicks">CLICKS</button>
</div>
<div class="click-btn">
<button class="click" type="button">0</button>
<button class="start" type="button">START</button>
</div>
<button class="reset" type="button">Reset</button>
You have a typo with assigning = instead of equality operator ===
function cc(tf) {
if (tf = 1) { // should be ===
return;
}
...
}
Also before you addEventListener('click', ...), a good practice is to remove previous click listeners with removeEventListener('click')

Vanilla JS remove eventlistener in currying function

I have a very "simple" vanillaJS problem. How can I remove event listener in loop inside a currying function? This just an example of my current solution where I actually need multiple parameters coming to the listener. I suspect that the event listener is not removed due to anonymous callback if I am right? How can I fix this? Example here https://codepen.io/shnigi/pen/wvPwqVR
const setEventListener = (buttons) => (event) => {
const buttonValue = event.target.value;
console.log('Eventlistener exists', buttonValue)
buttons.forEach(button => button.removeEventListener('click', setEventListener));
};
const buttons = document.querySelectorAll('button');
buttons.forEach(button => button.addEventListener('click', setEventListener(buttons)));
// Works as expected, listener is named
const testbutton = document.getElementById('kek');
const testListener = () => {
console.log('I show up only once');
testbutton.removeEventListener('click', testListener);
};
testbutton.addEventListener('click', testListener);
<button value="1">press me</button>
<button value="2">press me</button>
<button id="kek">eventlistener removed on click</button>
I suspect that the event listener is not removed due to anonymous callback if I am right?
Yes, that is a problem.
So you have to assigned anonymous function to variable and use it to remove event listener.
Here is sample code for you.
const setEventListener = (buttons: NodeList) => {
const returnedFunc = (event: Event) => {
const buttonValue = (event.target as HTMLButtonElement).value;
console.log('Eventlistener exists', buttonValue)
buttons.forEach(button => button.removeEventListener('click', returnedFunc));
};
return returnedFunc;
};
const buttons: Nodelist = document.querySelectorAll('button');
buttons.forEach(button => button.addEventListener('click', setEventListener(buttons)));
// Works as expected, listener is named
const testbutton = document.getElementById('kek');
const testListener = () => {
console.log('I show up only once');
testbutton.removeEventListener('click', testListener);
};
testbutton.addEventListener('click', testListener);
If you don't care about Internet Explorer support, you can remove anonymous event listeners in more elegant way using AbortController. Pass signal option to addEventListener() and call AbortController's abort() method when you want to remove event listener:
var listenersRemover = new AbortController();
button.addEventListener('click', callback, {signal: listenersRemover.signal}));
listenersRemover.abort();
Note: you should "rearm" abort controller to use the same signal for the new addEventListener() calls. Just initialize it again with new AbortController().
var listenersRemover = new AbortController();
const setEventListener = (buttons) => (event) => {
const buttonValue = event.target.value;
console.log('Eventlistener exists', buttonValue)
// buttons.forEach(button => button.removeEventListener('click', setEventListener));
};
const buttons = document.querySelectorAll('button');
buttons.forEach(button => button.addEventListener('click', setEventListener(buttons), {signal: listenersRemover.signal}));
// Works as expected, listener is named
const testbutton = document.getElementById('kek');
const testListener = () => {
console.log('I show up only once');
//testbutton.removeEventListener('click', testListener);
listenersRemover.abort();
};
testbutton.addEventListener('click', testListener, {signal: listenersRemover.signal});
<button value="1">press me</button>
<button value="2">press me</button>
<button id="kek">eventlistener removed on click</button>

remove event listeners with extra arguments

Here is the event listener I'm using:
const eventHandler = (word, e) => {
if(isAnimating && duplicates.includes(word.innerHTML)) return;
if(String.fromCharCode(e.which) == word.innerHTML && !distWords.includes(word)) {
move(word);
}
}
words.forEach(word => {
window.addEventListener("keypress", eventHandler.bind(null, word));
});
The issue is I'm unable to remove the listener, I used something like this with no luck:
removeIT = () => {
words.forEach(word => {
window.removeEventListener("keypress", eventHandler.bind(null, word));
});
}
How can I remove the listers effectivly?
If you want to continue with this pattern you will need to store references to each bound function and iterate over them to remove the listeners.
const words_listeners = [];
words.forEach(word => {
const handler = eventHandler.bind(null, word);
window.addEventListener("keypress", handler);
words_listeners.push(handler);
});
removeIT = () => {
words_listeners.forEach(handler => {
window.removeEventListener("keypress", handler);
});
}
The snippet below is attaching click listeners to buttons, so it is necessary to also store the element that the listener is attached to, but in your case, since you're attaching to the window, you won't need to. Basically, in the loop in which you are attaching the listeners, create your bound function and assign it to a variable, attach your listener with it, then push it to an array which can be used to remove the listeners later.
const buttons = document.querySelectorAll('.word')
const word_listeners = [];
const eventHandler = (word, e) => {
console.log(word)
}
buttons.forEach(button => {
const handler = eventHandler.bind(null, button.textContent);
button.addEventListener('click', handler);
word_listeners.push([button, handler]);
});
function remove_listeners() {
word_listeners.forEach(([button, handler]) => button.removeEventListener('click', handler));
}
document.getElementById('remove').addEventListener('click', remove_listeners);
<button type="button" class="word">One</button>
<button type="button" class="word">Two</button>
<button type="button" class="word">Three</button>
<button type="button" id="remove">Remove listeners</button>
I don't think it needs to be that complicated. If you call a function that returns a closure that acts the listener you don't need any binding.
I've had to use buttons here because I don't know what your markup is like.
const buttons = document.querySelectorAll('button');
function handleClick() {
console.log(this);
}
function removeAllListeners(buttons) {
buttons.forEach(button => {
button.disabled = true;
button.removeEventListener('click', handleClick);
});
}
function handler(word) {
console.log(word);
return handleClick;
}
buttons.forEach(button => {
const { textContent } = button;
button.addEventListener('click', handler(textContent), false);
});
setTimeout(removeAllListeners, 5000, buttons);
button { background-color: #efefef; cursor: pointer; }
button:disabled { background-color: #ffb3b3; cursor: default; }
<button>Tree</button>
<button>Giant</button>
<button>Billy Joel</button>

Avoid numbers incrementing multiple times when calling a function multiple times

I have written a quantity selector function to display on a page. The page can open some modals, which need to have another quantity selector within each.
I am calling the function within the main page, and also within the modal (to enable the functionality once the modal is displayed.)
When I adjust the quantity in the modal, close the modal, and adjust the quantity on the main page, the quantity increments/decrements double (or 3 times if I was to call the function 3 times.)
Is there a way to "reset" each of these event listeners/functions, to only adjust for their respective elements?
I've looked into "removeEventListener" but haven't had any joy in implementing this within my code.
Example of my work so far here (you can see what I mean if you click the buttons.)
https://codepen.io/777333/pen/zYoKYRN
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
};
quantitySelector(); // called within main page
quantitySelector(); // called within modal
The issue at hand is that each time you're calling the function, a new event handler is added on top of the previous ones. The best way to avoid this is through Event Delegation where you add a global event handler only once.
// A global event handler
document.addEventListener(
"click",
function (event) {
// Find the qty-group if clicked on it
const qtyGroup = event.target.closest(".qty-group");
// Stop if the click was elsewhere
if (qtyGroup) {
// Get your elements
const qtyDecrease = qtyGroup.querySelector("[data-quantity-decrease]");
const qtyIncrease = qtyGroup.querySelector("[data-quantity-increase]");
const qtyInput = qtyGroup.querySelector("[data-quantity-input]");
const disableEnableDecrease = () => {
if (qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
// Match your elements against what was clicked on.
if (event.target == qtyDecrease) {
event.preventDefault();
if (qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
}
if (event.target == qtyIncrease) {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
}
}
},
false
);
Instead of listening to individual elements, you can capture all the clicks on the document, and then finding those that click on elements of interest. You can make a second event handler for the keyup event.
You can save the value of qtyInput on mousedown event and then in the increment you add or subtract one from the saved value instead of the current value of the input.
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
let savedValue = null;
const saveState = (evebt) => savedValue = Number(qtyInput.value);
qtyDecrease.addEventListener('mousedown', saveState)
qtyIncrease.addEventListener('mousedown', saveState)
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
if(qtyInput.value > 1) {
qtyInput.value = savedValue - 1;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
qtyInput.value = savedValue + 1;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
event.stopPropagation();
});
});
}
};
quantitySelector();
quantitySelector();
There is a method called removeEventListener (MDN) but I suggest you to reshape your code such that you do not add event listener if they are already present.
Put all of your addEventListener just when you create your elements, or in a "document ready" callback if they are instantiated by HTML code. Then, when you open your modal, just update your values.
UPDATING YOUR CODE
// hide/show modal function
function toggleModal() {
let modal = document.getElementById('modal');
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
}
// your document ready function
function onReady() {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
// attach hide/show modal handler
const toggle = document.getElementById('modal_toggle');
toggle.addEventListener('click', toggleModal);
}
onReady();
<div class="qty-group">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<div class="qty-group" id="modal" style="display: none;">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<button id="modal_toggle">Toggle Modal</button>
REFACTORING
It is better in such cases to reason as Components. Components ensure code encapsulation, maintainability, reusage, single responsability and many other usefull principles:
// hide/show modal function
function toggleModal() {
// get the modal
let modal = document.getElementById('modal');
// hide the modal
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
// reset the input of the modal
modalInputReference.reset();
}
function createQuantityInput(target, initialQuantity=1, min=1, max=10, step=1) {
let quantity = 0;
// assign and check if should be disable, also bind to input value
let assign = (q) => {
quantity = Math.max(Math.min(q, max), min);
decrease.disabled = quantity <= min;
increase.disabled = quantity >= max;
input.value = quantity;
};
// CREATION
// This part is not mandatory, you can also get the elements from
// the target (document.querySelector('button.decrease') or similar)
// and then attach the listener.
// Creation is better: ensure encapsulation and single responsability
// create decrease button
let decrease = document.createElement('button');
decrease.addEventListener('click', () => { assign(quantity - step); });
decrease.innerText = '-';
// create increase button
let increase = document.createElement('button');
increase.addEventListener('click', () => { assign(quantity + step); });
increase.innerText = '+'
// create input field
let input = document.createElement('input');
input.value = quantity
input.addEventListener('change', () => { assign(parseFloat(input.value)); });
// resetting the quantity
assign(initialQuantity);
// appending the new component to its parent
target.appendChild(decrease);
target.appendChild(input);
target.appendChild(increase);
// return a reference to manipulate this component
return {
get quantity() { return quantity; },
set quantity(q) { assign(q); },
assign,
reset: () => assign(initialQuantity)
};
}
// this will be your modal reference
let modalInputReference;
function onReady() {
// inject all qty-group with a "quantityInput" component
document.querySelectorAll('.qty-group').forEach(elem => {
let input = createQuantityInput(elem);
if (elem.id == 'modal') {
// if it is the modal I save it for later use
// this is just an hack for now,
// a full code should split this part into a "modal" component maybe
modalInputReference = input;
}
});
// emualte the modal
let toggle = document.getElementById('modal_toggle')
toggle.addEventListener('click', toggleModal)
}
// this function should be wrapped by a
// $(document).ready(onReady) or any other
// function that ensure that all the DOM is successfully loaded
// and the code is not executed before the browser has generated
// all the elements present in the HTML
onReady();
<div class="qty-group"></div>
<div class="qty-group" id="modal" style="display: none;"></div>
<button id="modal_toggle">Toggle Modal</button>
It is shorter (without comments) and also more maintenable. Don't trust who says it is overengineered, it is just kind of time to learn to reason this way, then is much easier and faster. It is just a time investment to waste less time in the future. Try figure out why React or Angular(JS) have climbed the charts of the best frameworks so fast.

Categories

Resources