复杂对象selectedObject的选项绑定总是不在列表中

Vik*_*iaR 7 knockout-2.0 knockout.js

我经常有这样的对象:

var objectToMap = {
    Id: 123, 
    UserType:{
        Id: 456,
        Name:"Some"
    }
};
Run Code Online (Sandbox Code Playgroud)

当我需要在用户界面中修改此对象时,我想从某个列表中进行选择.例如,数组:

var list = [
    {Id:456, Name: "Some"}, 
    {Id:567, Name: "Some other name"}];
Run Code Online (Sandbox Code Playgroud)

我使用选项绑定,类似的东西:

<select data-bind="options: list, optionsText: 'Name', value: UserType, optionsCaption: 'Select...'"></select>
Run Code Online (Sandbox Code Playgroud)

问题是,淘汰赛认为来自objectToMap {Id:456,名称:"Some"}的UserType与列表中的对象{Id:456,名称:"Some"}不同.因此,自动UserType从列表中获取未定义但不需要的选项.

我通过这种方式克服了问题:我使用ko.utils.arrayFirst在列表中找到项目并替换objectToMap中的UserType.但这看起来很难看,需要额外的编码.有更好的方法吗?

Rob*_*und 9

即使我认为淘汰行为是正确的,正如我在评论中提到的,这里有几种方法可以解决您的问题(请注意下面的所有代码都删除了错误检查;例如,您应该处理没有错误检查的情况具有给定id的项目在列表中找到,下面的代码没有.

您还可以创建一个自定义bindingHandler,它确保将属性值设置为list属性中的项目,基于其他一些形式的相等性(例如它们的JSON表示相等).但是,对我来说,感觉好像这种逻辑(将属性值与list属性中的项匹配)更适合视图模型或可能是扩展器,因为这些方法比涉及bindingHandler更容易创建单元测试这个逻辑.

备选方案1 - 使用 optionsValue

使用optionsValue绑定,您可以将选择列表设置为绑定到实际ID.这在很多方面实际上都是'正确'的方式,因为objectToMap可能不应该包含整个UserType而只是包含id.您可以在computedviewModel上添加一个属性,该属性UserType根据id 获取正确的值.

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];
});
Run Code Online (Sandbox Code Playgroud)

然后,您将绑定到userTypeId属性,如下所示:

<select data-bind="options:items, value: myObject.userTypeId, optionsValue: 'id', optionsText: 'name'">
</select>
Run Code Online (Sandbox Code Playgroud)

这段代码可以在jsfiddle中测试:http://jsfiddle.net/6Sg29/

备选方案2 - 创建查找扩展程序

如果您确实不想向对象添加其他属性,则可以创建一次性查找扩展器,该查找扩展器将根据使用id查找正确项目的比较函数从列表中查找值.例如,如果对象上的项目与列表中的项目之间的name属性不同,请注意此解决方案将隐藏不匹配的数据.

这种扩展器的一个例子是:

ko.extenders.oneTimeLookup = function(target, options) {
    var comparePropertyName = options.compare;
    var item = target();
    var foundItem = options.list.filter(function(listItem){
        return item[comparePropertyName] === listItem[comparePropertyName];
    })[0];
    target(foundItem);
    return target;
};
Run Code Online (Sandbox Code Playgroud)

扩展器的使用方式如下:

self.myObject = {
    id: 9348,
    userType: ko.observable({
        id: 2,
        name: 'OriginalName'
    }).extend({ oneTimeLookup: { list: self.items, compare: 'id' } })
};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,绑定可以只是指向已扩展的observable 的普通options绑定,因为我们已经执行了查找valueuserType

<select data-bind="options:items, value: myObject.userType, optionsText: 'name'">
</select>
Run Code Online (Sandbox Code Playgroud)

可以在http://jsfiddle.net/qGFQ7/的jsfiddle中运行此代码.

备选方案3 - 在创建对象时进行手动查找.

这是您已经使用的解决方案,您可以在进行任何绑定之前进行手动查找并使用列表中的项替换该对象.这类似于备选方案2,但可重用性较低.哪一个更清楚地表明你的意图,你必须自己决定.

摘要

我建议使用第一种方法,因为原始对象可能不应该保留对完整项目的引用,因为您有一个要从中检索项目的查找列表.在第二种和第三种方法中,您只是丢弃可能隐藏潜在错误的数据.但这只是我对这个问题的一般看法,当然可能会有一些特殊情况我会考虑替代2或3.