Event listener to dynamically created buttons - javascript

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>

Related

Javascript - Creating event listener that returns id of dynamically generated content

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>

Create multiple Buttons with ClickListener and identify what Button was clicked? (JavaScript)

Think of the Uno Card Game logic for example, how could I get the clicked Card on the players hand, when I create the cards on the players hand dynamically by clicking on a draw button?
something like:
var btn = document.createElement("BUTTON");
btn.onclick = () => {
alert(btnCounter);
}
btnCounter++;
but the onclick function should be written once and then be saved for this button.
One option is to rely on event bubbling. We can add the event listener to the container for our hand and then, on click, make sure the clicked element was a button. In this example, I put our card's value in the button's dataset, but you can grab the info from anywhere on the target.
const hand = document.querySelector("#hand");
hand.addEventListener("click", function(e) {
if (e.target.tagName !== "BUTTON") return;
console.log(e.target.dataset.card);
});
function drawCards(numberOfCards) {
for (let i = 0; i < numberOfCards; i++) {
const button = document.createElement("button");
const card = Math.floor(Math.random() * 10);
button.setAttribute("data-card", card);
button.innerHTML = card;
hand.appendChild(button);
}
};
drawCards(5);
<div id="hand"></div>
In the context where the button (and its id) are created, bind the id in the o'clock function...
const ids = ['1', '2', '3'];
var parent = document.getElementById('parent')
ids.map(id => {
let btn = document.createElement('button');
btn.setAttribute('id', id)
btn.innerHTML = `Button ${id}`
parent.appendChild(btn)
btn.onclick = event => {
alert(id)
}
})
<div id="parent">
</div>

Getting the ID from a button created with JavaScript

So I am creating a few buttons dynamically via javascript.
function dobutton() {
for (i=0; i<eigenschaften; i++){
var btn = document.createElement("BUTTON");
btn.className = 'button black';
btn.id = eigenschaftsarray[i];
btn.name = 'pickbutton';
btn.innerHTML = eigenschaftsarray[i].split("_").join(" ");
document.getElementById('auswahl').appendChild(btn);
document.getElementById('auswahl').innerHTML += " ";
}
}
eigenschaften = 39
eigenschaftsarray = just some names
Now I want to get the ID of the button I click. I was not able to get anything from this JavaScript - onClick to get the ID of the clicked button running because of my method using js to create those buttons. Adding the onClick event to my code just instantly alerts 39 "unknown".
Can someone provide me some help, I am just using Javascript, no jQuery :)
Thanks!
When you create elements dynamically, you have to keep in mind that you can bind events to them only after they are available in the DOM.
Here is a working demo: jsfiddle demo
In the demo, we bind an event listener ("click") to the parent that contains the buttons. The parent is a static element, already available in the DOM.
The JavaScript code is:
var eigenschaften = 3;
var eigenschaftsarray = ["b0","b1","b2"];
// fn: detect if a button was clicked -> get id
function buttonClick(e){
// check if the clicked element is a button
if (e.target.tagName.toLowerCase() == "button") {
var btn = e.target;
// alert the user what button was clicked
alert("button id="+btn.id);
}
}
// fn: create buttons dynamically
function dobutton() {
// identify parent
var parent = document.getElementById('auswahl');
// create buttons dynamically
for (i=0; i<eigenschaften; i++){
var btn = document.createElement("button");
btn.className = 'button black';
btn.id = eigenschaftsarray[i];
btn.name = 'pickbutton';
btn.innerHTML = eigenschaftsarray[i].split("_").join(" ");
// append btn to parent
parent.appendChild(btn);
parent.innerHTML += " ";
}
// add "click" listener on parent
parent.addEventListener("click", buttonClick);
}
// create buttons
dobutton();
Step 1: Add Buttons Dynamically To DOM
HTML :
<body>
<h1>
HelloWorld HelloWorld
</h1>
<div id="stackMe"></div>
</body>
Javascript :
const data = [{'Name' : 'Hello', 'Id' : 1},
{'Name' : 'World', 'Id' : 2}];
window.addEventListener('DOMContentLoaded', function (){
console.log('DOM loaded');
generateButtons(data);
});
function generateButtons(data) {
const buttonResults = data.map(bt =>`<button id= "${bt.Id}" onClick="btClicked(${bt.Id})">Button ${ bt.Name }</button>`);
document.getElementById("stackMe").innerHTML = buttonResults;
}
Step 2: Add an event listener for the button clicked
function btClicked(currentId) {
let elementClicked = data.find(d => d.Id == currentId);
console.log(elementClicked);
}
Output: Out after the button is clicked

Listener not being added to a creted div

