var oldValueBinding = ko.bindingHandlers['value']; ko.bindingHandlers['value'] = { 'init': function (element, valueAccessor, allBindingsAccessor) { // If `checked` binding is present, ignore this binding because // user wishes to bind the checked value to the model value var allBindings = allBindingsAccessor(), hasChecked = allBindings.hasOwnProperty("checked"); if (hasChecked) { return; } oldValueBinding['init'].apply(this, arguments); }, 'update': function (element, valueAccessor, allBindingsAccessor) { // If `checked` binding is present, ignore this binding because // user wishes to bind the checked value to the model value var allBindings = allBindingsAccessor(), hasChecked = allBindings.hasOwnProperty("checked"); if (hasChecked) { return; } oldValueBinding['update'].apply(this, arguments); } }; ko.bindingHandlers['checked'] = { 'init': function (element, valueAccessor, allBindingsAccessor) { var updateHandler = function() { var valueToWrite; if (element.type == "checkbox") { valueToWrite = element.checked; } else if ((element.type == "radio") && (element.checked)) { valueToWrite = element.value; } else { return; // "checked" binding only responds to checkboxes and selected radio buttons } var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue), allBindingsValue = allBindingsAccessor(); if ((element.type == "checkbox") && (unwrappedValue instanceof Array)) { // For checkboxes bound to an array, we add/remove the checkbox value to that array // This works for both observable and non-observable arrays var boundValue = (allBindingsValue.hasOwnProperty("value") && ko.utils.unwrapObservable(allBindingsValue.value)) || element.value; var existingEntryIndex = ko.utils.arrayIndexOf(unwrappedValue, boundValue); if (element.checked && (existingEntryIndex < 0)) modelValue.push(boundValue); else if ((!element.checked) && (existingEntryIndex >= 0)) modelValue.splice(existingEntryIndex, 1); } else { ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true); } }; ko.utils.registerEventHandler(element, "click", updateHandler); // IE 6 won't allow radio buttons to be selected unless they have a name if ((element.type == "radio") && !element.name) ko.bindingHandlers['uniqueName']['init'](element, function() { return true }); }, 'update': function (element, valueAccessor, allBindingsAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()), allBindingsValue = allBindingsAccessor(); if (element.type == "checkbox") { if (value instanceof Array) { var boundValue = (allBindingsValue.hasOwnProperty("value") && ko.utils.unwrapObservable(allBindingsValue.value)) || element.value; // When bound to an array, the checkbox being checked represents its value being present in that array element.checked = ko.utils.arrayIndexOf(value, boundValue) >= 0; } else { // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish element.checked = value; } } else if (element.type == "radio") { element.checked = (element.value == value); } } }; window.vm = { selectedItems: ko.observableArray([]), items: ko.observableArray([ { id: 1, name: "Foo" }, { id: 2, name: "Foo2" } ]) }; // // Ah. Much better! vm.isItem1Selected = ko.computed(function () { return this.selectedItems.indexOf(1) > -1; }, vm); // debug vm.model = ko.computed(function () { var js = ko.toJS(this); delete js.model; return ko.toJSON(js); }, vm); ko.applyBindings(vm);
<h1>Better Checked Binding</h1> <ul data-bind="foreach: items"> <li><input type="checkbox" data-bind="checked: $parent.selectedItems, value: id"> <input type="text" data-bind="value: name"> <span data-bind="text: name"></span></li> </ul> <strong>Item 1 selected? <span data-bind="text: isItem1Selected"></span></strong> <pre data-bind="text: model"></pre>