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]);
Related
Error in my code is, I add condition to run a JavaScript function only one time but when I add if else logic web page can open all sections
here is my code logic:
let count = 0;
window.addEventListener('scroll', fade);
function fade()
{
if (count < 1)
{
let animation=document.querySelectorAll('.fade');
for (let i=0; i<animation.length; i++)
{
let windowheight=window.innerHeight;
let top=animation[i].getBoundingClientRect().top;
if (top < windowheight)
{
animation[i].classList.add('visible');
}
else
{
animation[i].classList.remove('visible');
}
}
}
else
{
return ;
}
count++;
}
Since you only need to handle the event once,
window.addEventListener('scroll', fade, {once: true});
You can safely remove count variable and if...else.
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.
I have this code working perfectly fine with mouse click events.
var nodes = document.querySelectorAll('.myclass');
for(var i=0; i < nodes.length; i++){
var node = nodes[i];
node.addEventListener('click', function () {
if(this.className == 'sample') {
document.getElementById("massage").innerHTML="Hello";
}
}, false);
}
Now if I just replace 'click' with 'keypress' all small letters, then the event is not triggered. What is the correct approach to do it.
And If I want to use Lambda's then what is the correct approach for the same task?
document.querySelectorAll('.myclass').addEventListener('mouseover', () => {
document.addEventListener('keypress', ({ key }) => {
if(this.className == 'sample') {
document.getElementById("massage").innerHTML="Hello";
}
});
});
MDN Documentation says do not use keypress any more. Also says it does not work for all key presses. Also it is for KEY press, not mouse CLICK. Different events!
Why are you using keypress for mouse?
This is what i got from your snippet
let el = document.querySelectorAll('.myclass');
let massage = document.getElementById('massage');
for(let i = 0; i < el.length; i++){
el[i].addEventListener('mouseover', e => {
e.target.classList.add('is-hover');
})
el[i].addEventListener('mouseout', e => {
e.target.classList.remove('is-hover');
})
}
document.body.addEventListener('mousedown', () => {
for(let i = 0; i < el.length; i++){
if (el[i].classList.contains('sample') && el[i].classList.contains('is-hover')){
massage.innerHTML = 'Hello';
}
}
})
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]);
}
}
}
I'm building a simple calculator app. I'm trying to accomplish three things:
Assign an event listener to the buttons.
When the button is clicked, fire an event.
Use the eventListener function to display the value of the clicked button.
for (i = 0; i < btn.length; i++) {
var btnVal = btn[i].value;
btn[i].addEventListener("click", function() { displayNumber(btnVal) }, false);
}
function displayNumber(param) {
displayedNum.innerHTML = param;
}
It seems btnVal is not accessible when passed to the event listener function.
The assignment won't work like that. Instead, use the target's value
for (i = 0; i < btn.length; i++) {
btn[i].addEventListener("click", function(e) {
displayNumber(e.target.value)
}, false);
}
Defining event listeners (or other asynchronous things) inside of a loop is tricky. You may think you're creating several different btnVal variables, one for each time through the loop, but you're not. That var btnVal gets hoisted to the top and reused, so your code ends up behaving like this:
var btnVal;
for (i = 0; i < btn.length; i++) {
btnVal = btn[i].value;
btn[i].addEventListener("click", function() { displayNumber(btnVal) }, false);
}
So all of your event listeners are interacting with the very same variable, and when they eventually get clicked, they'll only see the very last value that was assigned to btnVal, which should be btn[btn.length -1].value. All the values earlier in the array are lost.
There are a few ways you can address this:
1) Rather than depending on a closure variable, you could pull it from the element itself when the event goes off.
for (i = 0; i < btn.length; i++) {
btn[i].addEventListener("click", function (event) {
displayNumber(event.target.value);
});
}
2) Move the event listener creation into a function, and pass btnVal in. Since it's now a function parameter, it gets a new binding.
for (i = 0; i < btn.length; i++) {
createListener(btn[i], btn[i].value);
}
function createListener(element, val) {
element.addEventListener("click", function () { displayNumber(val) }, false);
}
3) you can do it inline using an IIFE.
for (i = 0; i < btn.length; i++) {
(function (button) {
button.addEventListener("click", function () { displayNumber(button.value) }, false);
})(btn[i]);
}
EDIT: added option 4
4) If you can use ES2015, use let. Let has block scope, and will get a new binding each time through the loop
for (i = 0; i < btn.length; i++) {
let btnVal = btn[i].value;
btn[i].addEventListener("click", function() { displayNumber(btnVal) }, false);
}
You cant use btnVal inside the event.
It should look like this
for (i = 0; i < btn.length; i++) {
btn[i].addEventListener("click", function(event) {
var clickedButton = event.target || event.srcElement;
console.log(clickedButton.value)
}, false);
}