why this eventListner callback function is running twice - javascript

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>

Related

Prevent buttons with same class from being clicked simultaneously

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>

Why doesn't any event work in dynamically loaded code?

I load an HTML page inside a modal
When I put the code out of the modal, the event works ( click, input, and others )
NOTE: I don't use and I don't want to use JQuery ( never )
Demo: https://codepen.io/jonathan_silva/pen/vYKqrvE?editors=0010
I've been trying to make it work for 3 days. It looks like everything is fine but...
Help me
const codeHTML = () => {
const code = `
<div id="page">
<div class="steps">
<article class="step1">
<div class="options-grid">
<div class="select-box">
<div class="options business-options">
<div class="business-option">
<input type="radio" class="radio" />
<label>Business A</label>
</div>
<div class="business-option">
<input type="radio" class="radio" />
<label>Business B</label>
</div>
<div class="business-option">
<input type="radio" class="radio" />
<label>Business C</label>
</div>
</div>
<div class="select-business">Select Business</div>
</div>
</div>
</article>
</div>
</div>
`;
return code;
}
/* MODAL */
const modal = async ({ target }) => {
const html = codeHTML(); // Load HTML page
let section = document.getElementById('modal-page');
if (!section) {
return;
}
document.getElementById('modal-content').insertAdjacentHTML('afterbegin', html);
section.classList.add('modal');
const onSectionClick = ({ target }) => {
if (!target.classList.contains('modal-close')) {
return;
}
section.classList.remove('modal');
section.removeEventListener('click', onSectionClick);
document.querySelector('#page').remove();
}
section.addEventListener('click', onSectionClick);
}
const openModal = document.querySelector('.announce a');
openModal.addEventListener('click', event => modal(event));
/* SELECT */
const selectBusiness = document.querySelector('.select-business');
const businessContainer = document.querySelector('.business-options');
const optionsBusiness = document.querySelectorAll('.business-option');
if (selectBusiness !== null) {
selectBusiness.addEventListener('click', e => {
console.log(e); // Nothing happens
businessContainer.classList.toggle("active");
});
}
The elements you're trying to querySelector aren't there when you call querySelector, since you only add them to the DOM within the modal() function. (You can do e.g. console.log(selectBusiness) to see that with your own eyes.)
You'll need to move that event binding in there, after the insertAdjacentHTML call.

How to avoid event from div below object?

I have this code:
<div style="width:200px;height:100px;border:solid 1px" onclick="alert(1)">
Title
<br />
Subtitle
<div style="float:right">
<input type="checkbox" />
</div>
</div>
I want to redirect the user to a url when he clicks on the div (where the alert is now) but I want to allow the user with functionality when he clicks on the checkbox.
Is it possible to allow the checkbox to be clicked and change status without invoking the alert(1) from the div below ?
You need to use event.stopPropagation(); function. This function prevents further propagation of the current event in the capturing and bubbling phases.
<div style="width:200px;height:100px;border:solid 1px" onclick="alert(1)">
Title
<br />
Subtitle
<div style="float:right">
<input type="checkbox" onclick="onCheckBoxCicked(event)"/>
</div>
function onCheckBoxCicked(event) {
alert(2)
event.stopPropagation();
};
You will need to use stopPropagation() method on the checkbox click event handler, so the click of the checkbox won't trigger the click of its parents divs:
HTML:
<input type="checkbox" onclick="avoidAlert(event)" />
JS:
function avoidAlert(event) {
event.stopPropagation();
}
function avoidAlert(event) {
event.stopPropagation();
}
<div style="width:200px;height:100px;border:solid 1px" onclick="alert(1)">
Title
<br /> Subtitle
<div style="float:right">
<input type="checkbox" onclick="avoidAlert(event)" />
</div>
</div>
MDN Ref for stopPropagation() method:
The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.
You could either try event.stopPropagation() inside an extra event handler on the checkbox, or simply check inside the div's event handler if the target of the click has been the checkbox:
var theDiv = document.querySelector('.outer'),
theCheckbox = document.querySelector('input');
theDiv.addEventListener('click', divClicked);
function divClicked(e) {
if (e.target !== theCheckbox) {
alert('You clicked the div!');
}
}
<div class="outer" style="width:200px;height:100px;border:solid 1px">
Title
<br />
Subtitle
<div style="float:right">
<input type="checkbox" />
</div>
</div>

Unable to fire javascript event OnClick of checkbox

