Skip to content

Commit

Permalink
x
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 14, 2024
1 parent 4ba9ed7 commit 91d0b92
Showing 1 changed file with 130 additions and 87 deletions.
217 changes: 130 additions & 87 deletions latte/en/cookbook/grouping.texy
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
Everything You Always Wanted to Know About Grouping
***************************************************
***********************************************

.[perex]
The tag `{iterateWhile}` is suitable for various tricks in foreach cycles.
Při práci s daty ve šablonách můžete často narazit na potřebu jejich seskupování nebo specifického zobrazení podle určitých kritérií. Latte pro tento účel nabízí hned několik silných nástrojů.

Suppose we have the following database table, where the items are divided into categories:
Filtr a funkce `|group` umožňují efektivní seskupení dat podle zadaného kritéria, filtr `|batch` zase usnadňuje rozdělení dat do pevně daných dávek a značka `{iterateWhile}` poskytuje možnost složitějšího řízení průběhu cyklů s podmínkami.
Každá z těchto značek nabízí specifické možnosti pro práci s daty, čímž se stávají nepostradatelnými nástroji pro dynamické a strukturované zobrazení informací v Latte šablonách.


Filtr a funkce `group` .{data-version:3.0.16}
=============================================

Představte si databázovou tabulku `items` s položkami rozdělenou do kategorií:

| id | categoryId | name
|------------------
Expand All @@ -15,7 +22,7 @@ Suppose we have the following database table, where the items are divided into c
| 5 | 3 | Red
| 6 | 3 | Blue

Of course, drawing items in a foreach loop as a list is easy:
Jednoduchý seznam všech položek pomocí Latte šablony by vypadal takto:

```latte
<ul>
Expand All @@ -25,7 +32,7 @@ Of course, drawing items in a foreach loop as a list is easy:
</ul>
```

But what to do if you want render each category in a separate list? In other words, how to solve the task of grouping items from a linear list in a foreach cycle. The output should look like this:
Pokud bychom ale chtěli, aby položky byly uspořádány do skupin podle kategorie, potřebujeme je rozdělit tak, že každá kategorie bude mít svůj vlastní seznam. Výsledek by pak měl vypadat následovně:

```latte
<ul>
Expand All @@ -44,92 +51,159 @@ But what to do if you want render each category in a separate list? In other wor
</ul>
```

We will show you how easily and elegantly the task can be solved with iterateWhile:
Úkol se dá snadno a elegantně vyřešit pomocí `|group`. Jako parametr uvedeme `categoryId`, což znamená, že se položky rozdělí do menších polí podle hodnoty `$item->categoryId` (pokud by `$item` bylo pole, použije se `$item['categoryId']`):

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

