Rene Pot
Rene Pot

Reputation: 24815

SELECT with 2 optgroups build with KnockOut resets the value to empty

I have this HTML

<select data-bind="value: $data.id">
    <optgroup label="Regions" data-bind="foreach: countries.regions.names">
        <option data-bind="html: $data.name, value: $data.value"></option>
    </optgroup>

    <optgroup label="Countries" data-bind="foreach: shipping.allCountries">
        <option data-bind="html: $data.name, value: $data.code"></option>
    </optgroup>

</select>

With this javascript which is being prefilled with an ID in either of the 2 optgroups. The ID is always present!

this.id = ko.observable(data.id);

But after the select is built by KnockOut, the value of this.id() is empty, and will return undefined. When I subscribe to the field, it gives me a notification it is being updated with nothing.

My current (dirty) fix:

setTimeout(function(){
    self.id(data.id);
}, 500);

This works, but of course is not recommended.

Also, after selecting another element in the select and returning to the original value, it is fine too.

How do I fix this problem?

note: the correct element is selected.

Upvotes: 2

Views: 348

Answers (2)

Michael Best
Michael Best

Reputation: 16688

The reason this fails is because the value of the <select> element is bound before its contents are bound using foreach and that the value binding will reset the model value to whatever is currently selected in the element if the model value can't itself be selected.

Example of problem: http://jsfiddle.net/mbest/8r6KY/

There are two solutions to this.

1. Prevent model value from being reset

One solution is to prevent the value binding from resetting the model value using a new binding option available in Knockout 3.1 (currently in Beta), valueAllowUnset.

<select data-bind="value: $data.id, valueAllowUnset: true">

Example: http://jsfiddle.net/mbest/D6dR2/

2. Binding contents before value

The second solution is to get the <option> elements in place before the value is bound. This can be done with a custom binding on the element that binds the contents early.

<select data-bind="bindInner, value: $data.id">

Binding:

ko.bindingHandlers.bindInner = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        ko.applyBindingsToDescendants(bindingContext, element);
        return { controlsDescendantBindings: true };
    }
};

Example: http://jsfiddle.net/mbest/ETFsa/

Recommendation

The first option uses a built-in solution, but does have a downside that the UI selection happens using setTimeout (within the value binding). Thus you could have a visible change in the selection as the view is initialized. The second option is, I think, the better one, although you could, of course, use both of these together to doubly fix the problem.

Upvotes: 3

Matthew
Matthew

Reputation: 411

Make sure that your countries.regions.names is actually pointing to the correct place. If those options don't exist then the values won't exist.

Upvotes: 0

Related Questions