2

已经发布了很多关于在 Knockout 的 foreach 循环中使用复选框的问题,但我还没有看到为什么初始化检查绑定在某些情况下有效而不是其他情况的答案。

使用这个简单的视图模型:

viewModel = function (obj) {
    this.AllPossibleThings = ko.observableArray(
    [
        { ID: "1", Value: 'Thing1' }, 
        { ID: "2", Value: 'Thing2' }, 
        { ID: "3", Value: 'Thing3' }, 
        { ID: "4", Value: 'Thing4' }
    ]);
    this.selectedThings = ko.observableArray(["2","3"]);
};
$(ko.applyBindings(new viewModel()));

将这些放在 foreach 模板中可以按您的预期工作。它显示了 4 个复选框,其中 2 和 3 已预先选中:

<table>
    <tbody data-bind="template: { name: 'ThingTmpl', foreach: AllPossibleThings }">
    </tbody>
</table>
<script id="ThingTmpl" type="text/html">
        <tr>
            <td><input type="checkbox" 
                      data-bind="attr: {value: ID}, checked: $root.selectedThings" /></td>
            <td><span data-bind="text: Value"></span></td>
        </tr>
</script>

在没有模板的情况下做应该做的事情是不一样的:

<table>
    <tbody data-bind="foreach: AllPossibleThings">
        <tr>
            <td><input type="checkbox" 
                      data-bind="checked: $root.selectedThings, 
                                 attr: {value: ID}" /></td>
            <td><span data-bind="text: Value"></span></td>
        </tr>
    </tbody>
</table>

非模板显示 4 个复选框,但没有预先选中。但是,当您单击第一个复选框时,然后选中 2 和 3。就像它在创建“selectedThings”数组之前被绑定并且从未观察到更改。

小提琴演示两者:http: //jsfiddle.net/jturnage/j763w/

任何人都知道为什么模板 foreach 在这里工作,但常规的 foreach 绑定却没有?

4

1 回答 1

2

问题在于绑定和浏览器的顺序。

据我了解,您迭代对象的属性名称的顺序没有明确定义,不能依赖。它可能会或可能不会按照您声明它们的顺序进行迭代。Knockout 在内部将此绑定转换为对象并迭代对象的属性。在这种情况下,它恰好是它被声明的顺序。

您对模板中复选框的绑定如下:

data-bind="attr: {value: ID}, checked: $root.selectedThings"

虽然不在您的模板中的绑定是这样的:

data-bind="checked: $root.selectedThings, attr: {value: ID}"

绑定对您不起作用的原因是attr尚未应用绑定。所以复选框没有将它们的值设置为相应的ID. 当checked绑定更新时,它正在搜索selectedThings不存在的值。如果您更改绑定的顺序,您会看到它会起作用,因此attr首先应用。


处理此 IMO 的更好和更安全的方法是测试自己是否应该检查它,而不是依赖于这种不稳定的行为。添加一个测试是否选择了特定项目的函数。那么这些绑定被触发的顺序就无关紧要了。

viewModel = function (obj) {
    // ...

    this.isSelected = function (thing) {
        return this.selectedThings.indexOf(thing.ID) !== -1;
    };
};
<td><input type="checkbox" 
           data-bind="checked: $root.isSelected($data), attr: {value: ID}" />
</td>

当然,如果要保持selectedThings同步,则必须进行调整。你可以通过巧妙地使用计算的 observable 来实现这一点。

this.isSelected = function (thing) {
    return ko.computed({
        read: function () {
            return this.selectedThings.indexOf(thing.ID) !== -1;
        },
        write: function (newValue) {
            var index = this.selectedThings.indexOf(thing.ID);
            if (newValue) {
                // checked
                if (index === -1)
                    this.selectedThings.push(thing.ID);
            } else {
                // unchecked
                if (index !== -1)
                    this.selectedThings.remove(thing.ID);
            }
        }
    }, this);
};

更新的小提琴

于 2012-10-19T05:28:23.650 回答