Search code examples
javascriptjquerybackbone.jsbackbone-events

backbone.js event binding is breaking radio buttons functionality


I am pretty new to Backbone.js, but I am pretty sure I am doing this right.

Here are my Backbone view and models:

// USER MODEL

var User = Backbone.Model.extend({
    defaults: {
        UserID: 0,
        FirstName: "",
        LastName: "",
        Assignment: 0,
        Selected: false
    },


    toggle: function (){
        var value = this.get("Selected");
        this.set({ Selected : !value });
    }
    
});

// USER VIEW

var UserView = Backbone.View.extend({
    parentview: null, // the parent view

    initialize: function () {
        this.parentview = this.options.parentview
        this.template = $("#user-template")

        this.model.bind("change", this.render, this);
    },

    render: function () {
        var content = this.template.tmpl(this.model.toJSON());
        $(this.el).html(content);
                   
        return this;
    },

    events: {
       "click ul li": "toggle"
    },

    toggle: function () {
        this.model.toggle();
    }

});

And here is the JQuery template that I am using for each user's view. The view is rendered inside another big view, that is the collection.

<ul  id="user-container"
    {{if Selected == 1 }} class="selected" {{/if}}
>
    <li class="col1" {{if Selected == 0}} style="visibility:hidden" {{/if}} >
        <img src="@Content.ImageUrl("pin-icon.png", "base")" />
    </li>
    <li class="col2">${FirstName} ${LastName} </li>
    <li class="col3-last">
        <input id="radio-${$item.view}-PM-${UserID}" name="radio-${$item.view}-${UserID}" type="radio" value="1" class="button"  
            {{if Assignment == 1}} checked="checked" {{/if}} 
        />
        <label for="radio-${$item.view}-PM-${UserID}">
            PM</label>
        <input id="radio-${$item.view}-SV-${UserID}" name="radio-${$item.view}-${UserID}" type="radio" value="2" class="button" 
            {{if Assignment == 2 }} checked="checked" {{/if}}
        />
        <label for="radio-${$item.view}-SV-${UserID}">
            STAFF</label>
        <input id="radio-${$item.view}-NA-${UserID}" name="radio-${$item.view}-${UserID}" type="radio" value="0" class="button" 
            {{if Assignment == 0 }} checked="checked" {{/if}}
        />
        <label for="radio-${$item.view}-NA-${UserID}">
            NONE</label>
    </li>
</ul>

In the last li I have three radio buttons, with dynamic id's and the same name (also dynamic). Each button has its own label.

All was working fine, the radio buttons were selecting and doing their job fine, until I added this line "click ul": "toggle" in the view's events.

The event works, when I click each user's row in the view, the model is updated and the user is selected / deselected.

Only thing is, that with this line, the radio buttons just don't work. The values are set, but nothing happens when I try to change the value (click on another radio button).

No error is generated in browser console, which is weird...

CORRECTION: When I click on a radio button, the click event from the backbone view is triggered, the selection is being done, but the button stays unchecked. It seems that backbone takes over, but all I want is to trigger just when I click on the ul portion, not on other elements inside, like buttons, etc.

However, when I click on the labels for the buttons, nothing happens. No backbone event is triggered, and no button is selected.

Anyone has any idea why this might happen?


Solution

  • The checkbox is not changed in the UI because whenever a checkbox is clicked, the click event propagates to the "li" element and toggle callback is called. When the toggle callback is called, you're re-rendering your whole view, and since the "Assignment" attribute of your User model isn't being changed, you're re-rendering the view with the same "checked" state as before.

    You can solve this problem by preventing the propagation of the click event when it's initialized by the radio button. For example add the following radio button click handler to your view:

            events: {
                "click input[type=radio]": "onRadioClick",
                "click ul li": "toggle"
            },
    
            onRadioClick: function (e) {
                e.stopPropagation();
                this.model.set({ Assignment: $(e.currentTarget).val() }, {silent:true}); // {silent: true} is optional. I'm only doing this to prevent an unnecessary re-rendering of the view
            },
    

    This will intercept the click event whenever the radio button is clicked and prevent your toggle callback from being called. Notice that I'm also updating the Assignment attribute on your User model so that when your view re-renders next time, your checked state will be properly set.