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>