Всичко, което винаги сте искали да знаете за групирането
Когато работите с данни в шаблони, често се сблъсквате с необходимостта да ги групирате или да ги показвате конкретно по определени критерии. За тази цел Latte предлага няколко мощни инструмента.
Филтърът и функцията |group
позволяват ефективно групиране на
данните въз основа на определени критерии, докато филтърът |batch
улеснява разделянето на данните на фиксирани партиди, а етикетът
{iterateWhile}
предоставя възможност за по-сложен контрол на циклите с
условия. Всеки от тези тагове предлага специфични възможности за
работа с данни, което ги прави незаменими инструменти за динамично и
структурирано показване на информация в шаблоните Latte.
Филтър и функция group
Представете си таблица от базата данни items
с елементи,
разделени на категории:
id | categoryId | name |
---|---|---|
1 | 1 | Apple |
2 | 1 | Банан |
3 | 2 | PHP |
4 | 3 | Зелен |
5 | 3 | Червено |
6 | 3 | Синьо |
Един прост списък на всички елементи, използващ шаблон Latte, би изглеждал така:
<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) ...}
.
Ако искате да групирате елементи по по-сложни критерии, можете да използвате функция в параметъра на филтъра. Например, групирането на елементи по дължината на името им ще изглежда по следния начин:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
Важно е да се отбележи, че $categoryItems
не е обикновен масив, а
обект, който се държи като итератор. За да получите достъп до първия
елемент в групата, можете да използвате first()
функция.
Тази гъвкавост при групирането на данни прави group
изключително полезен инструмент за представяне на данни в
шаблони Latte.
Вложени цикли
Да кажем, че имаме таблица в базата данни с още една колона
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 |
---|---|
1 | Fruits |
2 | Езици |
3 | Цветове |
Зареждаме данни от таблицата items
, като използваме командата на
Nette Database Explorer $items = $db->table('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
, за да групираме по
свързания ред $item->category
, а не само по колоната categoryId
.
Това ни дава ActiveRow
на дадената категория в променливата ключ,
което ни позволява директно да покажем нейното име с помощта на
{$category->name}
. Това е практически пример за това как групирането
може да опрости шаблоните и да улесни обработката на данни.
Филтър |batch
Филтърът ви позволява да разделите списък от елементи на групи с предварително определен брой елементи. Този филтър е идеален за ситуации, в които искате да представите данните в няколко по-малки групи, например за по-добра яснота или визуална организация на страницата.
Представете си, че имаме списък с елементи и искаме да ги покажем в
списъци, всеки от които съдържа максимум три елемента. Използването на
филтъра |batch
е много практично в такъв случай:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
В този пример списъкът $items
е разделен на по-малки групи, като
всяка група ($batch
) съдържа до три елемента. След това всяка група
се показва в отделен <ul>
списък.
Ако последната група не съдържа достатъчно елементи, за да се достигне желаният брой, вторият параметър на филтъра ви позволява да определите с какво ще бъде допълнена тази група. Това е идеално за естетическо подравняване на елементи, при които непълен ред може да изглежда безредно.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Етикет {iterateWhile}
Ще демонстрираме същите задачи, които разгледахме с филтъра
|group
, като използваме тага {iterateWhile}
. Основната разлика
между двата подхода е, че group
първо обработва и групира всички
входни данни, докато {iterateWhile}
контролира хода на циклите с
условия, така че итерацията се извършва последователно.
Първо, съставяме таблица с категории, като използваме iterateWhile:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
Докато тагът {foreach}
маркира външната част на цикъла, т.е.
изготвянето на списъци за всяка категория, тагът {iterateWhile}
маркира вътрешната част, т.е. отделните елементи. Условието в тага end
казва, че повторението ще продължи, докато текущият и следващият
елемент принадлежат към една и съща категория ($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}
В рамките на един цикъл можем да създадем няколко вътрешни цикъла и дори да ги вложим. По този начин могат да се групират подкатегории, например.
Да предположим, че таблицата има още една колона 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}