Question

Please see this jsfiddle

I am encountering two issues:

  1. I am unable to return the selected object's (cascadingOption) text.
  2. I am lost with how to faithfully translate the dropdowns to select2.

Specifically, this:

    <select id="make" data-bind="options: carMakers, 
                                 value: selectedMake, 
                                 optionsText : 'text', 
                                 optionsCaption : 'Select your make'">
    </select><br/>
    Selected Make: <span data-bind="text: selectedMake"></span><br/>

returns [object Object] to the screen. If I alter the last line to span data-bind="text: selectedMake.text" it returns nothing. However, if I use subscribe from knockout and log to the console, I can return the object.text fine?

The second issue is when I alter the first select tag by adding select2: { } to its data-bind attribute. This will properly change the dropdown to the select2 style, but all the cascading properties fall apart.

Any help or guidance would be greatly appreciated.

Was it helpful?

Solution

selectedMake is an observable, its value will be cascadingOption instance. To use this in your span:text binding you will need :

<span data-bind="text: selectedMake().text"></span>

It works when you use subscribe because you get the value of the observable.


Select2 tries to synchronise the select element with it's internal state. Unfortunately it relies on each option having an Id value. Because you don't use the optionsValue binding this will not work. I also use select2 but I have changed their code to use optionIndex instead of Id and a fairly complicated select2 binding to manage the differences, like Ajax, and multiselect.

However, you get your sample working with select2...

  1. Create an observable to store the Id.
  2. Change your value binding to use Id observable
  3. Add an optionsValue binding to return a unique Id from your source option. This will be used to synchronise the select2 option and the select option.
  4. Add a subscription to the Id observable to filter the options and return the object from your options array
  5. Add a subscription to the value observable to keep the Id in sync ( be careful about infinite loop between the 2 subscriptions )

I have updated your JSFiddle but differences are below. In this I create a wrapper function to build an observable/id pair with subscriptions between the 2. The select binding has a different value binding value, and an added optionValue binding The span binding has a different text binding value.

HTML

<div>
    <select id="make" data-bind="options: carMakers, value: selectedMake.id, optionsValue: 'text', optionsText : 'text', optionsCaption : 'Select your make', select2: {}"></select><br/>
    Selected Make: <span data-bind="text: selectedMake().text"></span><br/>
    <select id="type" data-bind="options: carTypes, value: selectedType.id, optionsValue: 'text', optionsText : 'text', optionsCaption : 'Select your type', enable : carTypes, select2: {}"></select><br/>
    Selected Model: <span data-bind="text: selectedType().text"></span><br/>
    <select id="model" data-bind="options: carModels, value: selectedModel.id, optionsValue: 'text', optionsText : 'text', optionsCaption : 'Select your Model', enable: carModels, select2: {}"></select><br/>
    Selected Model: <span data-bind="text: selectedModel().text"></span><br/>
</div>

Javascript

var makeObservableForSelect2 = function( sourceOptions, idSelector ) {
    var target = ko.observable({});
    target.id = ko.observable();

    target.id.subscribe( function(id) {
        var realSource = ko.unwrap(sourceOptions)
        if ( !realSource ) {
            return;
        };
        // Don't set target if id already matches to stop infinite loop.
        if ( target() && target()[idSelector] === id ) {
            return;
        }

        target( realSource.filter( function(item) { return item[idSelector] === id; } )[0] || {} );
    } );

    target.subscribe( function(value) {
        // Don't set id if id already matches to stop infinite loop.
        if ( target.id() && value[idSelector] === target.id() ) {
            return;
        }

        target.id(value[idSelector]);
    });

    return target;
};

var viewModel = {
    carMakers: buildData()
};

viewModel.selectedMake = makeObservableForSelect2( viewModel.carMakers, 'text');

viewModel.carTypes = ko.computed(function(){
    return viewModel.selectedMake() ? viewModel.selectedMake().childOptions : null;
});

viewModel.selectedType = makeObservableForSelect2( viewModel.carTypes, 'text');

viewModel.carModels = ko.computed(function(){
    return viewModel.selectedType() ? viewModel.selectedType().childOptions : null;
});

viewModel.selectedModel = makeObservableForSelect2( viewModel.carModels, 'text');

OTHER TIPS

Working fiddle: http://jsfiddle.net/jiggle/Lw2qJ/

Issue 1:

You could do span data-bind="text: selectedMake().text" (note the brackets), but only if selectedMake was always going to have a value (and therefore have the .text property).

There are a few other ways to do this, which are outlined on http://www.knockmeout.net/2011/08/simplifying-and-cleaning-up-views-in.html

Issue 2:

However, when I started looking at Issue 2, I found that the optionsValue property must be set for the select2 to work correctly (though someone could correct me on this), so I refactored a little bit so your selectedMake is no longer the object, but rather the text property and the optionsValue:"text" like so:

<select id="make" data-bind="select2:{} , options: carMakers, value:    selectedMake,   
optionsValue:'text', 
optionsText : 'text', optionsCaption : 'Select your make'"></select><br/>

This meant for your cascade computeds for the next levels, you have to change it to first look up the make from the selected text value of the make like this:

viewModel.carTypes = ko.computed(function(){
if(viewModel.selectedMake()){
    var make = ko.utils.arrayFirst(viewModel.carMakers,function(item){
        console.log(item.text,viewModel.selectedMake());
            return item.text===viewModel.selectedMake();          
    });
    return make.childOptions;
} 
});

This also means that you can just use Selected Make: <span data-bind="text: selectedMake"></span> rather than using the brackets as you're not trying to access a property of an observable.

Here's the fiddle again: http://jsfiddle.net/jiggle/Lw2qJ/

Hope it helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top