使用创建回调将嵌套对象映射为复杂 JSON 的可观察对象
Mapping a nested object as an observable from a complex JSON using the create callback
我有一个 JSON 格式的复杂对象。我正在使用 Knockout Mapping,自定义 create
回调,并尝试确保每个应该是可观察对象的对象 - 实际上都将这样映射。
以下代码是我得到的示例:
它使用户能够添加 cartItems
、保存它们(作为 JSON)、清空购物车,然后加载保存的项目。
加载部分失败:不显示加载的选项(即加载的cartItemName
)。我想这与选项列表中的对象和被限定为 cartItemName
的对象之间的某些不匹配有关(请参阅此 ),但我无法弄清楚。
代码(fiddle):
var cartItemsAsJson = "";
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) {
return self.language() == "english" ?
product.productName().english : product.productName().french;
}
}
self.getProductValue = function (selectedProduct) {
// if not caption
if (selectedProduct) {
var matched = ko.utils.arrayFirst(self.availableProducts(), function (product) {
return product.productName().english == selectedProduct.productName().english;
});
return matched;
}
}
self.getProductColours = function (selectedProduct) {
selectedProduct = selectedProduct();
if (selectedProduct) {
return selectedProduct.availableColours();
}
}
self.addCartItem = function () {
self.cartItems.push(new cartItemVM());
}
self.emptyCart = function () {
self.cartItems([]);
}
self.saveCart = function () {
cartItemsAsJson = ko.toJSON(self.cartItems);
console.log(cartItemsAsJson);
}
self.loadCart = function () {
var loadedCartItems = ko.mapping.fromJSON(cartItemsAsJson, {
create: function(options) {
return new cartItemVM(options.data);
}
});
self.cartItems(loadedCartItems());
}
}
var productVM = function (name, availableColours, data) {
var self = this;
self.productName = ko.observable({ english: name, french: name + "eux" });
self.availableColours = ko.observableArray(availableColours);
}
var cartItemVM = function (data) {
var self = this;
self.cartItemName = data ?
ko.observable(ko.mapping.fromJS(data.cartItemName)) :
ko.observable();
self.cartItemColour = data ?
ko.observable(data.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>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.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.getProductValue(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: 'empty cart', click: emptyCart" />
<button data-bind="text: 'save cart', click: saveCart" />
<button data-bind="text: 'load cart', click: loadCart" />
</div>
</div>
需要更改什么来修复它?
P.S.: 我有另一段代码(参见 here),它演示了即使在更改选项后所选值的持久性 - 尽管 optionsValue
是一个简单的字符串,而这里是一个对象。
编辑:
我发现了问题:调用 ko.mapping.fromJS(data.cartItemName)
创建了一个新的 productVM
对象,它不是 availableProducts
数组中的对象之一。结果,none 的选项对应于加载的 cartItemName
中包含的 productVM
,因此 Knockout 从而完全清除该值并通过 undefined
。
但问题仍然存在:如何解决这个问题?
在从 ViewModel -> plain object -> ViewModel
的过渡中,您失去了购物车中的产品与 handlerVM
中的产品之间的关系。
一个常见的解决方案是,在加载普通对象时,手动搜索现有的视图模型并引用它们。即:
- 我们从普通对象
创建一个新的cartItemVM
- 在其
cartItemName
中,有一个 handlerVM
中不存在的对象。
- 我们在
handlerVM
中查找与此对象相似 的产品,并用我们找到的对象替换该对象。
在代码中,在 loadCart
内,在设置新视图模型之前:
loadedCartItems().forEach(
ci => {
// Find out which product we have:
const newProduct = ci.cartItemName().productName;
const linkedProduct = self.availableProducts()
.find(p => p.productName().english === newProduct.english());
// Replace the newProduct by the one that is in `handlerVM`
ci.cartItemName(linkedProduct)
}
)
Fiddle: https://jsfiddle.net/7z6010jz/
如您所见,相等比较有点难看。我们查找 english
产品名称并使用它来确定匹配项。您还可以看到可观察和不可观察的差异。
我的建议是为您的产品使用独特的 id
属性,并开始使用这些属性。您可以创建一个更简单的 optionsValue
绑定并自动匹配新旧值。如果您愿意,我也可以向您展示此重构的示例。让我知道是否有帮助。
我有一个 JSON 格式的复杂对象。我正在使用 Knockout Mapping,自定义 create
回调,并尝试确保每个应该是可观察对象的对象 - 实际上都将这样映射。
以下代码是我得到的示例:
它使用户能够添加 cartItems
、保存它们(作为 JSON)、清空购物车,然后加载保存的项目。
加载部分失败:不显示加载的选项(即加载的cartItemName
)。我想这与选项列表中的对象和被限定为 cartItemName
的对象之间的某些不匹配有关(请参阅此
代码(fiddle):
var cartItemsAsJson = "";
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) {
return self.language() == "english" ?
product.productName().english : product.productName().french;
}
}
self.getProductValue = function (selectedProduct) {
// if not caption
if (selectedProduct) {
var matched = ko.utils.arrayFirst(self.availableProducts(), function (product) {
return product.productName().english == selectedProduct.productName().english;
});
return matched;
}
}
self.getProductColours = function (selectedProduct) {
selectedProduct = selectedProduct();
if (selectedProduct) {
return selectedProduct.availableColours();
}
}
self.addCartItem = function () {
self.cartItems.push(new cartItemVM());
}
self.emptyCart = function () {
self.cartItems([]);
}
self.saveCart = function () {
cartItemsAsJson = ko.toJSON(self.cartItems);
console.log(cartItemsAsJson);
}
self.loadCart = function () {
var loadedCartItems = ko.mapping.fromJSON(cartItemsAsJson, {
create: function(options) {
return new cartItemVM(options.data);
}
});
self.cartItems(loadedCartItems());
}
}
var productVM = function (name, availableColours, data) {
var self = this;
self.productName = ko.observable({ english: name, french: name + "eux" });
self.availableColours = ko.observableArray(availableColours);
}
var cartItemVM = function (data) {
var self = this;
self.cartItemName = data ?
ko.observable(ko.mapping.fromJS(data.cartItemName)) :
ko.observable();
self.cartItemColour = data ?
ko.observable(data.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>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.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.getProductValue(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: 'empty cart', click: emptyCart" />
<button data-bind="text: 'save cart', click: saveCart" />
<button data-bind="text: 'load cart', click: loadCart" />
</div>
</div>
需要更改什么来修复它?
P.S.: 我有另一段代码(参见 here),它演示了即使在更改选项后所选值的持久性 - 尽管 optionsValue
是一个简单的字符串,而这里是一个对象。
编辑:
我发现了问题:调用 ko.mapping.fromJS(data.cartItemName)
创建了一个新的 productVM
对象,它不是 availableProducts
数组中的对象之一。结果,none 的选项对应于加载的 cartItemName
中包含的 productVM
,因此 Knockout 从而完全清除该值并通过 undefined
。
但问题仍然存在:如何解决这个问题?
在从 ViewModel -> plain object -> ViewModel
的过渡中,您失去了购物车中的产品与 handlerVM
中的产品之间的关系。
一个常见的解决方案是,在加载普通对象时,手动搜索现有的视图模型并引用它们。即:
- 我们从普通对象 创建一个新的
- 在其
cartItemName
中,有一个handlerVM
中不存在的对象。 - 我们在
handlerVM
中查找与此对象相似 的产品,并用我们找到的对象替换该对象。
cartItemVM
在代码中,在 loadCart
内,在设置新视图模型之前:
loadedCartItems().forEach(
ci => {
// Find out which product we have:
const newProduct = ci.cartItemName().productName;
const linkedProduct = self.availableProducts()
.find(p => p.productName().english === newProduct.english());
// Replace the newProduct by the one that is in `handlerVM`
ci.cartItemName(linkedProduct)
}
)
Fiddle: https://jsfiddle.net/7z6010jz/
如您所见,相等比较有点难看。我们查找 english
产品名称并使用它来确定匹配项。您还可以看到可观察和不可观察的差异。
我的建议是为您的产品使用独特的 id
属性,并开始使用这些属性。您可以创建一个更简单的 optionsValue
绑定并自动匹配新旧值。如果您愿意,我也可以向您展示此重构的示例。让我知道是否有帮助。