So I have a checkbox control that I cannot get to fire an OnClick event. I've tried a number of different ways: Binding the event onload and adding the event as a parameter in the <input type="checkbox"> tag.
Here is my most recent iteration of code. I'm trying to fire an Alert just to confirm that it changed.
<section class="border-bottom">
<div id="approx" class="content">
<h3>This is my approximate location</h3>
<div class="form-control-group">
<div class="form-control form-control-toggle" data-on-label="yes" data-off-label="no">
<input type="checkbox" />
</div>
</div>
</div>
</section>
JavaScript:
$('input[type="checkbox"]').bind('click', function() {
alert("OK");
})
I also wouldn't mind being able to run it via the following input:
<input type="checkbox" onclick="runMyFunction(); return false;" />
You should use one of the following methods:
.click(function() { ... })
.change(function() { ... })
.on('click', function() { ... })
HTML:
<div section class="border-bottom">
<div id="approx" class="content">
<h3>This is my approximate location</h3>
<div class="form-control-group">
<div class="form-control form-control-toggle" data-on-label="yes" data-off-label="no">
<input type="checkbox" id="checkbox" />
</div>
</div>
</div>
</section>
JavaScript:
$('#checkbox').change(function() {
alert("OK");
});
Some of the plugins require:
$("#checkbox").on('change', function() {
// content
});
or look for the manual of your plugin (sometimes you need to use: pluginName.change exchange for change)
First use your jquery function inside $(document).ready function to make sure the dom is ready like :
$(document).ready(function () {
$('input[type="checkbox"]').bind('click', function () {
alert("OK");
})
});
https://jsfiddle.net/4fmL1zfr/8/

How to use jQuery to assign a class to only one radio button in a group when user clicks on it?

I have the following markup. I would like to add class_A to <p class="subitem-text"> (that holds the radio button and the label) when user clicks on the <input> or <label>.
If user clicks some other radio-button/label in the same group, I would like to add class_A to this radio-button's parent paragraph and remove class_A from any other paragraph that hold radio-buttons/labels in that group. Effectively, in each <li>, only one <p class="subitem-text"> should have class_A added to it.
Is there a jQuery plug-in that does this? Or is there a simple trick that can do this?
<ul>
<li>
<div class="myitem-wrapper" id="10">
<div class="myitem clearfix">
<span class="number">1</span>
<div class="item-text">Some text here </div>
</div>
<p class="subitem-text">
<input type="radio" name="10" value="15" id="99">
<label for="99">First subitem </label>
</p>
<p class="subitem-text">
<input type="radio" name="10" value="77" id="21">
<label for="21">Second subitem</label>
</p>
</div>
</li>
<li>
<div class="myitem-wrapper" id="11">
<div class="myitem clearfix">
<span class="number">2</span>
<div class="item-text">Some other text here ... </div>
</div>
<p class="subitem-text">
<input type="radio" name="11" value="32" id="201">
<label for="201">First subitem ... </label>
</p>
<p class="subitem-text">
<input type="radio" name="11" value="68" id="205">
<label for="205">Second subitem ...</label>
</p>
<p class="subitem-text">
<input type="radio" name="11" value="160" id="206">
<label for="206">Third subitem ...</label>
</p>
</div>
</li>
</ul>
DEMO: http://jsbin.com/ebaye3
since you are putting all inside P you can use it!
$(function($) {
$('.subitem-text').click(function() {
$(this).parent().find('.subitem-text').removeClass('class_A');
if ( $(this).children(':radio').is(':checked') ) // for sake! ;-)
$(this).addClass('class_A');
});
//you can also write it like this:
$('.subitem-text :radio').click(function() {
$(this).parent().parent().children().removeClass('class_A');
if ( $(this).is(':checked') )
$(this).parent().addClass('class_A');
});
});
You can do something like this:
// find all the radio inputs inside a subitem-text
$('.subitem-text input[type=radio]').bind('change', function() {
// find our parent LI
var $li = $(this).closest('li');
// remove any "class_A"
$li.find('.class_A').removeClass('class_A');
// find the subitem with the checked input and add "class_A"
$li.find('.subitem-text:has(input[checked])').addClass('class_A');
});
jsbin preview/demo
Like this:
$(':radio').click(function() {
$(this).closest('ul').find('.subitem-text').removeClass('active');
$(this).closest('.subitem-text').addClass('active');
});
There is no set way to do this since it is dependent on the html in your document. The simplest way to do this is to bind to each of your radio input elements but you can also use event delegation and bind to the div.myitem-wrapper, li, parent ul or event body tag.
Just binding click handlers to each of the input elements we are interested in:
$("div.myitem-wrapper input[type=radio]").bind('change', function (event) {
$(event.target).is(':checked').closest('p')
.addClass('class_A')
.siblings('p.subitem-text').removeClass('class_A');
});
Same thing but using event delegation to reduce the number of handlers to one. This can really speed things up if you find that you are binding to a large number of elements.
$("#id_of_the_parent_UL").bind('change', function (event) {
var $target = $(event.target);
if ($target.is('input[type=radio]:checked')) {
$target.closest('p')
.addClass('class_A')
.siblings('p.subitem-text').removeClass('class_A');
}
});
Note that it is perfectly valid to say $(this) instead of $(event.target) but it will give different results if and when you start moving to event delegation. An added advantage is that it will be easier for you or the next guy to understand the code in in two months if you go with event.target.

Categories

Resources