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>
Advertisement
Answer
Your problem occurs when you change the options
of your select. During the change, your value
bound observable, cartItemName
, contains the English string. For example: Door
. As soon as you change the language, there is not a single option
that returns Door
for its optionsValue
expression, thereby clearing the value
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:
// Remove self.cartItemName = ko.observable(); // Add self.cartItem = ko.observable(); // Change <select data-bind="... value: cartItem " />
In a working snippet, with some other changes to make my work easier:
var handlerVM = function () { var self = this; self.cartItems = ko.observableArray([]); self.language = ko.observable("english"); self.availableProducts = ko.observableArray([ new productVM("Shelf", ['White', 'Brown']), new productVM("Door", ['Green', 'Blue', 'Pink']), new productVM("Window", ['Red', 'Orange']) ]); self.productNameFor = function(product) { return product.productName()[self.language()]; }; 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.cartItem = ko.observable(); self.cartItemColour = ko.observable(); } ko.applyBindings(new handlerVM());
<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: $root.availableProducts, optionsText: $root.productNameFor, optionsCaption: 'Choose a product', value: cartItem" > </select> </div> <div data-bind="with: cartItem"> <select data-bind="options: availableColours, optionsCaption: 'Choose a colour', value: $parent.cartItemColour" > </select> </div> </div> <div> <button data-bind="text: 'add cart item', click: addCartItem" /> <button data-bind="text: 'change language', click: changeLanguage" /> </div> </div>