javascriptcheckboxknockout.jscustom-binding

Knockout custom binding for indeterminate checkbox won't work


Let's put it short: This is my knockout custom binding for putting a checkbox in indeterminate state.

ko.bindingHandlers.nullableChecked = {
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        if (value == null) element.indeterminate = true;
        ko.bindingHandlers.checked.update(element, function () { return value; });
    }
};

If the initial value is null everything works fine and checkbox is put in indeterminate state but when I click the checkbox it doesn't seem to update the bound property's value to false/true accordingly. Am I missing something?


Solution

  • You're not calling Init.

    Simply proxy the init function for checked in your nullableChecked init function like you did in the update.

    ko.bindingHandlers.nullableChecked = {
        init: function(element, valueAccessor) {
              ko.bindingHandlers.checked.init(element, valueAccessor);
        },
        update: function (element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());
            if (value == null) element.indeterminate = true;
            ko.bindingHandlers.checked.update(element, valueAccessor);
        }
    };
    

    Without the init, it's never actually setting up a "click" binding on the checkbox to tell knockout that something has changed. If you look at the debug code (http://knockoutjs.com/downloads/knockout-2.2.1.debug.js), you'll see that init uses jQuery to set up a 'click' event on the checkbox to update the observable when the value changes.

    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);
            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 existingEntryIndex = ko.utils.arrayIndexOf(unwrappedValue, element.value);
                if (element.checked && (existingEntryIndex < 0))
                    modelValue.push(element.value);
                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 });
    },
    

    Edit: Here's a Fiddle: http://jsfiddle.net/cclose/NFfVn/