简体   繁体   中英

Why does this jQuery .change event stop working after .click event

I have a script that displays questions from an array and switches from one question to the next using the .click event on two buttons: previous and next. When I load the page the $(":radio").change selector works fine, but when I click on previous or next it stops working.

I tried changing the $("#previous) to a console.log to see if .click was the culprit and it seems to be working. I assume there is something wrong with my display function but I can't seem to figure out why.

Here is the jsFiddle . And here is the project on GitHub

Questions Array

var quizQuestions =
[{
    question: "1. Who is Prime Minister of the United Kingdom?",
    choices: ["David Cameron", "Gordon Brown", "Winston Churchill", "Tony Blair"],
    correctAnswer:0
},
{
    question: "2. Who is Prime Minister of the United Kingdom?",
    choices: ["David Cameron", "Gordon Brown", "Winston Churchill", "Tony Blair"],
    correctAnswer:2
},
{
    question: "3. Who is Prime Minister of the United Kingdom?",
    choices: ["David Cameron", "Gordon Brown", "Winston Churchill", "Tony Blair"],
    correctAnswer:1
}];

JavaScript Code (jQuery)

$(document).ready(function(){
    var all = quizQuestions,
        q = $("#question"),
        c = $("#choices"),
        ans = [],
        current = 0;

    display(current);

    function display (id) {
        var question = all[id].question;
        var choices = all[id].choices;

        q.html(question);
        c.html(function(){
            var output = "<form>";
            for (var i = 0; i < choices.length; i++) {
                output += "<input type='radio' name='question" +id
                + "' value='" + choices[i] + "'> "
                + choices[i] + "<br>";
            };
            output += "</form>"
            // console.log(output);
            return output;
        });
    };

    function changeDisplay (dir) {
        if (dir == 'next' && current < all.length-1) current++;
        if (dir == 'prev' && current > 0) current--;
        display(current);
    }

    function answerQ (q, a) {
        ans[q] = a;
        console.log(ans);
    }

    $(":radio").change(function(){
        answerQ( $(this)[0].name, $(this).val() );
    });

    $("#previous").click(function (){ changeDisplay('prev'); });
    $("#next").click(function (){ changeDisplay('next'); });

    // console.log("all.length: " + all.length + " | ans: " + ans + " | current: " + current);
});

This is most likely because the event is not delegated. When the page is loaded you bind the click events. But as the dom is manipulated (elements removed) the events are deleted. You would have to use a delegate for this.

Like this:

$('.container').on('change', ':radio', function(){
    answerQ( $(this)[0].name, $(this).val() );
});

Edit: I would suggest that you save or hide the answers instead of overwriting them. That would make it easier to implement the previous function.

It looks like when you move to the next set of questions you replace the HTML on the page for each radio button. However you never assign click handlers to the new radio buttons. You do this initially to all radio buttons when the page is loaded, but after you change the display you don't reapply the click handler to the radio buttons.

In your document.ready handler you have this near the bottom:

    $(":radio").change(function(){
        answerQ( $(this)[0].name, $(this).val() );
    });

But in the display function (called when you choose previous or next) you have this:

        c.html(function(){
            var output = "<form>";
            for (var i = 0; i < choices.length; i++) {
                output += "<input type='radio' name='question" +id
                + "' value='" + choices[i] + "'> "
                + choices[i] + "<br>";
            };
            output += "</form>"
            // console.log(output);
            return output;
        });

Which doesn't add a click handler to any of the new input options.

Here's the problem, when you directly add listeners like ".click()" or ".change()", etc, to an element, you're only setting the listener for that object specifically. To create a listener that applies to all elements currently on the page, and also any future elements created through dynamic means (ajax, new elements added via javascript after page load completes) you should set a document level "on" function:

Change what you have here:

$(":radio").change(function(){
    answerQ( $(this)[0].name, $(this).val() );
});

to this:

$(document).on("change", ":radio", function(){
    answerQ($(this).name, $(this).val());
});

(I don't think the [0] on $(this) means anything, because at that moment it would only apply to the element that was just changed anyway).

Hope that helps!

Here's a link for futher reading on the subject: http://api.jquery.com/on/

I would move the display function out of the callback method for the $(document).ready() event. IMHO, it belongs in the global space because it's being invoked by other functions in the global space. It's just cleaner than relying on a Closure to keep a reference to an object hidden in an event handler.

EDIT: I imagine the problem is the use of this, without stepping through the code. Would things change if you use pass in the event object in your handler .change(), and use that to get a reference to the element?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM