グループ化について知りたかったことのすべて
テンプレートでデータを扱うとき、特定の条件に従ってデータをグループ化したり、特別に表示したりする必要性にしばしば遭遇します。この目的のために、Latteはいくつかの強力なツールを提供しています。
フィルタとファンクション|group
は指定された条件に基づいて効率的にデータをグループ化することができます。|batch
フィルタはデータを一定のバッチに分割することを容易にし、{iterateWhile}
タグは条件によってより複雑なサイクル制御の可能性を提供します。
これらのタグはそれぞれデータ操作のための特別なオプションを提供し、ラテのテンプレートで情報をダイナミックかつ構造的に表示するために不可欠なツールとなっています。
フィルタと関数group
アイテムがカテゴリーに分けられたデータベース・テーブルitems
を想像してください:
id|カテゴリーID|名前 |
---|
1|アップル |
2|1|バナナ |
3|2|PHP |
4|3|グリーン |
5|3|赤 |
6|3|青 |
ラテテンプレートを使った単純な全項目リストは次のようになる:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
しかし、アイテムをカテゴリーごとにグループ化したい場合は、各カテゴリーが独自のリストを持つようにアイテムを分割する必要があります。その結果、次のようになる:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
このタスクは、|group
を使って簡単かつエレガントに解決できる。パラメータとしてcategoryId
を指定する。つまり、$item->categoryId
の値に基づいて、アイテムがより小さな配列に分割されることを意味する($item
が配列の場合、次のようになる)。 $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
フィルタはLatteの関数として使うこともできるので、別の構文も用意されている:{foreach group($items, categoryId) ...}
.
より複雑な条件に従ってアイテムをグループ化したい場合は、filterパラメータに関数を使用できます。たとえば、名前の長さでアイテムをグループ化すると、次のようになります:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
注意すべき点は、$categoryItems
は通常の配列ではなく、イテレータのように振る舞うオブジェクトであるということです。グループの最初の項目にアクセスするには
first()
関数を使います。
このようにデータを柔軟にグループ化できるので、group
はラテのテンプレートでデータを表示するのに非常に便利なツールです。
ネストされたループ
各アイテムのサブカテゴリーを定義する別のカラムsubcategoryId
を持つデータベーステーブルがあるとします。各メインカテゴリを個別のリストで表示し、各サブカテゴリを個別のネストされた
<ul>
リストで表示し、各サブカテゴリーを別のネストされた
<ol>
リストに表示したい:
{foreach ($items|group: categoryId) as $categoryItems}
<ul>
{foreach ($categoryItems|group: subcategoryId) as $subcategoryItems}
<ol>
{foreach $subcategoryItems as $item}
<li>{$item->name}
{/foreach}
</ol>
{/foreach}
</ul>
{/foreach}
Nette データベースとの接続
Nette
Databaseと組み合わせて、データグループ化を効果的に使う方法を説明しよう。最初の例のitems
テーブルを操作しているとします。 テーブルは、categoryId
カラムを通して、このcategories
テーブルに接続されています:
categoryId | name ||カテゴリーID ||名前
1 | フルーツ |
---|---|
2| 言語 | |
3|色 |
Nette Database Explorerのコマンド$items = $db->table('items')
を使って、items
テーブルからデータをロードする。これらのデータに対する反復の間、$item->name
や$item->categoryId
のような属性にアクセスする機会があるだけでなく、categories
テーブルとの接続のおかげで、$item->category
を介してその中の関連行にもアクセスすることができます。この接続は、興味深い使い方ができる:
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
この場合、|group
フィルタを使用して、categoryId
列だけでなく、接続された行$item->category
によってグループ化します。これにより、変数キーに指定されたカテゴリのActiveRow
が得られ、{$category->name}
を使ってその名前を直接表示することができる。これは、グループ化によってテンプレートが簡素化され、データの取り扱いが容易になることの実例である。
フィルター|batch
フィルタを使うと、要素のリストをあらかじめ決められた数のグループに分割することができます。このフィルタは、例えば、ページ上でより明瞭に、あるいは視覚的に整理するために、データをいくつかの小さなグループに分けて表示したい場合に最適です。
アイテムのリストがあり、それぞれ最大3つのアイテムを含むリストで表示したいとします。このような場合、|batch
フィルタの使用は非常に実用的です:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
この例では、リスト$items
は小さなグループに分割され、各グループ ($batch
)
には最大3つのアイテムが含まれます。各グループは別々の
<ul>
リストに表示されます。
最後のグループには必要な数に達するだけの要素が含まれていない場合、フィルタの2番目のパラメータで、このグループに何を追加するかを定義することができます。これは、不完全な行が無秩序に見えるかもしれない要素を美的に整列させるのに理想的です。
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
タグ{iterateWhile}
{iterateWhile}
タグを使用して、|group
フィルタで扱ったのと同じタスクを実演する。この2つのアプローチの主な違いは、group
が最初にすべての入力データを処理してグループ化するのに対し、{iterateWhile}
は条件付きサイクルの進行を制御するため、反復が順次発生することです。
まず、iterateWhileを使用して、カテゴリーで表を描画する:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
{foreach}
がサイクルの外側、すなわち各カテゴリーのリストを描画する部分を示すのに対し、{iterateWhile}
タグは内側、すなわち個々の項目を示す。
終了タグの条件は、現在の要素と次の要素が同じカテゴリーに属している限り、繰り返しを続けるというものです($iterator->nextValue
は次の項目です)。
この条件が常に満たされていれば、すべての要素が内側のサイクルで描かれることになる:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
結果は次のようになる:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
このようなiterateWhileの使い方は何でしょうか?テーブルが空で要素がない場合、空の
<ul></ul>
は出力されません。
冒頭の{iterateWhile}
タグで条件を指定すると、動作が変わります。条件(および次の要素への遷移)は、終了時ではなく、内部サイクルの開始時に実行されます。
したがって、{iterateWhile}
には常に無条件で入るが、{iterateWhile $cond}
には$cond
の条件が満たされたときだけ入る。そして同時に、次の要素が$item
に書き込まれる。
これは、例えば、各カテゴリーの最初の要素を、次のように異なるようにレンダリングしたい場合に便利です:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
元のコードを修正して、まず最初の項目をレンダリングし、次に内側のサイクル{iterateWhile}
で同じカテゴリの他の項目をレンダリングするようにします:
{foreach $items as $item}
<h1>{$item->name}</h1>
<ul>
{iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
1つのサイクルの中で、複数の内部ループを作成し、ネストさせることもできる。こうすることで、たとえば、サブカテゴリーをグループ化することができる。
テーブルにもう1つのカラムsubcategoryId
、各カテゴリーがそれぞれ別のカラムにあるとします。 <ul>
サブカテゴリーを別の
<ol>
:
{foreach $items as $item}
<ul>
{iterateWhile}
<ol>
{iterateWhile}
<li>{$item->name}
{/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId}
</ol>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}