brienna
brienna

Reputation: 1604

How do I attach eventListeners to dynamically generated radio buttons?

I want to attach an event listener to each radio input click, so that I can retrieve that radio input's value and assign it as the value of a new property in the current question object in the allQuestions array.

I'm not sure why I'm having difficulty making choice.addEventListener work.

My HTML:

<div id="quiz"></div>
<button id="button">Next</button>

My JavaScript:

var allQuestions = [{question: "What is the capital of the Czech Republic?", 
                choices: ["Skopje", "Budapest", "Prague", "Bucharest"], 
                correctAnswer: 2},

                {question: "When was the Declaration of Independence signed?",
                choices: ["1492", "1776", "1812", "1791"],
                correctAnswer: 1}];

var quiz = document.getElementById("quiz");
var index = -1;

var next = function() {
    index += 1;
    quiz.innerHTML = "";
    for (var i = 0; i < allQuestions[index]['choices'].length; i++) {
        var choice = document.createElement('input');
        choice.type = 'radio';
        choice.name = 'choices';
        choice.value = i;
        choice.addEventListener('click', function() {
            alert('hi');
        });

        quiz.appendChild(choice);
        quiz.innerHTML += allQuestions[index]['choices'][i] + '<br>';
    }
};

var button = document.getElementById('button');
button.addEventListener('click', next);

Upvotes: 2

Views: 1171

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074148

You're trying to use markup and DOM elements at the same time. Here's the main problem:

quiz.appendChild(choice);
quiz.innerHTML += allQuestions[index]['choices'][i] + '<br>';

The first line appends a DOM element with an attached event handler to the quiz element. The second line converts the contents of quiz to an HTML string, appends some further text (markup) to that string, and then parses that HTML and replaces the content of quiz with the parsed result. That wipes out anything not represented in the HTML, including the dynamically-added event handler.

The solution is not to do innerHTML += ..., which is almost always a bad idea. In this particular case, you can do this:

quiz.appendChild(choice);
quiz.appendChild(document.createTextNode(allQuestions[index]['choices'][i]));
quiz.appendChild(document.createElement('br'));

...which also has the advantage that any characters that are special in HTML (like <) are treated literally (because that's what createTextNode does).


Now, having said that, you don't need handlers on every radio button. Instead, you can use event delegation by using a handler on your quiz element, and then using e.target within the handler to know which radio button was clicked:

quiz.addEventListener("click", function(e) {
    if (e.target.tagName.toUpperCase() === "INPUT") {
        var value = e.target.value; // The value of the clicked radio button
    }
});

That way, you don't have to worry about adding handlers dynamically; just do it once, after the quiz element is known to exist, and you can update its content all you want without having to worry about attaching handlers to the radio buttons within.

To do event delegation in general, you usually have to loop starting from e.target and going to its parentNode in order to find the element you're interested in (say you're interested in a div, but the click was on a span inside it); but of course, input elements can't have any elements inside them, so it's simple here.

You're probably wondering why the check on the tag name. If you associate your radio buttons with label elements (as is usually good practice), either by putting the input inside the label or using the for attribute on the label, you'll see two clicks when you click the label: One on the label itself, and a second one on the input that the label relates to. We're only interested in the one on the actual radio button.

Here's a simple example of the delegation above:

var quiz = document.getElementById("quiz");
quiz.addEventListener("click", function(e) {
    if (e.target.tagName.toUpperCase() === "INPUT") {
        var value = e.target.value; // The value of the clicked radio button
        console.log("The value of that radio button is " + value);
    }
});
for (var n = 0; n < 5; ++n) {
    var div = document.createElement("div");
    div.innerHTML = '<label><input type="radio" value="' + n + '" name="answer"> Radio ' + n + '</label>';
    quiz.appendChild(div);
}
<div id="quiz"></div>

Upvotes: 4

Related Questions