Nette Documentation Preview

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

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

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


Филтър и функция `group` .{data-version:3.0.16}
===============================================

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

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

Прост списък на всички елементи с помощта на Latte шаблон би изглеждал така:

```latte
<ul>
{foreach $items as $item}
	<li>{$item->name}</li>
{/foreach}
</ul>
```

Ако обаче искахме елементите да бъдат подредени в групи според категорията, трябва да ги разделим така, че всяка категория да има свой собствен списък. Резултатът тогава трябва да изглежда по следния начин:

```latte
<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']`):

```latte
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
	<ul>
		{foreach $categoryItems as $item}
			<li>{$item->name}</li>
		{/foreach}
	</ul>
{/foreach}
```

Филтърът може в Latte да се използва и като функция, което ни дава алтернативен синтаксис: `{foreach group($items, categoryId) ...}`.

Ако искате да групирате елементи според по-сложни критерии, можете в параметъра на филтъра да използвате функция. Например, групиране на елементи според дължината на името би изглеждало така:

```latte
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
	...
{/foreach}
```

Важно е да се осъзнае, че `$categoryItems` не е обикновен масив, а обект, който се държи като итератор. За достъп до първия елемент на групата можете да използвате функцията [`first()` |latte:functions#first].

Тази гъвкавост в групирането на данни прави `group` изключително полезен инструмент за представяне на данни в шаблони Latte.


Вложени цикли
-------------

Представете си, че имаме база данни с допълнителна колона `subcategoryId`, която дефинира подкатегориите на отделните елементи. Искаме да покажем всяка основна категория в отделен списък `<ul>` и всяка подкатегория в отделен вложен списък `<ol>`:

```latte
{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`. На тази връзка може да се демонстрира интересно използване:

```latte
{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` в такъв случай е много практично:

```latte
<ul>
{foreach ($items|batch: 3) as $batch}
	{foreach $batch as $item}
		<li>{$item->name}</li>
	{/foreach}
{/foreach}
</ul>
```

В този пример списъкът `$items` е разделен на по-малки групи, като всяка група (`$batch`) съдържа до три елемента. Всяка група след това се показва в отделен `<ul>` списък.

Ако последната група не съдържа достатъчно елементи за достигане на желания брой, вторият параметър на филтъра позволява да се дефинира с какво ще бъде допълнена тази група. Това е идеално за естетическо подравняване на елементите там, където непълният ред би могъл да изглежда неподреден.

```latte
{foreach ($items|batch: 3, '—') as $batch}
	...
{/foreach}
```


Таг `{iterateWhile}`
====================

Същите задачи, които решавахме с филтъра `|group`, ще покажем с използването на тага `{iterateWhile}`. Основната разлика между двата подхода е в това, че `group` първо обработва и групира всички входни данни, докато `{iterateWhile}` управлява протичането на цикли с условия, така че итерацията протича постепенно.

Първо ще рендираме таблицата с категориите с помощта на iterateWhile:

```latte
{foreach $items as $item}
	<ul>
		{iterateWhile}
			<li>{$item->name}</li>
		{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
	</ul>
{/foreach}
```

Докато `{foreach}` обозначава външната част на цикъла, т.е. рендирането на списъци за всяка категория, тагът `{iterateWhile}` обозначава вътрешната част, т.е. отделните елементи. Условието в крайния таг казва, че повторението ще продължи дотогава, докато текущият и следващият елемент принадлежат към същата категория (`$iterator->nextValue` е [следващият елемент |/tags#iterator]).

Ако условието беше изпълнено винаги, тогава във вътрешния цикъл ще се рендират всички елементи:

```latte
{foreach $items as $item}
	<ul>
		{iterateWhile}
			<li>{$item->name}
		{/iterateWhile true}
	</ul>
{/foreach}
```

Резултатът ще изглежда така:

```latte
<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` се записва следващият елемент.

Което е полезно например в ситуация, когато искаме първия елемент във всяка категория да рендираме по различен начин, например така:

```latte
<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}` да рендираме другите елементи от същата категория:

```latte
{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>`:

```latte
{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}
```


{{leftbar: /@left-menu}}

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

При работа с данни в шаблони често можете да срещнете нуждата от тяхното групиране или специфично показване според определени критерии. 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}