2

This does NOT work (custom binding is called but dropdown is empty)

<select id="parentArea" class="chosen-select" data-bind="
   chosen:{},
   options: parentAreas,
   optionsCaption: 'Choose...',
   optionsText: 'Label',
   value: selectedParentArea">
</select>

But this works (dropdown is filled)

<select id="parentArea" class="chosen-select" data-bind="
   options: parentAreas,
   optionsCaption: 'Choose...',
   optionsText: 'Label',
   value: selectedParentArea">
</select>

I would like to add a custom binding to the dropdown but not sure how to do it.

Custom binding is simple

ko.bindingHandlers.chosen = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        console.log('chosen', element);
        $(element).chosen({});
    }
};

UPDATE

.chosen({});

is a method from another piece of Javascript (harvesthq.github.io/chosen).

I realised that when it is commented out, the remaining binding work. What I really need is to run "$(element).chosen ({});" AFTER all other binding finish.

UPDATE 2

When I apply 'chosen' manually after all the bindings are applied, it works well. Eg I can use a button which runs this JS

 function run() {
    $('.chosen-select').chosen({});
};    

I just need to do it automatically (a callback function?) when all bindings are complete. I do not know how to do it.

UPDATE 3

"parentAreas" is not a static array. It is loaded from a web service:

function ViewModel() {

    var self = this;

   self.init = function () {

        //load parent areas from web service
    };

    self.init(); //Running the init code
}

ko.applyBindings( new ViewModel());

I want to initialise "chosen" box custom binding when parent areas are ready.

UPDATE 4

New version (works but is non-reusable since it has hardcoded bindings)

ko.bindingHandlers.chosen = { init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {

        viewModel.parentAreas.subscribe(function(newParentAreas) {
            if (newParentAreas && newParentAreas.length > 0) {

                ko.applyBindingsToNode(element, {
                    options: viewModel.parentAreas,
                    optionsCaption: 'Choose...',
                    optionsText: 'Label',
                    value: viewModel.selectedParentArea
                });
                $(element).chosen({});
            }
        });
    }
};

// and binding is just data-bind="chosen:{}

UPDATE 5 Avoiding multiple initialisation (hacky way)

ko.bindingHandlers.parentAreaComboBox = {

    initialised: false,
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {

        viewModel.parentAreas.subscribe(function (newParentAreas) {

            if (newParentAreas && newParentAreas.length > 0) {

                if (ko.bindingHandlers.parentAreaComboBox.initialised) {
                    return;
                }
                ko.applyBindingsToNode(element, {
                    options: viewModel.parentAreas,
                    optionsCaption: 'Choose...',
                    optionsText: 'Label',
                    value: viewModel.selectedParentArea
                });
                $(element).chosen({});
                ko.bindingHandlers.parentAreaComboBox.initialised = true;
            }
        });
    }
};

UPDATE 6

I've written generic solution (see my answer below)

Maxim Eliseev
  • 3,248
  • 4
  • 29
  • 35
  • what is $(element). **chosen** ({}); – ebram khalil Oct 31 '13 at 11:32
  • 1
    Good question. ".chosen({});" is a method from another piece of Javascript (http://harvesthq.github.io/chosen/). I realised that when it is commented out, the remaining binding work. What I need is to run "$(element). chosen ({});" AFTER all other binding finish. – Maxim Eliseev Oct 31 '13 at 11:41
  • It must be possible to make this generic though – Anders Oct 31 '13 at 14:09
  • 1
    you should not apply more than once, this way is unsupported and just luck if it works. With 2.3 it should throw error actuallly saying you cant apply bindings more then once – Anders Oct 31 '13 at 14:10

4 Answers4

5

Its a antipattern that you depend on the order of bindings.

If you have a custom binding that needs other bindings to run before itself you should call those bindings from the custom binding like

ko.applyBindingsToNode(element, { options: arr, value: val });

and after that do $(element).chosen

wonea
  • 4,783
  • 17
  • 86
  • 139
Anders
  • 17,306
  • 10
  • 76
  • 144
1

Yup, just reorder your bindings (fiddle: http://jsfiddle.net/gBhbx/4/):

<select id="parentArea" class="chosen-select" data-bind="   
   options: parentAreas,
   optionsCaption: 'Choose...',
   optionsText: 'Label',
   chosen:{},
   value: selectedParentArea">
</select>
pax162
  • 4,735
  • 2
  • 22
  • 28
  • Well, it's unstyled (didn't load the chosen css), but it should work. If you click on the first textbox and click on an option it will be selected. Updated fiddle: http://jsfiddle.net/gBhbx/5/ , check the console – pax162 Oct 31 '13 at 12:05
  • Yes, version 5 (jsfiddle.net/gBhbx/5 ) works in jsFiddle. I've removed (very confusing) 'chosenSimple' select and example still works http://jsfiddle.net/LzjLH/ – Maxim Eliseev Oct 31 '13 at 12:23
  • do not do it like this, its not solid and will break easily – Anders Oct 31 '13 at 13:16
  • @pax162 - works in jsFiddle but not in my solution. I am going to investigate – Maxim Eliseev Oct 31 '13 at 13:40
1

I think that your problem is in the chosen plugin itself. When you apply .chosen on your select tag it changes its markup(it's not longer a normal html select).
So in your bindning your apply chosen custom binding first which then change the html markup and so your binding are not working properly..

To solve that problem you need to apply your custom binding last not at first .. so that ko binding are applied normally then your custom binding is applied and change your select(but now you have built your select properly)

Update

to run function after the option elements are generated you can use optionsAfterRender callback .. Check out the documentaion here

Another dirty solution is to use settimeout

ebram khalil
  • 8,252
  • 7
  • 42
  • 60
  • Thanks! It is what I wrote in the "update" section: " What I really need is to run "$(element).chosen ({});" AFTER all other binding finish." But I do not know how to do it. – Maxim Eliseev Oct 31 '13 at 12:00
  • @MaximEliseev kindly check the documentaion here:: http://knockoutjs.com/documentation/options-binding.html#note_2_postprocessing_the_generated_options – ebram khalil Oct 31 '13 at 12:03
  • Note that optionsAfterRender runs for each item in the select list. I could program it so it only runs for 1st item. I would be a slightly hacky solution. I could try. – Maxim Eliseev Oct 31 '13 at 13:41
1

The generic solution

HTML

<select id="parentArea" data-bind="comboBox:{ options: parentAreas, optionsCaption:'Choose...' , optionsText: 'Label', value: selectedParentArea }"></select>

and javascript is

   ko.bindingHandlers.comboBox = {

    init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
        var bindings = valueAccessor();
        var optionsObservableArray = bindings.options;
        optionsObservableArray.subscribe(function (newObservableArray) {

            if (newObservableArray && newObservableArray.length > 0) {

                if (element.comboBoxInitialised) {
                    return;
                }

                ko.applyBindingsToNode(element, {
                    options: bindings.options,
                    optionsCaption: bindings.optionsCaption,
                    optionsText: bindings.optionsText,
                    value: bindings.value
                });
                $(element).chosen({});
                element.comboBoxInitialised = true;
            }
        });
    }
};
Maxim Eliseev
  • 3,248
  • 4
  • 29
  • 35