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]);
    }
};