Knockout foreach flaky with checkbox initial,与模板一起使用

时间:2012-10-19 03:55:19

标签: knockout.js

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

使用这个简单的viewmodel:

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绑定没有?

1 个答案:

答案 0 :(得分:2)

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

我的理解是the order in which you iterate over the property names of an object is not well-defined并且不能依赖。它可能会也可能不会按您声明的顺序进行迭代。 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);
};

Updated fiddle