5

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.

JotaBe
  • 38,030
  • 8
  • 98
  • 117
Yauhen.F
  • 2,382
  • 3
  • 19
  • 25

1 Answers1

6

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/

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • have tried the same technique for enable, visible and all other top level bindings (for example to disable drop down when only one option available). as far as I could see addBindingCallback doesn't wire top level binding with sub-observable of another. init of options is called after enable update its state. any ideas? – Yauhen.F Dec 17 '13 at 15:01
  • can you put something in jsFiddle? not sure I understand. – RP Niemeyer Dec 17 '13 at 16:37
  • something like this one: http://jsfiddle.net/Y2SUn/ I see some "after" property inside bindinghandlers but not sure it is documented. Generally it could be even custom binding (if it is not possible to patch original binding) that "and" original enable value and some options value calculated boolean. – Yauhen.F Dec 17 '13 at 16:42
  • looks like enable is running before the observable is created. can avoid preprocess and just apply bindings directly to the node like: http://jsfiddle.net/rniemeyer/RzYaB/ – RP Niemeyer Dec 17 '13 at 23:10