Reputation: 24815
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
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.
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/
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/
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
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