In the following code, a product (represented with productVM
) has an observable property (productName
) containing its name in two languages (english and french).
Once a cartItem
is added, and a product is selected, I want its displayed name to be updated when the button "change language" is clicked (e.g., if "Door" is selected, and "change language" is then clicked, the displayed name should be the french version (which is simply the english word plus a french-ish suffix "eux")).
But it doesn't work: The options do change, but the selected option is changed to the caption option.
What needs to be changed/added to fix it?
var handlerVM = function () {
var self = this;
self.cartItems = ko.observableArray([]);
self.availableProducts = ko.observableArray([]);
self.language = ko.observable();
self.init = function () {
self.initProducts();
self.language("english");
}
self.initProducts = function () {
self.availableProducts.push(
new productVM("Shelf", ['White', 'Brown']),
new productVM("Door", ['Green', 'Blue', 'Pink']),
new productVM("Window", ['Red', 'Orange'])
);
}
self.getProducts = function () {
return self.availableProducts;
}
self.getProductName = function (product) {
if (product != undefined) {
return self.language() == "english" ?
product.productName().english : product.productName().french;
}
}
self.getProductColours = function (selectedProductName) {
selectedProductName = selectedProductName();
// if not caption
if (selectedProductName) {
var matched = ko.utils.arrayFirst(self.availableProducts(), function (product) {
return (self.language() == "english" ? product.productName().english : product.productName().french) == selectedProductName;
});
return matched.availableColours;
}
}
self.addCartItem = function (a, b, c, d) {
self.cartItems.push(new cartItemVM());
}
self.changeLanguage = function () {
self.language() == "english" ?
self.language("french") :
self.language("english");
}
}
self.productVM = function (name, availableColours) {
var self = this;
self.productName = ko.observable({
english: name,
french: name + "eux",
});
self.availableColours = ko.observableArray(availableColours);
}
self.cartItemVM = function () {
var self = this;
self.cartItemName = ko.observable();
self.cartItemColour = ko.observable();
}
var handler = new handlerVM();
handler.init();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div>
<div data-bind="foreach: cartItems">
<div>
<select data-bind="options: $parent.getProducts(),
optionsText: function (item) { return $parent.getProductName(item); },
optionsValue: function (item) { return $parent.getProductName(item); },
optionsCaption: 'Choose a product',
value: cartItemName"
>
</select>
</div>
<div>
<select data-bind="options: $parent.getProductColours(cartItemName),
optionsText: $data,
optionsCaption: 'Choose a colour',
value: cartItemColour,
visible: cartItemName() != undefined"
>
</select>
</div>
</div>
<div>
<button data-bind="text: 'add cart item', click: addCartItem" />
<button data-bind="text: 'change language', click: changeLanguage" />
</div>
</div>
Your problem occurs when you change the
options
of your select. During the change, yourvalue
bound observable,cartItemName
, contains the English string. For example:Door
. As soon as you change the language, there is not a singleoption
that returnsDoor
for itsoptionsValue
expression, thereby clearing thevalue
altogether.The best solution is to store a reference to your actual viewmodel, rather than just its string name. This does require you to move some other bits & pieces around, since you're manually updating quite a bit.
The starting point of the change:
In a working snippet, with some other changes to make my work easier: