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まだ適用されていないためです。したがって、チェックボックスの値は対応するに設定されていませんIDcheckedバインディングが更新されると、存在しない値が検索さ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同期を維持したい場合は、調整を行う必要があります。計算されたオブザーバブルを巧妙に使用することで、それを得ることができます。

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 に答える