So I have the following function called addPanels() which adds an event listener to every element with class "accordion"
function addPanels(){
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
var arrow = this.children[0].children[0];
if (panel.style.display === "block") {
arrow.src = "Iconos/arrow_up.png";
} else {
arrow.src = "Iconos/arrow_down.png";
}
});
}
}
Then I have this:
$(document).ready(function() {
api.routines.getRoutines().done(function(data) {
$.each(data, function(i, item){
var list = document.getElementById("routines_list");
var acc = document.createElement("div");
acc.setAttribute("class", "accordion");
var panel = document.createElement("div");
panel.setAttribute("class", "panel");
//more stuff
});
acc.appendChild(panel);
list.appendChild(acc);
// ...
});
addPanels(); //WORKS ONLY WITH PREVIOUS ACCORDIONS NOT WITH THE NEW ONE! WHY??
}).fail(function(jqXHR, textStatus, errorThrown) {
//do sth
});
});
addPanels seems to add the listener ONLY to the elements with class ACCORDION that weren't loaded from API (they were already in the HTML). Why is this happening?
Because at the time the listeners were added, your new elements didn't exist.
api.routines.getRoutines().done specifies a callback function that should run at some future point in time - we don't know when that will be. So, while that API goes out to begin its process, addPanels(); is immediately called next, which sets up the event handlers, but getRoutines() hasn't completed yet, so you wind up only setting up the handlers on the panels that exist before getRoutines() is finished. This is how asynchronous processing works.
To solve a problem like this, you need to use event delegation, which is when you set up the event handler on an object that exists right from the start and then you capture events that were initiated by descendant elements via event bubbling. When the event is handled, you check the source of the event and see if it matches the criteria you want. This way, it doesn't matter when the element gets created.
Here's a simplified example:
// Set up a click event handler on the document (which all descendant element events will bubble up to)
document.addEventListener("click", function(evt){
console.log("Click event handled!");
// Is the clicked element NOT the document?
if(evt.target.classList){
// Check for dynamically created elements that were given
// the "dynamic" class upon creation (you can use any criteria you want here)
if(evt.target.classList.contains("dynamic")){
console.log("Click event for dynamically created element, " + evt.target.nodeName + " handled by document");
}
}
});
// Now, we'll create some elements and inject them into the DOM AFTER the event handler
// has been set up. Also, these elements were not specifically set up with event handlers.
var div = document.createElement("div");
div.classList.add("dynamic");
div.textContent = "Click Me";
document.body.appendChild(div);
var btn = document.createElement("input");
btn.classList.add("dynamic");
btn.type = "button";
btn.value = "Click Me!";
document.body.appendChild(btn);
.dynamic { font-weight:bold; color:#800080; }

element.removeEventListener('mousedown', externalFunction, useCapture); is not working

I need to first remove the event listener before dynamically adding more elements which also need the same event listener. I am using an external function name (not an anonymous function) and specifying the same useCapture value in both the add and remove.
The function is nested within another function. < suspected problem was the problem
You can see the problem by clicking the first "add button" more than once. The first click adds one more button, the second click adds two more, the third click adds four more, etc. Each click should only add one more. I guess the return value of removeEventListener is always undefined so I can only tell that removal did not work from the duplicate events.
var app = function() {
console.log('app');
var setup = function() {
console.log('setup');
var addButton = function(e) {
console.log(e);
var button = e.target;
var newButton = document.createElement('BUTTON');
newButton.innerText = 'add another button';
button.parentNode.appendChild( newButton );
setup();
}
var buttons = document.querySelectorAll('button');
for(var i=0; i<buttons.length; i++) {
var button = buttons[i];
button.removeEventListener('mousedown', addButton, false);
button.addEventListener('mousedown', addButton, false);
}
}
setup();
}
app();
<div>
<button>add button</button>
</div>
Removing the addButton function from the nest fixed the problem.
Also, defining app.addButton within app.setup or within the nest causes app.addButton to be overwritten each time app.setup is called which destroys the reference needed for removeEventListener. So the name alone is not all that matters. It must be the original function, not an exact copy.
var app = {};
app.addButton = function(e) {
console.log(e);
var button = e.target;
var newButton = document.createElement('BUTTON');
newButton.innerText = 'add another button';
button.parentNode.appendChild( newButton );
app.setup();
}
app.setup = function() {
console.log('setup');
var buttons = document.querySelectorAll('button');
for(var i=0; i<buttons.length; i++) {
var button = buttons[i];
button.removeEventListener('mousedown', app.addButton, false);
button.addEventListener('mousedown', app.addButton, false);
}
}
app.init = function() {
console.log('app');
app.setup();
}
app.init();
<div>
<button>add button</button>
</div>

Categories