While `{foreach}` marks the outer part of the cycle, ie the drawing of lists for each category, the tags `{iterateWhile}` indicate the inner part, ie the individual items.
The condition in the end tag says that the repetition will continue as long as the current and the next element belong to the same category (`$iterator->nextValue` is [next item |/tags#$iterator]).
Filtr lze v Latte použít i jako funkci, což nám dává alternativní syntaxi: `{foreach group($items, categoryId) ...}`.

If the condition is always met, then all elements are drawn in the inner cycle:
Chcete-li seskupovat položky podle složitějších kritérií, můžete v parametru filtru použít funkci. Například, seskupení položek podle délky názvu by vypadalo takto:

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

Je důležité si uvědomit, že `$categoryItems` není běžné pole, ale objekt, který se chová jako iterátor. Pro přístup k první položce skupiny můžete použít funkci [`first()`|latte:functions#first].

Tato flexibilita v seskupování dat činí `group` výjimečně užitečným nástrojem pro prezentaci dat v šablonách Latte.


Vnořené smyčky
--------------

Představme si, že máme databázovou tabulku s dalším sloupcem `subcategoryId`, který definuje podkategorie jednotlivých položek. Chceme zobrazit každou hlavní kategorii v samostatném seznamu `<ul>` a každou podkategorii v samostatném vnořeném seznamu `<ol>`:

```latte
{foreach ($items|group: categoryId) as $categoryItems}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
{foreach ($categoryItems|group: subcategoryId) as $subcategoryItems}
<ol>
{foreach $subcategoryItems as $item}
<li>{$item->name}
{/foreach}
</ol>
{/foreach}
</ul>
{/foreach}
```

The result will look like this:

Spojení s Nette Database
------------------------

Pojďme si ukázat, jak efektivně využít seskupování dat v kombinaci s Nette Database. Předpokládejme, že pracujeme s tabulkou `items` z úvodního příkladu, která je prostřednictvím sloupce `categoryId` spojená s touto tabulkou `categories`:

| categoryId | name |
|------------|------------|
| 1 | Fruits |
| 2 | Languages |
| 3 | Colors |

Data z tabulky `items` načteme pomocí Nette Database Explorer příkazem `$items = $db->table('items')`. Během iterace nad těmito daty máme možnost přistupovat nejen k atributům jako `$item->name` a `$item->categoryId`, ale díky propojení s tabulkou `categories` také k souvisejícímu řádku v ní přes `$item->category`. Na tomto propojení lze demonstrovat zajímavé využití:

```latte
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
```

What good is such an use of iterateWhile? How it differs from the solution we showed at the very beginning of this tutorial? The difference is that if the table is empty and does not contain any elements, it will not render empty `<ul></ul>`.
V tomto případě používáme filtr `|group` k seskupení podle propojeného řádku `$item->category`, nikoliv jen dle sloupce `categoryId`. Díky tomu v proměnné klíči přímo `ActiveRow` dané kategorie, což nám umožňuje přímo vypisovat její název pomocí `{$category->name}`. Toto je praktický příklad, jak může seskupování zpřehlednit šablony a usnadnit práci s daty.


Filtr `|batch`
==============

Solution Without `{iterateWhile}`
---------------------------------
Filtr umožňuje rozdělit seznam prvků do skupin s předem určeným počtem prvků. Tento filtr je ideální pro situace, kdy chcete data prezentovat ve více menších skupinách, například pro lepší přehlednost nebo vizuální uspořádání na stránce.

If we solved the same task with completely basic constructions of template systems, for example in Twig, Blade, or pure PHP, the solution would look something like this:
Představme si, že máme seznam položek a chceme je zobrazit v seznamech, kde každý obsahuje maximálně tři položky. Použití filtru `|batch` je v takovém případě velmi praktické:

```latte
{var $prevCategoryId = null}
{foreach $items as $item}
{if $item->categoryId !== $prevCategoryId}
{* the category has changed *}
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
```

{* we close the previous <ul>, if it is not the first item *}
{if $prevCategoryId !== null}
</ul>
{/if}
V tomto příkladu je seznam `$items` rozdělen do menších skupin, přičemž každá skupina (`$batch`) obsahuje až tři položky. Každá skupina je poté zobrazena v samostatném `<ul>` seznamu.

{* we will open a new list *}
<ul>
Pokud poslední skupina neobsahuje dostatek prvků k dosažení požadovaného počtu, druhý parametr filtru umožňuje definovat, čím bude tato skupina doplněna. To je ideální pro estetické zarovnání prvků tam, kde by neúplná řada mohla působit neuspořádaně.

{do $prevCategoryId = $item->categoryId}
{/if}
```latte
{foreach ($items|batch: 3, '—') as $batch}
...
</ul>
```

<li>{$item->name}</li>
{/foreach}

{if $prevCategoryId !== null}
{* we close the last list *}
Značka `{iterateWhile}`
=======================

Stejné úkoly, jako jsme řešili s filtrem `|group`, si ukážeme s použitím značky `{iterateWhile}`. Hlavní rozdíl mezi oběma přístupy je v tom, že `group` nejprve zpracuje a seskupí všechna vstupní data, zatímco `{iterateWhile}` řídí průběhu cyklů s podmínkami, takže iterace probíhá postupně.

Nejprve vykreslíme tabulku s kategoriemi pomocí iterateWhile:

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

However, this code is incomprehensible and unintuitive. The connection between the opening and closing HTML tags is not clear at all. It is not clear at first glance if there is a mistake. And it requires auxiliary variables like `$prevCategoryId`.
Zatímco `{foreach}` označuje vnější část cyklu, tedy vykreslování seznamů pro každou kategorii, tak značka `{iterateWhile}` označuje vnitřní část, tedy jednotlivé položky.
Podmínka v koncové značce říká, že opakování bude probíhat do té doby, dokud aktuální i následující prvek patří do stejné kategorie (`$iterator->nextValue` je [následující položka|/tags#$iterator]).

Kdyby podmínka byla splněná vždy, tak se ve vnitřním cyklu vykreslí všechny prvky:

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

In contrast, the solution with `{iterateWhile}` is clean, clear, does not need auxiliary variables and is foolproof.
Výsledek bude vypadat takto:

```latte
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
```

Condition in the Closing Tag
----------------------------
K čemu je takové použití iterateWhile dobré? Když bude tabulka prázdná a nebude obsahovat žádné prvky, nevypíše se prázdné `<ul></ul>`.

If we specify a condition in the opening tag `{iterateWhile}`, the behavior changes: the condition (and the advance to the next element) is executed at the beginning of the inner cycle, not at the end.
Thus, while `{iterateWhile}` without condition is always entered, `{iterateWhile $cond}` is entered only when condition `$cond` is met. At the same time, the following element is written to `$item`.
Pokud uvedeme podmínku v otevírací značce `{iterateWhile}`, tak se chování změní: podmínka (a přechod na další prvek) se vykoná už na začátku vnitřního cyklu, nikoliv na konci.
Tedy zatímco do `{iterateWhile}` bez podmínky se vstoupí vždy, do `{iterateWhile $cond}` jen při splnění podmínky `$cond`. A zároveň se s tím do `$item` zapíše následující prvek.

This is useful, for example, in a situation where you want to render the first element in each category in a different way, such as:
Což se hodí například v situaci, kdy budeme chtít první prvek v každé kategorii vykreslit jiným způsobem, například takto:

```latte
<h1>Apple</h1>
Expand All @@ -148,7 +222,7 @@ This is useful, for example, in a situation where you want to render the first e
</ul>
```

Lets modify the original code, we draw first item and then additional items from the same category in the inner loop `{iterateWhile}`:
Původní kód upravíme tak, že nejprve vykreslíme první položku a poté ve vnitřním cyklu `{iterateWhile}` vykreslíme další položky ze stejné kategorie:

```latte
{foreach $items as $item}
Expand All @@ -161,13 +235,9 @@ Lets modify the original code, we draw first item and then additional items from
{/foreach}
```

V rámci jednoho cyklu můžeme vytvářet více vnitřních smyček a dokonce je zanořovat. Takto by se daly seskupovat třeba podkategorie atd.

Nested Loops
------------

We can create multiple inner loops in one cycle and even nest them. In this way, for example, subcategories could be grouped.

Suppose there is another column in the table `subcategoryId` and in addition to each category being in a separate `<ul>`, each subcategory will be in a separate `<ol>`:
Dejme tomu, že v tabulce bude ještě další sloupec `subcategoryId` a kromě toho, že každá kategorie bude v samostatném `<ul>`, každá každý podkategorie samostatném `<ol>`:

```latte
{foreach $items as $item}
Expand All @@ -184,31 +254,4 @@ Suppose there is another column in the table `subcategoryId` and in addition to
```


Filter |batch
-------------

The grouping of linear items is also provided by a filter `batch`, into batches with a fixed number of elements:

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

It can be replaced with iterateWhile as follows:

```latte
<ul>
{foreach $items as $item}
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $iterator->counter0 % 3}
{/foreach}
</ul>
```

{{leftbar: /@left-menu}}

0 comments on commit 91d0b92

Please sign in to comment.