Knockout JS Checkbox binding not updating correctly

4.5k Views Asked by At

I have a checkbox that is bound to a property on my object used in my observableArray. I can't get the checkboxes to update correctly when clicked. The value doesn't set correctly nor does the checkbox uncheck itself when i click it. The example bindings have a property on the ViewModel directly rather then a property of an object used inside of the ViewModel. Can anyone shed light on how to get this working?

            <tbody id="StatusGrid" data-bind="foreach:{data: Statuses, as: 'status'}">
            <tr data-bind="attr: { index: $index }" style="padding-bottom:5px;">
                <td style="padding-bottom:5px;">
                    <input class="statusID" data-bind="value: status.StatusID, visible: status.ShowID, attr: { name: 'Statuses[' + $index() + '].StatusID'}" />
                </td>
                <td style="padding-bottom:5px;">
                    <input class="description" data-bind="value: status.Description, attr: { name: 'Statuses[' + $index() + '].Description'}" />
                </td>
                <td style="padding-bottom:5px;">
                    <input type="checkbox" class="active" data-bind="value: status.Active, checked: status.Active, click: $parent.updateCheckbox, attr: { name: 'Statuses[' + $index() + '].Active'}" />
                </td>
                <td style="padding-bottom:5px;">
                    <input type="button" data-bind="click: $parent.removeStatus, visible: status.IsNew" value="Remove" />
                </td>
            </tr>
        </tbody>


 //////KNOCKOUT//////
var _viewModel = new ViewModel();

function status(index) {
    this.StatusID = ko.observable('');
    this.Description = ko.observable('');
    this.Index = ko.observable(index);
    this.Active = ko.observable(true);
    this.ShowID = ko.observable(false);
    this.IsNew = ko.observable(true);
    return this;
};

function ViewModel() {
    var self = this;

    self.Statuses = ko.observableArray(convertJSONToKoObservableObject());
    //self.Statuses.push(new status(0));

    //Ko Functions
    self.addStatus = function () {
        var index = $('#StatusGrid tr:last').attr('index');
        self.Statuses.push(new status(index + 1));
    }

    self.removeStatus = function (row) {
        self.Statuses.remove(row);
    }

    self.updateCheckbox = function (row) {
        var index = row.Index();
        var checkbox = $('#StatusGrid tr').eq(index).find('[type=checkbox]');
        if ($(checkbox).is(":checked")) {
            $(checkbox).attr('checked', false);
            row.Index(false);
        }
        else {
            $(checkbox).attr('checked', true);
            row.index(true);
        }
        return true;
    }
}

//FUNCTION
function convertJSONToKoObservableObject() {
    var json = JSON.parse('@Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model.Statuses))');
    var ret = [];
    var index = 0;
    $.each(json, function (i, obj) {
        var newOBJ = new status(index);
        newOBJ.StatusID = ko.observable(obj["StatusID"]);
        newOBJ.Description = ko.observable(obj["Description"]);
        newOBJ.Active = ko.observable(obj["Active"]);
        newOBJ.Index = ko.observable(index);
        newOBJ.ShowID = ko.observable(false);
        newOBJ.IsNew = ko.observable(false);
        ret.push(newOBJ);
        index++;
    });

    return ret;
}

//BIND UP!
ko.applyBindings(_viewModel);
3

There are 3 best solutions below

0
On

After making the change suggested and then tweaking the html further for the mvc post. Below works for the check binding and the value binding for the post back :)

                        <input type="checkbox" class="active" data-bind="checked: status.Active" />
                    <input type="hidden" data-bind="value: status.Active, attr: { name: 'Statuses[' + $index() + '].Active'}">
1
On

I think may be the value binding is interfering with the checked binding

<input type="checkbox" class="active" data-bind="value: status.Active, checked: status.Active, click: $parent.updateCheckbox, attr: { name: 'Statuses[' + $index() + '].Active'}" />

Try it with the checked binding only I believe knockout keeps the observable in sync

<input type="checkbox" class="active" data-bind="checked: status.Active, click: $parent.updateCheckbox, attr: { name: 'Statuses[' + $index() + '].Active'}" />

If I understand your code correctly, I also doubt you need to check/uncheck the checkbox using jQuery, knockout should handle the checking and updating the observable automatically by using the checked binding

0
On

You can use an if binding, so:

<td style="padding-bottom:5px;">
<span data-bind="if: status.Active"> 
   <input type="checkbox" data-bind="value: status.Active" checked/>
</span>
<span data-bind="ifnot: status.Active"> 
   <input type="checkbox" data-bind="value: status.Active" />
</span>
</td>

It may not be pretty, but it works instead of using 2x 2-way binding together (namely, value and checked binding together), which does not seem to give you your typical checkbox behaviour. ...