I have two buttons that have the same class name and have same functionality but different inputs must be added, so I used document.querySelectorAll() and the forEach() method to get them, but now when I click one, the other gets clicked too. Is there a way I can prevent this without having two addEventListener for both buttons? Enable it to click only one button at a time.
My code:
let inputElements = document.querySelectorAll('.inputElement');
const submitBtn = document.querySelectorAll('.submitBtn');
const backersElement = document.querySelector('.number-of-backers');
let donationsMade = [];
function calculateBamboo() {
inputElements.forEach(inputElement => {
const inputValue = parseFloat(inputElement.value);
if (inputValue < 25 || inputValue === '') return alert('Pledge must be at least $25.');
donationsMade.push(inputValue);
const donationsTotal = donationsMade.reduce((a, b) => a += b);
pledgedAmount.textContent = `$${donationsTotal}`;
backersElement.textContent = donationsMade.length;
return donationsTotal;
})
}
submitBtn.forEach(button => {
button.addEventListener('click', calculateBamboo);
})
It's not actually "clicking" both buttons. What's happening is the following:
Your calculateBamboo() functions loops through all the inputElements: inputElements.forEach(); and you're executing your logic for all the inputs. So, no matter which button you press, calculateBamboo() is processing each input.
Passing target input using data-attributes will help you to identify which input belongs to clicked button
Also, #JerryBen is right, you don't need to add event listener to each button but instead, we can wrap all the buttons in one element, add event listener to it and identify which element was clicked.
const wrapper = document.getElementById('wrapper');
const pledgedAmount = document.querySelector('.backed-users');
const backersElement = document.querySelector('.number-of-backers');
wrapper.addEventListener('click', calculateBamboo);
let donationsMade = [];
function calculateBamboo(event) {
/* Here you can access to the event argument,
which contains target: the clicked element*/
const el = event.target;
if (el.nodeName !== 'BUTTON' || !el.classList.contains('submitBtn')) {
return;
}
// Get target input from button's data-attr
const targetInput = el.dataset.input;
const inputElement = document.querySelector(`input[data-input="${targetInput}"]`);
// Continue with the code you had...
const inputValue = parseFloat(inputElement.value) || 0;
if (inputValue < 25 || inputValue === '') return alert('Pledge must be at least $25.');
donationsMade.push(inputValue);
const donationsTotal = donationsMade.reduce((a, b) => a += b);
pledgedAmount.textContent = `$${donationsTotal}`;
backersElement.textContent = donationsMade.length;
return donationsTotal;
}
<div class="backed">
<h1 class="backed-users">0</h1>
</div>
<div class="backers">
<h1 class="number-of-backers">0</h1>
</div>
<hr>
<div id="wrapper">
<div class=".pledge-edition">
<div class="pledge">
<section class="pledgeTwo"></section>
<div>
<h2>Bamboo Stand</h2>
Pledge $25 or more
<div>
<h2>101</h2>
<div>left</div>
</div>
</div>
</div>
<p>
You get an ergonomic stand made of natural bamboo. You've helped us launch our promotional campaign, and you’ll be added to a special Backer member list.
</p>
<div class="pledge-amount">
<p>Enter your pledge</p>
<div>
<input class="inputElement bambooInputElement" data-input="1" placeholder="$25" min="25" type="number">
<button class="submitBtn bambooBtn" data-input="1">Continue</button>
</div>
</div>
</div>
<div class=".pledge-edition">
<div class="pledge">
<section class="pledgeThree"></section>
<div>
<h2>Black Edition Stand</h2>
Pledge $75 or more
<div>
<h2>64</h2>
<div>left</div>
</div>
</div>
</div>
<p>
You get a Black Special Edition computer stand and a personal thank you. You’ll be added to our Backer member list. Shipping is included.
</p>
<div class="pledge-amount">
<p>Enter your pledge</p>
<div>
<input class="inputElement bambooInputElement" data-input="2" placeholder="$75" min="75" type="number">
<button class="submitBtn blackEditionBtn" data-input="2" placeholder="$75" min="75">Continue</button>
</div>
</div>
</div>
</div>
Adding event listeners to each button is considered a bad practice. Instead, use a single event listener to rule them all:
Wrap the buttons in a div element
Add click event only to the div wrapper
Pass an event object as an argument to the handler function
The event handler function will use the event.target to identify which specific button was clicked:
function calculateBamboo(evt){ const inputValue = parseFloat(evt.target.value) }
I hope this helps 🙄
My current JS
function calculateBamboo(target) {
let inputElement = document.querySelector(`input[data-input="${target}"]`);
donationsMade.push(inputElement);
const donationsTotal = donationsMade.reduce((a, b) => a += b);
backersElement.textContent = donationsMade.length;
pledgedAmount.textContent = `$${donationsTotal}`;
successElement.style.display = 'block';
return donationsTotal;
}
submitBtn.forEach(button => {
const target = button.dataset.input;
button.addEventListener('click', calculateBamboo.bind(target));
})
HTML buttons and input
<!-- this is how my buttons are placed -->
<div id="wrapper">
<div class=".pledge-edition">
<div class="pledge">
<section class="pledgeTwo"></section>
<div>
<h2>Bamboo Stand</h2>
Pledge $25 or more
<div>
<h2>101</h2>
<div>left</div>
</div>
</div>
</div>
<p>
You get an ergonomic stand made of natural bamboo. You've helped us launch our promotional campaign, and
you’ll be added to a special Backer member list.
</p>
<div class="pledge-amount">
<p>Enter your pledge</p>
<div>
<input class="inputElement bambooInputElement" data-input="1" placeholder="$25" min="25" type="number">
<button class="submitBtn bambooBtn" data-input="1">Continue</button>
</div>
</div>
</div>
<div class=".pledge-edition">
<div class="pledge">
<section class="pledgeThree"></section>
<div>
<h2>Black Edition Stand</h2>
Pledge $75 or more
<div>
<h2>64</h2>
<div>left</div>
</div>
</div>
</div>
<p>
You get a Black Special Edition computer stand and a personal thank you. You’ll be added to our Backer
member list. Shipping is included.
</p>
<div class="pledge-amount">
<p>Enter your pledge</p>
<div>
<input class="inputElement bambooInputElement" data-input="2" placeholder="$75" min="75" type="number">
<button class="submitBtn blackEditionBtn" data-input="2" placeholder="$75" min="75">Continue</button>
</div>
</div>
</div>
HTML for when the values are entered
<div class="backed">
<h1 class="backed-users">0</h1>
</div>
<div class="backers">
<h1 class="number-of-backers">0</h1>
</div>
Related
const addons = document.querySelectorAll('.addon');
const toggleAddon = (e, addon) => {
console.log(addon);
console.log(addon.querySelector('input').checked);
if (addon.querySelector('input').checked)
addon.classList.remove('selected-plan');
else addon.classList.add('selected-plan');
};
addons.forEach((addon) => {
addon.addEventListener('click', (e) => toggleAddon(e, addon));
});
<label>
<div class="addon addon-2 selected-addon">
<input type="checkbox" name="addon-2" class="addon-chkbox" id="larger-storage">
<div class="addon-info">
<h3 class="addon-name">Larger Storage</h3>
<p class="addon-features">Extra 1TB of cloud save</p>
</div>
<div class="addon-pricing-box">
<h3 class="addon-pricing">$2/mo</h3>
</div>
</div>
</label>
Why when I click on this element, the function toggleAddon() runs twice and in first run console.log(addon.querySelector('input').checked) comes false and on second it comes true.
Thanks for the help.
This is another simplified example that will better show what I'm pointing out:
let counter = 0;
document.querySelector('.addon')
.addEventListener('click', event =>{
console.log(`[${++counter}]`);
console.log(event.target);
});
<label>
<div class="addon">
<input type="checkbox">
<div class="addon-info">
<h3 class="addon-name">Click here</h3>
</div>
</div>
</label>
The problem is with the <label> element, when it is clicked, it trigger another click event on the input element within.
I suggest using change event instead.
check it in codesandbox.
The whole label is for the input? so why not listen to the input click event only which will automatically be handled by the label? like:
let counter = 0;
document.querySelector('.addon-input')
.addEventListener('click', event =>{
console.log(`[${++counter}]`);
console.log(event.target);
});
<label>
<div class="addon">
<input class="addon-input" type="checkbox">
<div class="addon-info">
<h3 class="addon-name">Click here</h3>
</div>
</div>
</label>
This question already has answers here:
Getting the parent div of element
(7 answers)
Closed last month.
I have a project. I am working to find a container using an only child in JavaScript.
I want to add a class to the container of the req-address.
I want to take req in Javascript using an only child of this element. How to do it?
const search = document.querySelector('.search-form');
const addresses = document.querySelectorAll('.req-address');
search.addEventListener('keyup', function(e) {
addresses.forEach(function(address) {
if (address.innerHTML === search.value) {
address.classList.add('.search-active');
}
});
});
<div class="reqs-container">
<div class="req">
<div class="req-time">
<div class="req-time_from">13:00</div>
<span></span>
<div class="req-time_to">15:00</div>
</div>
<div class="req-restaurant">Argentina Grill</div>
<div class="req-address">Оболонь</div>
<div class="req-name">Аліна</div>
Instagram
Приєднатися
</div>
<div class="req">
<div class="req-time">
<div class="req-time_from">13:00</div>
<span></span>
<div class="req-time_to">15:00</div>
</div>
<div class="req-restaurant">Argentina Grill</div>
<div class="req-address">Хрещатик</div>
<div class="req-name">Аліна</div>
Instagram
Приєднатися
</div>
</div>
You needed to remove the dot from the class in .classList.add('.search-active')
To add to the parent div with class = req, you can use
address.closest('div.req')
Here is an alternative version which will toggle instead of just add.
Also I use input event since it handles paste too
Lastly I use includes, textContent, trim and toLowerCase to give the user a better chance to find stuff since innerHTML could have all sorts of whitespace
If you insist on the complete value in the address field must be typed to be found, change
address.textContent.toLowerCase().trim().includes(val)
to
address.textContent === val
const search = document.querySelector('.search-form');
const addresses = document.querySelectorAll('.req-address');
search.addEventListener('input', function(e) {
const val = this.value.toLowerCase();
addresses.forEach(address => address.closest('div.req').classList.toggle('search-active', address.textContent.toLowerCase().trim().includes(val)));
});
.search-active { color: green }
<input type="text" class="search-form" />
<div class="reqs-container">
<div class="req">
<div class="req-time">
<div class="req-time_from">13:00</div>
<span></span>
<div class="req-time_to">15:00</div>
</div>
<div class="req-restaurant">Argentina Grill</div>
<div class="req-address">Оболонь</div>
<div class="req-name">Аліна</div>
Instagram
Приєднатися
</div>
<div class="req">
<div class="req-time">
<div class="req-time_from">13:00</div>
<span></span>
<div class="req-time_to">15:00</div>
</div>
<div class="req-restaurant">Argentina Grill</div>
<div class="req-address">Хрещатик</div>
<div class="req-name">Аліна</div>
Instagram
Приєднатися
</div>
</div>
I have an rate app box,
I want the user to rate the app from 1-5 by clicking one of five buttons.
The button that was clicked should have color, all the others none. So if he clicked first on 3 and then 2, when clicking on 2 the color from the 3 button will be removed so only the last button was clicked (in this case 2) will have a color.
I DID manage to do it using button array, but i know for sure there is shorter way that isnt involved button array, only by code inside the function.
html:
<body>
<div class="container">
<div class="img-container">
<img src="./images/icon-star.svg" alt="" class="img-star">
</div>
<div class="content">
<h1>How did we do?</h1>
<p id="content-paragraph">
Please let us know how we did with your support request. All feedback is appreciated
to help us improve our offering!
</p>
</div>
<div class="buttons-container">
<button value = 1 class="choose " id="btn-one" onclick="paintBtn(this)">1</button>
<button value = 2 class="choose" id="btn-two" onclick="paintBtn(this)">2</button>
<button value = 3 class="choose" id="btn-three" onclick="paintBtn(this)">3</button>
<button value = 4 class="choose" id="btn-four" onclick="paintBtn(this)">4</button>
<button value = 5 class="choose" id="btn-five" onclick="paintBtn(this)">5</button>
</div>
<form action="thankYou.html">
<button id="submit">SUBMIT</button>
</form>
<script src="index.js"></script>
</body
js:
const buttonOne = document.getElementById("btn-one")
const buttonTwo = document.getElementById("btn-two")
const buttonThree = document.getElementById("btn-three")
const buttonFour = document.getElementById("btn-four")
const buttonFive = document.getElementById("btn-five")
const buttonsArr = [buttonOne, buttonTwo, buttonThree, buttonFour, buttonFive]
function paintBtn(button) {
buttonsArr.map(btn => btn.classList.remove("btn-clicked"))
button.classList.add("btn-clicked")
}
The shorter way of doing and not having to pass every button inside an array would be to do a document.querySelectorAll(".choose") and with that way you would be able to access the NodeList of matching elements to your class.
You can examine it just like any array. If the array is empty (that is, its length property is 0), then no matches would be found.
Otherwise, you can use standard array notation to access the contents of the list. You can use any common looping statement, such as a forEach statement.
It works just fine as in the attached example.
function paintBtn(newClickedButton) {
// clear styling from buttons
let buttons = document.querySelectorAll(".choose");
buttons.forEach(function(button){
button.classList.remove("btn-clicked");
});
newClickedButton.classList.add("btn-clicked");
}
.btn-clicked{
background-color: red;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
<body>
<div class="container">
<div class="img-container">
<img src="./images/icon-star.svg" alt="" class="img-star">
</div>
<div class="content">
<h1>How did we do?</h1>
<p id="content-paragraph">
Please let us know how we did with your support request. All feedback is appreciated
to help us improve our offering!
</p>
</div>
<div class="buttons-container">
<button value = 1 class=" choose" id="btn-one" onclick="paintBtn(this)">1</button>
<button value = 2 class=" choose" id="btn-two" onclick="paintBtn(this)">2</button>
<button value = 3 class=" choose" id="btn-three" onclick="paintBtn(this)">3</button>
<button value = 4 class=" choose" id="btn-four" onclick="paintBtn(this)">4</button>
<button value = 5 class=" choose" id="btn-five" onclick="paintBtn(this)">5</button>
</div>
<form action="thankYou.html">
<button id="submit">SUBMIT</button>
</form>
<script src="index.js"></script>
</body>
use onclick functionallaity to solve this problem okay
You can use instead document.getElementsByClassName to search for the button that is currently clicked instead of searching and traversing through all of the buttons:
function paintBtn(newClickedButton) {
const clickedButtonClassName = "btn-clicked";
const clickedButton = document.getElementsByClassName(clickedButtonClassName)[0];
clickedButton.remove(clickedButtonClassName);
newClickedButton.classList.add(clickedButtonClassName);
}
I am trying to create a TO DO List application. In this application when I click submit button of the formlayer, a new eventcard with all the data should be appended in the events div.
function reveal() {
document.getElementById("layer1").style.display = "block";
document.getElementById("formlayer").style.display = "block";
}
function hide() {
document.getElementById("layer1").style.display = "none";
document.getElementById("formlayer").style.display = "none";
}
function addEvent() {
document.getElementById("layer1").style.display = "none";
document.getElementById("formlayer").style.display = "none";
let title = document.getElementById("ftitle").value;
let time = document.getElementById("ftime").value;
let desc = document.getElementById("fdesc").value;
// this will create the card
let card = document.createElement("div");
card.classList.add("eventcard");
//title div
let cardtitle = document.createElement("div");
cardtitle.classList.add("eventtitle");
cardtitle.innerHTML = title;
card.appendChild(cardtitle);
//time div
let cardtime = document.createElement("div");
cardtime.classList.add("eventtime");
cardtime.innerHTML = time;
card.appendChild(cardtime);
//desc div
let carddesc = document.createElement("div");
carddesc.classList.add("eventdesc");
carddesc.innerHTML = desc;
card.appendChild(carddesc);
// del button
let cardbtn = document.createElement("button");
cardbtn.classList.add("delete");
cardbtn.innerHTML = "Delete";
card.appendChild(cardbtn);
// adding card to events
document.getElementById("events").appendChild(card);
}
<div id="container">
<div id="title">To Do List</div>
<div id="events">
<div class="eventcard">
<div class="eventtitle">Meeting</div>
<div class="eventtime">10:00 AM</div>
<div class="eventdesc">The meeting regarding the discussion of company sales</div>
<button class="delete">Delete</button>
</div>
</div>
<div id="addbtn" onclick="reveal()">Add Events</div>
</div>
<div id="layer1"></div>
<div id="formlayer">
<div id="text">Event Details</div>
<form>
<label for="Title">Event Title: </label>
<input type="text" name="ftitle" id="ftitle"> <br><br>
<label for="Time">Event Time: </label>
<input type="text" name="ftime" id="ftime"> <br><br>
<label for="Desc">Event Description: </label>
<input type="text" name="fdesc" id="fdesc"> <br>
<button class="fbut" onclick="addEvent()">Submit</button>
<button class="fbut" onclick="hide()">Close</button>
</form>
</div>
However, when I fill up the details in the form and click submit button, the new eventcard gets appended in the events div for a split second and that too with all the correct information and styling, but then automatically gets deleted for some reason. Why it is getting deleted? I have also tried placing the script in <head> and after the </body> hoping that would work but it doesn't.
Could you guys please point out where I am doing wrong? Also I am doing this on Firefox browser (it might be related).
Add type="button" to your two buttons in the form, otherwise they will submit the form. This will cause a reload, so you lose your dynamic updates.
<button type="button" class="fbut" onclick="addEvent()">Submit</button>
<button type="button" class="fbut" onclick="hide()">Close</button>
For a todo list, I'm trying to dynamically add a button as a child of a list element and a sibling of a text node as below.
<ul>
<li>Hello world <button>X</button></li>
</ul>
The unordered list is in the HTML file but the list, text and button need to be inserted dynamically from the JS file. Is this possible without wrapping the text in a p tag?
Here's the code:
const todoInputEl = document.querySelector(".todo__input");
const todoListEl = document.querySelector(".todo__list");
const todoItemEls = document.querySelector(".todo__item");
const deleteItemEl = document.querySelector(".done");
function addListItem() {
todoInputEl.addEventListener("keypress", function(e) {
if (e.keyCode === 13) {
let newListItem = createListItem(todoInputEl.value);
todoListEl.insertBefore(newListItem, todoListEl.childNodes[0]);
todoInputEl.value = "";
}
})
}
function createListItem(text) {
const deleteButton = document.createElement("button");
const newListElement = document.createElement("li");
const newParaElement = document.createElement("p");
deleteButton.setAttribute("type", "button");
deleteButton.classList.add("delete");
deleteButton.innerHTML = "×";
newListElement.appendChild(newParaElement);
newListElement.setAttribute("class", "todo__item");
newParaElement.setAttribute("class", "todo__p");
newParaElement.textContent = text;
newParaElement.parentNode.insertBefore(deleteButton, deleteButton.nextElementSibling);
return newListElement;
}
addListItem();
<main>
<div class="container">
<div class="todo">
<div class="todo__header">
<h1 class="todo__title">to dos</h1>
<label for="todo input">
<input type="text" class="todo__input" placeholder="enter a thing to do">
</label>
</div>
<div class="todo__body">
<ul class="todo__list">
</ul>
</div>
</div>
</div>
</main>
As you see, it works if the text is inside a p tag, but I can't get it to work insert to the same place if it's just a plain text node. I looked extensively for examples of this being done, but haven't found any yet.
DOM manipulation can be expensive (jQuery DOM manipulations - performance comparation?). Using innerHTML sidesteps this and in my opinion makes things simpler.
const todoInputEl = document.querySelector(".todo__input");
const todoListEl = document.querySelector(".todo__list");
const todoItemEls = document.querySelector(".todo__item");
const deleteItemEl = document.querySelector(".done");
function addListItem() {
todoInputEl.addEventListener("keypress", function(e) {
if (e.keyCode === 13) {
todoListEl.innerHTML += createListItem(todoInputEl.value);
}
})
}
function createListItem(text) {
let listTemplate = "<li>{{ToDo}} <button type='button' class='delete'>×</button></li>";
return listTemplate.replace("{{ToDo}}", text);
}
addListItem();
<main>
<div class="container">
<div class="todo">
<div class="todo__header">
<h1 class="todo__title">to dos</h1>
<label for="todo input">
<input type="text" class="todo__input" placeholder="enter a thing to do">
</label>
</div>
<div class="todo__body">
<ul class="todo__list">
</ul>
</div>
</div>
</div>
</main>