Why does updating the html content break my event handler functions? (jQuery)

I am attempting to reverse engineer the AngularJS tutorial example of a “Todo List” using only jQuery/native JS. I am doing this simply as a learning experience, as it is clear that AngularJS is a superior method for building this kind of an app.

Here is my JSFiddle

Not all functionality is in place yet (e.g. archiving finished tasks) but most of what I have put in place works as it should. The issue at hand involves the event handlers for clicking on the .unchecked and .checked checkboxes. They work as intended when the document loads the first time, but after the updateToDo() function is called, those event handlers fail to manipulate the DOM even though it contains elements with classes that should trigger the handlers when clicked.

I’ve reviewed the code several times, making use of the JS console, ensuring no typos were to blame, and even rearranging the placement of the functions to allow for the target elements to be rendered when the functions are executed.

Some pertinent excerpts from the code:

HTML:

<div id="content">
    <div id="holder">
        <h3 id="title"><span id="currentnum"></span> of <span id="totalnum"></span> showing.</h3>
 <a href="#" id="archive">[Archive]</a>

        <br />
        <ul id="ul-list" class="ul-list">
            <!-- Here goes the list -->
        </ul>
        <br />
        <input type="text" placeholder="New Todo" id="textinput" />
        <button id="btn">Add</button>
    </div>
</div>

jQuery:

//Add new items to ToDoList
    var addTodo = function (input) {
        ToDoList.list.push({
            text: input,
            done: false
        });
        updateTodo();
    };

    // Update view to reflect model
    var updateTodo = function () {
        var listText = "";
        var listLength = getListLength();
        console.log(ToDoList.list);

        for (var i = 0; i < listLength; i++) {
            listText += "<input type='checkbox' class='unchecked' /><li id='li-list-" + i + "' class='li-list'>" + ToDoList.list[i].text + "</li><br />";
        }

        $("#ul-list").html(listText);
        updateCounter();
    };

Event Handlers:

$('input.unchecked').click(function () {
        $(this).toggleClass('checked unchecked');
        $('.checked').next('.li-list').css({
            'text-decoration': 'line-through',
                'color': 'gray'
        });
        $('.unchecked').next('.li-list').css({
            'text-decoration': 'none',
                'color': '#000'
        });
        updateCounter();
    });

    $('input.checked').click(function () {
        $(this).toggleClass('checked unchecked');
        $('.checked').next('.li-list').css('text-decoration', 'line-through');
        $('.unchecked').next('.li-list').css('text-decoration', 'none');
        updateCounter();
    });

    //Button action
    $('#btn').click(function () {
        var input = $('#textinput').val();
        if (input !== "") {
            addTodo(input);
        }
    });

Answer

Updating the HTML is in reality replacing the HTML. That means removing everything that’s in the DOM, event handlers included.

Then the new HTML is added, effectively replacing the old HTML, only that the old event handlers haven’t been re-added.

A common solution to this problem is to use event delegation. That is, add an event handler in a node that’s a parent of what’s getting replaced, and listen for events that have an e.target (that is, the node that the event originated on) which satisfies the child node you want.

The difference is that new nodes will still match the check for nodes like such and such, and won’t be removed when replacing their children.

You can read some more about event delegation here: Getting Over jQuery

Leave a Reply

Your email address will not be published. Required fields are marked *