I have this function that should I think trigger an alert whenever one of the buttons in my page gets clicked, however nothing happens and if I open the console on the webpage, no errors show. why is it?
document.addEventListener('DOMContentLoaded' , () => {
document.querySelectorAll('.new-button').forEach (button => {
button.onclick = () => {
const buttonName = button.innerHTML;
alert(`you have selected the button! ${buttonName}`);
}
});
});
I am using the ES6 version of JavaScript if that's any help.
If you dynamically add elements, you have to attach the eventListener to some ancestor element that was statically added. documentworks, but you can add more specific elements for performance. This concept is called delegate listener.
document.addEventListener('click',function(e){
if(e.target && e.target.matches('.new-button')){
const buttonName = e.target.innerHTML;
alert(`you have selected the button! ${buttonName}`);
const newButton = document.createElement('button');
newButton.classList.add('new-button');
newButton.innerHTML = 'click me!!';
document.getElementById('container').append(newButton);
}
});
<div id="container">
<button class="new-button">click me!</button>
</div>
Related
Button text is not updating when the event is triggered. This is a submit event but I've also tried a click event. I've also tested the code outside of the event listener and it works correctly. So the problem seems to be within the event listener.
Here is the code:
let addToCartForms = document.querySelectorAll(".bundle-builder--add-to-bundle-form");
addToCartForms.forEach((el)=>{
let btn = el.querySelector('.add-to-cart');
let btnText = btn.innerText;
el.addEventListener("submit",(e)=>{
btn.innerText = "added"
});
})
Both click and submit work. Maybe the form is submitted so you can't see the effect?
let addToCartForms = document.querySelectorAll(".bundle-builder--add-to-bundle-form");
addToCartForms.forEach((el) => {
let btn = el.querySelector('.add-to-cart');
let btnText = btn.innerText;
el.addEventListener("submit", (e) => {
btn.innerText = "added"
});
})
<form class="bundle-builder--add-to-bundle-form">
<button class="add-to-cart">click</button>
</form>
Maybe this jQuery solution could work.
$('body').on('submit', '.bundle-builder--add-to-bundle-form .add-to-cart', function(e){
(e).preventDefault();
(this).text('added');
setTimeout(function() {
(this).submit()},500)
})
my main project is too complicated to show here so I created a small script demonstrating the problem I am working on. In simple terms, I need to create a button that once clicked, generates a button that also has an event listener that returns that button's id to the console.
See code below:
button_number = 0
create_buttons = document.getElementById('create_buttons')
div = document.getElementById('div')
create_buttons.addEventListener('click', e=>{
button_number += 1
new_button = document.createElement('button')
new_button.setAttribute('id', 'button'+button_number)
new_button.innerHTML = 'What number am I?'
new_button.addEventListener('click', show_button_number)
div.appendChild(new_button)
})
function show_button_number () {
let number = button_number
button = document.getElementById('button' + number)
console.log(button.id)
}
<div id="div">
<button id="create_buttons">Create a button!</button>
</div>
As written, all generated buttons return the button id of the most recently generated button versus their own id. Is there anyway I can change the anonymous function to return the button id of the button that was clicked? In order to integrate this into my main project, I need to create the event listener for the dynamically generated buttons using an anonymous function.
You could make a higher-order function, one that takes the current button number as an argument and returns a function using it:
const makeListener = num => () => {
const button = document.getElementById('button' + num)
console.log(button.id)
};
new_button.addEventListener('click', makeListener(button_number))
Or, you may not need the ID at all, just pass the element itself:
create_buttons.addEventListener('click', e=>{
const btn = div.appendChild(document.createElement('button'));
btn.textContent = 'What number am I?'
btn.addEventListener('click', () => {
console.log(new_button);
});
});
The major way to do that is to use event delegation mechanim
const divParent = document.getElementById('div')
var button_number = 0
divParent.addEventListener('click', e =>
{
if (!e.target.matches('button')) return // ignore clicks from other things
if (e.target.id === 'create_buttons')
{
let new_button = document.createElement('button')
new_button.id = 'button' + ++button_number
new_button.textContent = 'What number am I?'
divParent.appendChild(new_button)
}
else
{
console.clear()
console.log( e.target.id )
}
})
<div id="div">
<button id="create_buttons">Create a button!</button>
</div>
I have a little problem that I replicated in the little code snippet below (in the most simple way possible, however it still shows the problem I am facing).
Here is the snippet :
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me';
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
dynamicBtnClickListener();
}
function dynamicBtnClickListener() {
resBox.addEventListener('click', function handler(e) {
console.log('You clicked me !');
});
// THE SOLUTION I FOUND FOR THE MOMENT :
//const btns = document.querySelectorAll('button.js-click');
//btns.forEach(btn => {
// btn.addEventListener('click', function handler(e) {
// console.log('You clicked me !');
// });
//});
}
<input type="text" id="search">
<div id="results"></div>
As you can see in the snippet, I have a first listener on input that generates a list of buttons when you type in it. When it is empty, the buttons disappear. In my real world case, it is a search input, that when a user types in, calls a function that populates a result box below it with results from DB.
I then have an on click listener on the buttons. In the code snippet, I simply put a console('You clicked me') when you click on the buttons. In my real app, it takes the result item (each result is an user) and inserts it in a table.
The problem appears when you open, close, then re-open the results box. This is done by inputing something, clearing the input, then re-input something. When you do that and then click on one of the buttons, it fires the click event on them as many times as you opened / closed the result box, so you will see the "You clicked me" on console multiple times.
I have done some research and most of it calls for using event.stopPropagation() and / or removing the event listener(s). I did try all these possible solutions, in every way I could think of, but I couldn't make it work.
Anyways I found a way around this (the commented portion of the dynamicBtnClickListener() function), but I feel it is not optimal. It consists of getting all the buttons with querySelectorAll(), then loop through them and add the click listener to every one of them, but I do not think it is optimal nor best-practice like. This is why I come here to ask if maybe there is a better solution, possibly one that keeps the click listener on the results box (if that is the most optimal solution. Is it by the way ?).
So even though I found a solution to this problem, could someone please tell me what is the best practice and optimal way of doing this ?
Thank you very much for your help
Each time the you type in the text area, resBox is accessed each time and the actual element resBox gets a new event listener every time(the buttons don't have any specific listener themselves, so I make EACH BUTTON have a specific listener individually)
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me';
btn.addEventListener('click',function(ev){console.log('You clicked me !')})
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
}
<input type="text" id="search">
<div id="results"></div>
Now, here is an example that only has one event listener but would completely handle the situation >:D
Technically this should be faster(since one event listener compared to many), but personally I prefer this option because it "feels better" due to one function controlling the whole button layout(which would make it less "rigid")
PS: The speed difference is so insignificant, you can pick and choose(but if a whole chuck ton of buttons, yea this becomes better)
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
const randChars=()=>{ //random characters to prove ONE event listener can work for multiple buttons in resBox
let arr=["a","b","c","d","e"]
let randIndex=()=>Math.floor(Math.random()*arr.length)||1
let n=randIndex(); let returnChar=""
for(let i=0;i<n;i++){returnChar+=arr[randIndex()]}
return returnChar
}
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
resBox.addEventListener('click',function(ev){ //single event listener for all buttons
let currentButton=ev.path[0]
if(currentButton.tagName!="BUTTON"){return;} //if a button was not clicked
console.log("Button with text\n'"+currentButton.innerText+"'\nwas clicked")
})
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me '+randChars();
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
}
<input type="text" id="search">
<div id="results"></div>
I have a button that append a template containing few divs and buttons.
The first button "btnGenerateResult_0" works fine probably because it exists when the page loads, while "btnGenerateResult_1" gets created but doesn't work.
How and where can I fix the event listener and attach it to these buttons, I need four buttons?
The bellow code is inside a document.readey function():
$(`#btnGenerateResult_${active}`).click(function ()
{
var newrow = "";
for (var i = 1; i < TablesObj[activeTableObj[active]].length; i ++ )
{
newrow = "";
newrow= "<tr><td>" + TablesObj[activeTableObj[active]][i][0] +
"</td><td>" + TablesObj[activeTableObj[active]][i][3] +
"</tr>";
$(`#resultTableMain_${active}`).append(newrow);
}
});
One option is to add the event listener to the newly created button after you have created it:
const container = document.getElementById('container');
function handleButtonClicked(e) {
console.log(`Button ${ e.target.textContent } clicked!`);
}
Array.from(container.children).forEach((button) => {
button.onclick = handleButtonClicked;
});
setTimeout(() => {
const newButton = document.createElement('BUTTON');
newButton.textContent = 'C';
container.appendChild(newButton);
// Add the event listener to this new button as well:
newButton.onclick = handleButtonClicked;
}, 2000);
<div id="container">
<button>A</button>
<button>B</button>
</div>
Another option, which might scale better, is to use event delegation. It consists of adding a single event listener to the parent or any common ancestor for all those buttons. The click event will then bubble app and you can use e.target to find out which button the event originated from:
const container = document.getElementById('container');
container.onclick = (e) => {
if (e.target.tagName !== 'BUTTON') {
console.log('Something else clicked...');
return;
}
console.log(`Button ${ e.target.textContent } clicked!`);
};
setTimeout(() => {
// See how this works with dynamically created buttons as wel, withiout adding the
// event listener to each of them individually. However, the event might be
// triggered from undesired elements as well (click in the space between the
// buttons), so you need to check for that, as you can see above.
const newButton = document.createElement('BUTTON');
newButton.textContent = 'C';
container.appendChild(newButton);
}, 2000);
<div id="container">
<button>A</button>
<button>B</button>
</div>
I'm learning basic javascript events and would like to know how I can undo a click event.
I have a simple event that changes text using the changeText method.
That's only half of what I wan't to do. As user experience is important , I'd like for this event to be "undone" when the user clicks anywhere on the page.
So essentially after the user has clicked the button and changed text, they should be able to click anywhere on the page causing the text to go back to its default "I will change".
const changeText = () => {
const p = document.querySelector('p');
p.textContent = "I changed because of an event listener.";
}
// Listen for click event
const button = document.querySelector('button');
button.addEventListener('click', changeText);
<button>Click me</button>
<p>I will change.</p>
You could add another function revertText which undoes the change, and call that when the document is clicked. If you do this, however, it will be triggered when you click the button as well. So, in order to only tigger the button event when the button is clicked use e.stopPropagation() to stop the document click event from also executing.
See working example below:
const changeText = e => { // Pass event (e) argument into function
e.stopPropagation(); // Stop the document click event from running
const p = document.querySelector('p');
p.textContent = "I changed because of an event listener.";
}
const revertText = () => {
const p = document.querySelector('p');
p.textContent = "I will change";
}
// Listen for click event
const button = document.querySelector('button');
button.addEventListener('click', changeText);
document.addEventListener('click', revertText) // Add second event listener on the document
<button>Click me</button>
<p>I will change.</p>
So you just want another click event but on another object.
In your HTML you have to make some sort of container and then:
const changeTextBack = () => {
const p = document.querySelector('p');
p.textContent = "I will change.";
}
// Listen for click event
const buttonContainer = document.querySelector('button-container');
buttonContainer.addEventListener('click', changeTextBack);
you can attach an eventListener to the document:
document.addEventListener('click', changeTextBack)
changeTextback (event) {
// use event target to make sure he is not clicking on button
// use same methods as in changeText method to target element and alter text
}
The document represents the entire page. You could also attach it to the window.
https://developer.mozilla.org/en-US/docs/Web/API/Document
https://developer.mozilla.org/nl/docs/Web/API/Window
was this helpful?
You can bind an event on the document and store the default text somewhere globally. See the solution below:
let obj = {}; // object to store the def text of "p"
const changeText = (event) => { // pass the event object here
event.stopPropagation(); // stop the event to propagate back to parent
const p = document.querySelector('p');
if (event.target.tagName === "BUTTON") { // if clicked item is button
if (!obj['p']) {
obj['p'] = p.textContent; // store the def text in obj
}
p.textContent = "I changed because of an event listener."; // change the text
} else { // otherwise
p.textContent = obj.p; // change back the def text
}
}
// Listen for click event
const button = document.querySelector('button');
button.addEventListener('click', changeText);
document.addEventListener('click', changeText);
<button>Click me</button>
<p>I will change.</p>