Wszystko, co zawsze chciałeś wiedzieć o grupowaniu
Podczas pracy z danymi w szablonach często pojawia się potrzeba ich grupowania lub wyświetlania według określonych kryteriów. W tym celu Latte oferuje kilka zaawansowanych narzędzi.
Filtr i funkcja |group
pozwalają na efektywne grupowanie danych w oparciu o określone kryteria, podczas gdy
filtr |batch
ułatwia dzielenie danych na stałe partie, a tag {iterateWhile}
zapewnia możliwość
bardziej złożonej kontroli cyklu z warunkami. Każdy z tych tagów oferuje określone opcje pracy z danymi, co czyni je
niezbędnymi narzędziami do dynamicznego i uporządkowanego wyświetlania informacji w szablonach Latte.
Filtr i funkcja group
Wyobraźmy sobie tabelę bazy danych items
z elementami podzielonymi na kategorie:
id | categoryId | name |
---|---|---|
1 | 1 | Jabłko |
2 | 1 | Banana |
3 | 2 | PHP |
4 | 3 | Zielony |
5 | 3 | Czerwony |
6 | 3 | Niebieski |
Prosta lista wszystkich elementów przy użyciu szablonu Latte wyglądałaby następująco:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Jeśli jednak chcemy, aby przedmioty były zorganizowane w grupy według kategorii, musimy podzielić je tak, aby każda kategoria miała własną listę. Wynik wyglądałby wtedy następująco:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Zadanie to można łatwo i elegancko rozwiązać za pomocą |group
. Jako parametr podajemy
categoryId
, co oznacza, że elementy zostaną podzielone na mniejsze tablice w oparciu o wartość
$item->categoryId
(gdyby $item
była tablicą, użylibyśmy $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Filtr może być również użyty jako funkcja w Latte, dając nam alternatywną składnię:
{foreach group($items, categoryId) ...}
.
Jeśli chcesz pogrupować elementy według bardziej złożonych kryteriów, możesz użyć funkcji w parametrze filtra. Na przykład grupowanie elementów według długości ich nazwy wyglądałoby następująco:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
Należy zauważyć, że $categoryItems
nie jest zwykłą tablicą, ale obiektem, który zachowuje się jak
iterator. Aby uzyskać dostęp do pierwszego elementu w grupie, można użyć funkcji first()
function.
Ta elastyczność w grupowaniu danych sprawia, że group
jest wyjątkowo przydatnym narzędziem do prezentacji
danych w szablonach Latte.
Zagnieżdżone pętle
Załóżmy, że mamy tabelę bazy danych z inną kolumną subcategoryId
, która definiuje podkategorie dla
każdego elementu. Chcemy wyświetlić każdą główną kategorię na osobnej liście <ul>
a każdą
podkategorię na osobnej zagnieżdżonej liście <ol>
liście:
{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}
Połączenie z bazą danych Nette
Pokażmy, jak efektywnie wykorzystać grupowanie danych w połączeniu z Nette Database. Załóżmy, że pracujemy z tabelą
items
z początkowego przykładu, która jest połączona przez kolumnę categoryId
z tabelą
categories
:
categoryId | name |
---|---|
1 | Fruits |
2 | Języki |
3 | Kolory |
Wczytujemy dane z tabeli items
za pomocą polecenia Nette Database Explorer
$items = $db->table('items')
. Podczas iteracji po tych danych mamy możliwość nie tylko dostępu do atrybutów
takich jak $item->name
i $item->categoryId
, ale dzięki połączeniu z tabelą
categories
, także do powiązanego z nią wiersza poprzez $item->category
. To połączenie może
wykazać interesujące zastosowania:
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
W tym przypadku używamy filtra |group
do grupowania według połączonego wiersza
$item->category
, a nie tylko według kolumny categoryId
. Daje nam to ActiveRow
danej
kategorii w kluczu zmiennej, co pozwala nam bezpośrednio wyświetlić jej nazwę za pomocą {$category->name}
.
Jest to praktyczny przykład tego, jak grupowanie może uprościć szablony i ułatwić obsługę danych.
Filtr |batch
Filtr umożliwia podzielenie listy elementów na grupy o z góry określonej liczbie elementów. Ten filtr jest idealny w sytuacjach, gdy chcesz przedstawić dane w kilku mniejszych grupach, na przykład dla lepszej przejrzystości lub wizualnej organizacji na stronie.
Wyobraźmy sobie, że mamy listę elementów i chcemy wyświetlić je na listach, z których każda zawiera maksymalnie trzy
elementy. Użycie filtra |batch
jest w takim przypadku bardzo praktyczne:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
W tym przykładzie lista $items
jest podzielona na mniejsze grupy, z których każda ($batch
)
zawiera maksymalnie trzy elementy. Każda grupa jest następnie wyświetlana na osobnej <ul>
liście.
Jeśli ostatnia grupa nie zawiera wystarczającej liczby elementów, aby osiągnąć pożądaną liczbę, drugi parametr filtra pozwala określić, czym ta grupa zostanie uzupełniona. Jest to idealne rozwiązanie do estetycznego wyrównania elementów, w przypadku których niekompletny wiersz może wyglądać na nieuporządkowany.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Tag {iterateWhile}
Zademonstrujemy te same zadania, którymi zajmowaliśmy się z filtrem |group
przy użyciu tagu
{iterateWhile}
. Główna różnica między tymi dwoma podejściami polega na tym, że group
najpierw
przetwarza i grupuje wszystkie dane wejściowe, podczas gdy {iterateWhile}
kontroluje postęp cykli z warunkami,
więc iteracja odbywa się sekwencyjnie.
Najpierw rysujemy tabelę z kategoriami za pomocą iterateWhile:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
Podczas gdy {foreach}
oznacza zewnętrzną część cyklu, tj. rysowanie list dla każdej kategorii, znacznik
{iterateWhile}
oznacza część wewnętrzną, tj. poszczególne elementy. Warunek w znaczniku end mówi, że
powtarzanie będzie kontynuowane tak długo, jak bieżący i następny element należą do tej samej kategorii
($iterator->nextValue
jest następnym elementem).
Gdyby warunek był zawsze spełniony, wszystkie elementy byłyby rysowane w wewnętrznym cyklu:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
Wynik będzie wyglądał następująco:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Jaki jest pożytek z iterateWhile w ten sposób? Gdy tabela jest pusta i nie zawiera żadnych elementów, nie jest wypisywana
pusta wartość <ul></ul>
jest drukowany.
Jeśli określimy warunek w otwierającym tagu {iterateWhile}
, zachowanie ulegnie zmianie: warunek (i przejście
do następnego elementu) jest wykonywany na początku wewnętrznego cyklu, a nie na końcu. Tak więc, podczas gdy zawsze
wchodzisz na {iterateWhile}
bez warunków, wchodzisz na {iterateWhile $cond}
tylko wtedy, gdy warunek
$cond
jest spełniony. W tym samym czasie następny element jest zapisywany w $item
.
Jest to przydatne na przykład w sytuacji, gdy chcemy wyrenderować pierwszy element w każdej kategorii w inny sposób, na przykład w następujący sposób:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
Modyfikujemy oryginalny kod w taki sposób, że najpierw renderujemy pierwszy element, a następnie w wewnętrznym cyklu
{iterateWhile}
renderujemy pozostałe elementy z tej samej kategorii:
{foreach $items as $item}
<h1>{$item->name}</h1>
<ul>
{iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
W ramach jednego cyklu możemy utworzyć wiele wewnętrznych pętli, a nawet je zagnieżdżać. W ten sposób można na przykład grupować podkategorie.
Załóżmy, że tabela ma jeszcze jedną kolumnę subcategoryId
, a oprócz tego każda kategoria znajduje się w
osobnej kolumnie. <ul>
a każda podkategoria w osobnej <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}