"foreach" バインディング
用途
foreach
バインディングは配列の各要素のためのマークアップセクションを繰り返し生成し、
それぞれのマークアップのコピーに対し、対応する配列要素をバインドします。
主にリストやテーブルをレンダリングする際に使用します。
配列が Observable Array である場合、
要素を追加・削除・並べ替えすると、このバインディングは効率良く UI を更新します。
具体的には、他の DOM エレメントに影響を与えることなくマークアップのコピーを挿入・削除、
または既存の DOM エレメントを並べ替えるようになっています。
この仕組みは、配列が変更されたときに foreach
の出力を全てやり直すよりはるかに高速です。
もちろん、foreach
バインディングをいくつでも入れ子にすることができますし、
if
や
with
のような別のフロー制御バインディングと同時に使うこともできます。
例1: 配列を反復処理する
配列の各要素をリードオンリーなテーブルの各行にするために foreach
を使用する例です。
< table > < thead > < tr >< th >First name</ th >< th >Last name</ th ></ tr > </ thead > < tbody data-bind = "foreach: people" > < tr > < td data-bind = "text: firstName" ></ td > < td data-bind = "text: lastName" ></ td > </ tr > </ tbody > </ table > < script type = "text/javascript" > ko.applyBindings({ people: [ { firstName: 'Bert', lastName: 'Bertington' }, { firstName: 'Charles', lastName: 'Charlesforth' }, { firstName: 'Denise', lastName: 'Dentiste' } ] }); </ script > |
例2: 追加・削除のデモ
Observable Array を使うことで変更が同期される例です。
ソースコード: View
< h4 >名簿</ h4 > < ul data-bind = "foreach: people" > < li > index: < span data-bind = "text: $index" > </ span > 名前: < span data-bind = "text: name" > </ span > < a href = "#" data-bind = "click: $parent.removePerson" >削除</ a > </ li > </ ul > < button data-bind = "click: addPerson" >追加</ button > |
ソースコード: ViewModel
funtion AppViewModel() { var self = this ; self.people = ko.observableArray([ { name: 'Bert' }, { name: 'Charles' }, { name: 'Denise' } ]); self.addPerson = function () { self.people.push({ name: new Date() + " に追加" }); }; self.removePerson = function () { self.people.remove( this ); }; } ko.applyBindings( new AppViewModel()); |
パラメタ
-
主パラメタ
反復処理させたい配列を渡して下さい。 バインディングは各要素のためのマークアップセクションを出力します。
別の方法として、
data
プロパティに反復処理させたい配列を指定した JavaScript オブジェクトリテラルを渡すことができます。 オブジェクトリテラルにはafterAdd
やincludeDestroyed
というオプションを含めることができます。詳細は後述の例を参照して下さい。配列が Observable Array である場合、
foreach
バインディングは要素が変更される度に DOM 内のマークアップセクションを追加・削除することにより更新します。 -
追加パラメタ
なし
(注1) $data で配列の各要素を参照する
上記の例で示したように、foreach
ブロック内部のバインディングでは配列要素のプロパティを使うことができます。
例えば、例1 では配列の各要素にある
firstName
と lastName
の2つのプロパティを参照しています。
しかし、配列の要素のプロパティではなく、要素自体を参照したい場合はどうでしょう。
その場合、特別なコンテキスト変数 である $data
を使います。
foreach
ブロック内部において、$data
は「現在のアイテム」を意味します。
< ul data-bind = "foreach: months" > < li > 現在のアイテム: < b data-bind = "text: $data" ></ b > </ li > </ ul > < script type = "text/javascript" > ko.applyBindings({ months: [ 'Jan', 'Feb', 'Mar', 'etc' ] }); </ script > |
望むならば、各要素のプロパティを参照する際に $data
を使用することもできます。
例えば、例1 の一部を次のように書き換えることができます。
< td data-bind = "text: $data.firstName" ></ td > |
でもそれはマストではありません。なぜなら、デフォルトで常に $data
のコンテキスト内で評価されるからです。
(注2) $index, $parent, およびその他のコンテキスト変数
上記の 例2 に見えるように、
$index
で現在の配列要素のゼロベース・インデックスを参照することができます。
$index
は Observable であり、アイテムのインデックスが変更されると更新されます。
(つまり、配列に要素が追加・削除されたとき $index がバインドされている部分が更新されます。)
同様に、$parent
で foreach
の外側のデータにアクセスできます。
< h1 data-bind = "text: blogPostTitle" ></ h1 > < ul data-bind = "foreach: likes" > < li > < b data-bind = "text: name" ></ b > は < b data-bind = "text: $parent.blogPostTitle" > が好きです。 </ b > </ li > </ ul > |
$index
や $parent
およびその他のコンテキストプロパティについての詳細は
バインディングコンテキストプロパティ を参照してください。
(注3) “as”を使って foreach アイテムに別名をつける
注1で述べたように、配列の各要素にアクセスするのには $data
コンテキスト変数 を使います。
この変数に対して、次のように as
オプションを使って別名をつけたほうがやりやすい場面があります。
< ul data-bind = "foreach: { data: people, as: 'person' }" ></ ul > |
この foreach
ループ内部のどこでも、person
を使って配列の現在の要素にアクセスできます。
特に便利な場面は、入れ子になった foreach
ブロックで、下層のブロックから上層のブロックのアイテムにアクセスしたい場合です。
< ul data-bind = "foreach: { data: categories, as: 'category' }" > < li > < ul data-bind = "foreach: { data: items, as: 'item' }" > < li > < span data-bind = "text: category.name" ></ span >: < span data-bind = "text: item" ></ span > </ li > </ ul > </ li > </ ul > < script > var viewModel = { categories: ko.observableArray([ { name: 'Fruit', items: [ 'Apple', 'Orange', 'Banana' ] }, { name: 'Vegetables', items: [ 'Celery', 'Corn', 'Spinach' ] } ]) }; ko.applyBindings(viewModel); </ script > |
メモ:as
に渡す値は文字列リテラルですので、シングルクォートで囲むことを忘れないで下さい。
( × : as: category
○ : as : 'category'
)
なぜなら、この値は新しい変数の名前であって、既に存在する変数の値を読み込むのではないからです。
(注4) コンテナエレメントなしで "foreach" を使う
「繰り返し部分に foreach
を使いたいけど、foreach
を使うためにエレメントを増やしたくない。」
ということがあります。次の例を見て下さい。
< ul > < li class = "header" >ヘッダアイテム</ li > <!-- 以降を配列から動的に生成 --> < li >アイテム A</ li > < li >アイテム B</ li > < li >アイテム C</ li > </ ul > |
この例では、通常の foreach
バインディングを設置する場所がありません。
<ul>
タグに設置するにしても、ヘッダアイテムが配置できません。
また、<lt>
タグは <ul>
タグ直下にしか配置できないため、
新たなエレメントを <ul>
タグ配下に配置するわけにもいきません。
これはコメントタグによるコンテナレス構文を使うことで解決できます。
< ul > < li class = "header" >ヘッダアイテム</ li > <!-- ko foreach: myItems --> < li >アイテム < span data-bind = "text: $data" ></ span ></ li > <!-- /ko --> </ ul > < script type = "text/javascript" > ko.applyBindings({ myItems: [ 'A', 'B', 'C' ] }); </ script > |
このコメント <!--ko-->
と <!--/ko-->
は、
内部にマークアップを含む“バーチャルエレメント”の 開始 / 終了 のマーカーとしての役割をもっています。
Knockout はこのバーチャルエレメント構文を理解し、本当のコンテナエレメントがあるかのようにバインドします。
(注5) 配列の変更が検出・処理される仕組み
配列の要素が追加・削除・移動されたとき、foreach
バインディングは何がどう変わったのかを把握するために
効率的な差分アルゴリズムを使用し、一致させるために DOM を更新します。
これにより、同時に複数の変更があっても処理することができます。
-
配列に要素を追加すると、
foreach
はテンプレートの新たなコピーを生成し DOM に挿入します。 -
配列の要素を削除すると、
foreach
は該当する DOM エレメントを単純に削除します。 -
配列の要素を (同じインスタンスを保ったまま) 並べ替えると、
foreach
は該当する DOM エレメントを単に新たな位置へ移動します。
DOM エレメントを非破壊的に並べ替えることが保証されないことに注意してください。 アルゴリズムを迅速に完了するために、少ない配列要素の単純な変動を検出するために最適化されています。 アルゴリズムは無関係な挿入と削除を含む、同時多発的な並べ替えを検出した場合、 スピードを保つために、「移動」の代わりに「削除」と「追加」による並べ替えを選択します。 その場合、対応する一連の DOM エレメントが削除され、再生成されます。 ほとんどの開発者は、この特殊なケースに遭遇することはまずありませんし、 もし遭遇したとしてもユーザエクスペリエンスに影響はありません。
(注6) “destroy”された要素はデフォルトで非表示になります
時折、レコードの存在を失うこと無く、エントリーを削除済みとしてマークしたいという要件があります。 論理削除と呼ばれる方法ですが、この方法についての詳細は observableArray の destroy と destroyAll を参照して下さい。
デフォルトでは、foreach
バインディングは削除済みとしてマークされた要素をスキップ (=隠蔽) します。
削除済みの要素を表示させたい場合は、次の例のように includeDestroyed
オプションを使用して下さい。
< div data-bind = 'foreach: { data: myArray, includeDestroyed: true }' > ... </ div > |
(注7) 生成された DOM エレメントを後処理またはアニメーションさせる方法
生成された DOM エレメントに対して何らかのカスタムロジックを実行させる必要がある場合、
コールバック afterRender
/afterAdd
/beforeRemove
/beforeMove
/afterMove
を使用します。
ご注意: これらのコールバックは、リスト内の変更に関連したアニメーションを引き起こすための機能として意図されています。 もしも新たな DOM エレメントが追加されたときの振る舞いを設けることが目的なのであれば (イベントハンドラやサードパーティの UI 制御を起動するなど)、 代わりにその新たな振る舞いを カスタムバインディング として実装するほうが遥かに簡単です。 なぜなら、カスタムバインディングにすることで、どこでも
foreach
バインディングに依存せずに使いまわすことができるからです。
次に示すのは、afterAdd
を使って、よくある“Yellow Fade”
(訳注: ページの一部が更新されたときに背景色が黄色くなって、だんだん薄くなっていくアレ。
from jQuery で "Yellow Fade" / ironsJPのブログ) を実装する例です。
背景色をアニメーションさせるために、jQuery Color plugin が必要です。
<ul data-bind= "foreach: { data: myItems, afterAdd: yellowFadeIn }" > <li data-bind= "text: $data" ></li> </ul> <button data-bind= "click: addItem" >Add</button> <script type= "text/javascript" > ko.applyBindings({ myItems: ko.observableArray([ 'A' , 'B' , 'C' ]), yellowFadeIn: function (element, index, data) { $(element).filter( "li" ) .animate({ backgroundColor: 'yellow' }, 200) .animate({ backgroundColor: 'white' }, 800); }, addItem: function () { this .myItems.push( '新しいアイテム' ); } }); </script> |
詳細:
-
afterRender
— 最初の初期化の時、および後に新たなアイテムが追加された時の両方において、foreach
の繰り返し項目がドキュメントが挿入される度に呼び出されます。
指定されたコールバックに、次のパラメータを提供します。- 挿入された DOM ノードの配列
- 項目に対してバインドされたアイテム
-
afterAdd
— 配列にアイテムが追加されたときのみ呼び出される点を除いて、afterRender
と同様です。 (配列の初期値に対してのforeach
の最初の反復処理では呼び出されません。)
afterAdd
の一般的な用途は、アイテムが追加されるたびにアニメーションさせるように、 jQuery の$(domNode).fadeIn()
のようなメソッドを呼び出すことです。
指定されたコールバックに、次のパラメータを提供します。- 追加された DOM ノード
- 追加されたアイテムの、配列上のインデックス
- 追加されたアイテム
-
beforeRemove
— 配列からアイテムが削除されたとき、該当する DOM ノードが削除される前に呼び出されます。beforeRemove
コールバックを指定することで、 DOM ノードの削除を開発者が行うことができるようになります。 わかりやすい用途は、該当する DOM ノードを jQuery の$(domNode).fadeOut()
のようなアニメーション効果を用いて削除する方法です。 - これを実装する場合、Knockout は“いつ対象の DOM ノードを削除して良いのか”を知ることができません。 (アニメーションにかかる時間は場合によりけりです) したがって、beforeRemove
コールバックを指定した場合は必ず、 開発者自身が DOM ノードを削除するようにコーディングする必要があります。
指定されたコールバックに、次のパラメータを提供します。- 削除されるべき DOM ノード
- 削除されたアイテムの、配列上のインデックス
- 削除されたアイテム
-
beforeMove
— 配列内のアイテムの位置が変更されたとき、該当する DOM ノードが移動される前に呼び出されます。beforeMove
は配列内の、インデックスが変化したすべてのアイテムに対して適用されることに注意してください。 たとえば配列の先頭にアイテムを挿入した場合、他のすべてのアイテムのインデックスは、それぞれ1ずつインクリメントされます。 そのため、挿入したアイテム以外のすべてのアイテムのためにコールバックが呼び出されます。 エレメントの移動をアニメーションで表現させたい場合に、beforeMove
で移動前の座標を取得しておいて、afterMove
で移動されたエレメントのアニメーションを開始するという使い方ができます。
指定されたコールバックに、次のパラメータを提供します。- 移動される可能性のある DOM ノード
- 移動されたアイテムの、配列上のインデックス
- 移動されたアイテム
-
afterMove
— 配列内のアイテムの位置が変更されたとき、該当する DOM ノードが移動された後に呼び出されます。afterMove
は配列内の、インデックスが変化したすべてのアイテムに対して適用されることに注意してください。 たとえば配列の先頭にアイテムを挿入した場合、他のすべてのアイテムのインデックスは、それぞれ1ずつインクリメントされます。 そのため、挿入したアイテム以外のすべてのアイテムのためにコールバックが呼び出されます。
指定されたコールバックに、次のパラメータを提供します。- 移動された可能性のある DOM ノード
- 移動されたアイテムの、配列上のインデックス
- 移動されたアイテム
afterAdd
および boforeRemove
の例は、
animated transitions
をご覧ください。
※訳者注 - 誤解にご注意
実際には、
afterAdd
,beforeRemove
,beforeMove
,afterMove
は新たに挿入された DOM ノードごとに、個別に呼び出されることに注意して下さい。 即ち (注7) で示した "Yellow Fade" の例では、<li>
タグの前後にある空白のテキストノードに対しても、yellowFadeIn
が呼び出されます。つまりこの例では一度の追加でyellowFadeIn
が3回実行されるのです。jQuery のfilter
メソッドでアニメーションさせる対象を<li>
タグにフィルタリングしているのはこのためです。
( v2.1.0, v2.2.0 にて確認 )
この挙動についてさらに詳しく知りたい場合は、 こちらのデモ で、 Google Chrome の Developer Tools Console もしくは FireFox プラグインの FireBug Console にて、それぞれのコールバックが呼ばれる回数、および渡される引数を確認することをお勧めします。
依存
Knockout コアライブラリ以外、なし。