knockout.jsko-custom-binding

Knockout: how to update another binding from custom binding?


We have usual problem with optionsCaption binding: it is always showed even when there is only one element. We solved this problem using our custom binding:

ko.bindingHandlers.optionsAutoSelect = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        var allBindings = allBindingsAccessor();

        if (value.length == 1) {
            allBindings.optionsCaption = null;
        }
        ko.bindingHandlers.options.update(element, valueAccessor, allBindingsAccessor);
    }
};

after updating to knockout 3.0 allBindings become readonly. So any changes are really skipped. Any ideas how it could be solved in ko 3.0? We really have a lot of such auto selects and don't want to copy paste some computed code on all views. So we want some single option/extensibility point. Unfortunately as I could see options binding is rather monolithic.


Solution

  • Probably some different approaches that you could take here. Here is one thought (feel free to use a better name than optionsPlus):

    ko.bindingHandlers.optionsPlus = {
        preprocess: function(value, name, addBindingCallback) {
            //add optionsCaption to the bindings against a "caption" sub-observable
            addBindingCallback("optionsCaption", value + ".caption"); 
    
            //just return the original value to allow this binding to remain as-is
            return value;
        },
        init: function(element, valueAccessor) {
            var options = valueAccessor();
    
            //create an observable to track the caption
            if (options && !ko.isObservable(options.caption)) {
                options.caption = ko.observable();
            }
    
            //call the real options binding, return to control descendant bindings
            return ko.bindingHandlers.options.init.apply(this, arguments);
        },
        update: function(element, valueAccessor, allBindings) {
            var options = valueAccessor(),
                value = ko.unwrap(options);
    
            //set the caption observable based on the length of data
            options.caption(value.length === 1 ? null : allBindings.get("defaultCaption"));
    
            //call the original options update
            ko.bindingHandlers.options.update.apply(this, arguments);        
        }
    
    };
    

    You would use it like:

    <select data-bind="optionsPlus: choices, defaultCaption: 'choose one...'"></select>
    

    It creates a caption observable off of your observableArray/array and updates the caption initially and whenever the options are updated (if using an observableArray).

    Sample here: http://jsfiddle.net/rniemeyer/jZ2FC/