kometen kometen - 7 months ago 11
Javascript Question

Adding addEventListener() in loop only works for last button

I get an json-array from dynamodb and is adding an addEventListener() to a button while traversing it. But only the last button responds to this.

This has been asked before on SO and was the first hit on google so I changed the loop to use closures. But I still can't attach an addEventListener() to other than the last button.

I initially tried this:

for (var i = 0; i < array_length; ++i) {
(function(j) {
var obj = data.Items[j];

adiv.innerHTML = adiv.innerHTML + "<div class='row'><div class='center-div six columns'>" + obj.Event + ", " + obj.Location + "</div></div>";
adiv.innerHTML = adiv.innerHTML + "<div class='row'><div class='center-div six columns'>" + obj.Date + ", " + obj.Time + "</div></div>";
adiv.innerHTML = adiv.innerHTML + "<div class='row'><div class='center-div six columns'><button id='yay_button_" + i + "' class='button-primary'>Deltag</button></div></div>";

var elem = document.getElementById('yay_button_' + j);
elem.addEventListener('click', function() {
alert('id: ' + j);
});
})(i);
}


And then this shorter version:

(function(j) {
document.getElementById('yay_button_' + j).addEventListener('click', function() {
alert('id: ' + j);
});
}(i));


Another variant I tried was to create a button using new:

function Button(id, number) {
document.getElementById(id + number).addEventListener('click', function() {
alert('click: ' + id + number);
});
}

new Button('yay_button_', i);


I tried both safari and chrome on el capitan without errors in the console. The buttons has the correct id when I inspect it, yay_button_0 and _1.

Answer

The main problem is that you redefine the HTML of adiv completely every time you iterate. This means that any event handler you had already bound to a child element of adiv is lost the moment you do adviv.innerHTML = .... Although you assign the same HTML plus some new one, you don't assign the event handlers that were previously defined. So they are lost.

Only the event handler you assign at the last iteration is not destroyed in this way.

Solution

A quick solution would be to first loop to create all the HTML, and then do a separate loop to assign the event handlers:

// First add all the new content:
var html = adiv.innerHTML;
for (var i = 0; i < array_length; ++i) {
    var obj = data.Items[i];

    html += "<div class='row'><div class='center-div six columns'>" + obj.Event + ", " + obj.Location + "</div></div>";
    html += "<div class='row'><div class='center-div six columns'>" + obj.Date + ", " + obj.Time + "</div></div>";
    html += "<div class='row'><div class='center-div six columns'><button id='yay_button_" + i + "' class='button-primary'>Deltag</button></div></div>";
}
adiv.innerHTML = html;

// Now bind the event handlers
// By using "let" instead of "var" the right value is retained
// in the handlers
for (let i = 0; i < array_length; ++i) {
    var elem = document.getElementById('yay_button_' + i);
    elem.addEventListener('click', function() {
        alert('id: ' + i);
    });
}