Knockout.js 复杂对象的选项绑定selectedObject始终不在列表中
我经常有这样的对象:Knockout.js 复杂对象的选项绑定selectedObject始终不在列表中,knockout.js,knockout-2.0,Knockout.js,Knockout 2.0,我经常有这样的对象: var objectToMap = { Id: 123, UserType:{ Id: 456, Name:"Some" } }; 当我需要在用户界面中修改这个对象时,我想从一些列表中进行选择。例如,数组: var list = [ {Id:456, Name: "Some"}, {Id:567, Name: "Some other name"}]; 我使用选项绑定,类似于: <selec
var objectToMap = {
Id: 123,
UserType:{
Id: 456,
Name:"Some"
}
};
当我需要在用户界面中修改这个对象时,我想从一些列表中进行选择。例如,数组:
var list = [
{Id:456, Name: "Some"},
{Id:567, Name: "Some other name"}];
我使用选项绑定,类似于:
<select data-bind="options: list, optionsText: 'Name', value: UserType, optionsCaption: 'Select...'"></select>
问题是,knockout认为objectToMap{Id:456,Name:“Some”}中的用户类型不同于列表{Id:456,Name:“Some”}中的对象。所以,UserType会自动从列表中获得未定义但不需要的选项
我通过这种方式解决了这个问题:我使用ko.utils.arrayFirst在列表中查找项,并在objectToMap中替换UserType。但这在我看来很难看,需要额外的编码。有更好的方法吗?尽管我认为击倒行为是正确的,正如我在评论中提到的,这里有几种方法可以解决您的问题(请注意,下面的所有代码都已删除错误检查;例如,您应该处理在列表中找不到具有给定id的项目的情况,而下面的代码没有这样做) 您还可以创建一个自定义bindingHandler,该处理程序确保基于其他形式的相等(例如,它们的JSON表示相等)将属性值设置为列表属性中的项。但是,对我来说,这似乎是一种逻辑(将属性值与列表属性中的项相匹配)更适合视图模型或潜在的扩展程序,因为这些方法比使用bindingHandler逻辑更容易创建单元测试 备选方案1-使用
选项value
使用选项value
绑定,您可以将选择列表设置为绑定到实际id。这在许多方面实际上是“正确”的方式,因为对象映射可能不应该包含整个用户类型,而只包含id。您可以在viewMod上添加computed
属性el,它根据id获取正确的UserType
self.myObject = {
id: 9348,
userTypeId: ko.observable(2)
};
//Add the userType computed property to the object
self.myObject.userType = ko.computed(function(){
var id = self.myObject.userTypeId();
return self.items.filter(function(item){
return item.id === id;
})[0];
});
然后将绑定到userTypeId属性,如下所示:
<select data-bind="options:items, value: myObject.userTypeId, optionsValue: 'id', optionsText: 'name'">
</select>
使用扩展器的地方如下所示:
self.myObject = {
id: 9348,
userType: ko.observable({
id: 2,
name: 'OriginalName'
}).extend({ oneTimeLookup: { list: self.items, compare: 'id' } })
};
在这种情况下,绑定可以只是一个普通的选项
绑定,其值
指向已扩展的用户类型
可见,因为我们已经执行了查找
<select data-bind="options:items, value: myObject.userType, optionsText: 'name'">
</select>
此代码可以在以下位置在JSFIDLE中运行:
备选方案3-创建对象时手动查找。
这是您已经在使用的解决方案,在进行任何绑定之前,您需要进行手动查找,并使用列表中的项目替换对象。这与备选方案2类似,但可重用性较差。您必须自己决定哪一个方案更清楚地说明您的意图
总结
我建议使用第一种方法,因为原始对象可能不会包含对完整项的引用,因为您有一个要从中检索项的查找列表。在第二种和第三种方法中,您只是丢弃可能隐藏潜在错误的数据。但这只是我对这一问题的一般看法当然,可能有一些特殊的情况,我会考虑替代2或3。 < P>在我的情况下,我必须保持物体的清洁而不改变它。< /P>
这是一个复杂的物体,比如:
[
{
"scenario":{
"id": "1",
"name": "Scenario name"
},
"scenario_data: {}
},
{
"scenario":{
"id": "2",
"name": "Scenario name"
},
"scenario_data: {}
}
]
所以我这样做了:
HTML
在这里,Knockout实际上是做了正确的事情,因为这两个对象不是同一个对象,即使它们看起来确实很像。我猜您希望在ID上进行比较,但是如果名称不同呢?您可以做您已经在做的事情,使其成为同一个对象,或者使用optionsValue
bindingHandler对m进行比较让它根据ID选择并比较相等。如果你选择选项value
,你将绑定到userTypeId
属性。如果你需要完整的对象,你将添加一个计算的
可观察对象,该对象根据所选ID从列表中获取项目。是的,我理解在这里进行敲除是预期的事情,但我希望如此存在一些自动神奇的方法使两个实体同步(一些配置,等等)…我认为这是一个常见的问题(假设不是一个问题,而是一个任务).顺便说一句,谢谢你提供的选项Value,我差点忘了它…哇!我花了这么长时间才弄明白,以为它是淘汰版错误或我的错误,但结果却是意料之中的!谢谢你的全面回答,我将重新考虑我的视图模型,我将使用备选方案1。
[
{
"scenario":{
"id": "1",
"name": "Scenario name"
},
"scenario_data: {}
},
{
"scenario":{
"id": "2",
"name": "Scenario name"
},
"scenario_data: {}
}
]
<select data-bind="foreach:scenarios,event:{ change:Studio.selectScenario }">
<option value="" data-bind="value:$data,text:$data.scenario.name"></option>
</select>
self.selectScenario = function(obj,event){
if (event.originalEvent) {
self.currentScenario(Studio.currentSessionData().scenarios[event.originalEvent.srcElement.selectedIndex]);
}
};