Nette Documentation Preview

syntax
Всичко, което някога сте искали да знаете за
групирането
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Всичко, което някога сте искали да знаете за групирането

При работа с данни в шаблони често можете да срещнете нуждата от тяхното групиране или специфично показване според определени критерии. Latte за тази цел предлага няколко силни инструмента.

Филтърът и функцията |group позволяват ефективно групиране на данни според зададен критерий, филтърът |batch пък улеснява разделянето на данни на предварително зададени партиди, а тагът {iterateWhile} предоставя възможност за по-сложно управление на протичането на цикли с условия. Всеки от тези тагове предлага специфични възможности за работа с данни, което ги прави незаменими инструменти за динамично и структурирано показване на информация в Latte шаблони.

Филтър и функция group

Представете си таблица в база данни items с елементи, разделени на категории:

id categoryId name
1 1 Apple
2 1 Banana
3 2 PHP
4 3 Green
5 3 Red
6 3 Blue

Прост списък на всички елементи с помощта на 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 Database

Нека покажем как ефективно да използваме групирането на данни в комбинация с Nette Database. Да предположим, че работим с таблицата items от уводния пример, която чрез колоната categoryId е свързана с тази таблица categories:

categoryId name
1 Fruits
2 Languages
3 Colors

Данните от таблицата 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} обозначава вътрешната част, т.е. отделните елементи. Условието в крайния таг казва, че повторението ще продължи дотогава, докато текущият и следващият елемент принадлежат към същата категория ($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}