Wszystko, co chcieliście wiedzieć o grupowaniu
Podczas pracy z danymi w szablonach często można napotkać potrzebę ich grupowania lub specyficznego wyświetlania według określonych kryteriów. Latte oferuje w tym celu kilka potężnych narzędzi.
Filtr i funkcja |group
umożliwiają efektywne grupowanie danych według podanego kryterium, filtr
|batch
ułatwia podział danych na ustalone partie, a znacznik {iterateWhile}
zapewnia możliwość
bardziej złożonego sterowania przebiegiem pętli z warunkami. Każdy z tych znaczników oferuje specyficzne możliwości pracy
z danymi, co czyni je niezbędnymi narzędziami do dynamicznego i strukturalnego wyświetlania informacji w
szablonach Latte.
Filtr i funkcja group
Wyobraź sobie tabelę bazy danych items
z pozycjami podzielonymi na kategorie:
id | categoryId | name |
---|---|---|
1 | 1 | Apple |
2 | 1 | Banana |
3 | 2 | PHP |
4 | 3 | Green |
5 | 3 | Red |
6 | 3 | Blue |
Prosta lista wszystkich pozycji za pomocą szablonu Latte wyglądałaby tak:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Jeśli jednak chcielibyśmy, aby pozycje były uporządkowane w grupy według kategorii, musimy je podzielić tak, aby każda kategoria miała swoją własną listę. Wynik powinien wyglądać 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 można łatwo i elegancko rozwiązać za pomocą |group
. Jako parametr podajemy categoryId
,
co oznacza, że pozycje zostaną podzielone na mniejsze tablice według wartości $item->categoryId
(jeśli
$item
byłoby tablicą, użyłoby się $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Filtr można w Latte użyć również jako funkcję, co daje nam alternatywną składnię:
{foreach group($items, categoryId) ...}
.
Jeśli chcesz grupować pozycje według bardziej złożonych kryteriów, możesz w parametrze filtra użyć funkcji. Na przykład, grupowanie pozycji według długości nazwy wyglądałoby tak:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
Ważne jest, aby pamiętać, że $categoryItems
nie jest zwykłą tablicą, ale obiektem, który zachowuje się
jak iterator. Aby uzyskać dostęp do pierwszej pozycji grupy, możesz użyć funkcji first()
.
Ta elastyczność w grupowaniu danych czyni group
wyjątkowo użytecznym narzędziem do prezentacji danych w
szablonach Latte.
Zagnieżdżone pętle
Wyobraźmy sobie, że mamy tabelę bazy danych z dodatkową kolumną subcategoryId
, która definiuje podkategorie
poszczególnych pozycji. Chcemy wyświetlić każdą główną kategorię w osobnej liście <ul>
i każdą
podkategorię w osobnej zagnieżdżonej liście <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}
Połączenie z Nette Database
Pokażmy, jak efektywnie wykorzystać grupowanie danych w połączeniu z Nette Database. Załóżmy, że pracujemy z tabelą
items
z przykładu wprowadzającego, która jest za pośrednictwem kolumny categoryId
połączona
z tą tabelą categories
:
categoryId | name |
---|---|
1 | Fruits |
2 | Languages |
3 | Colors |
Dane z tabeli items
wczytamy za pomocą Nette Database Explorer poleceniem
$items = $db->table('items')
. Podczas iteracji nad tymi danymi mamy możliwość dostępu nie tylko do atrybutów
jak $item->name
i $item->categoryId
, ale dzięki połączeniu z tabelą categories
również do powiązanego wiersza w niej przez $item->category
. Na tym połączeniu można zademonstrować ciekawe
wykorzystanie:
{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
. Dzięki temu w zmiennej klucza mamy
bezpośrednio ActiveRow
danej kategorii, co pozwala nam bezpośrednio wypisywać jej nazwę za pomocą
{$category->name}
. Jest to praktyczny przykład, jak grupowanie może uczynić szablony bardziej przejrzystymi
i ułatwić pracę z danymi.
Filtr |batch
Filtr umożliwia podział listy elementów na grupy o z góry określonej liczbie elementów. Ten filtr jest idealny w sytuacjach, gdy chcesz prezentować dane w wielu mniejszych grupach, na przykład dla lepszej przejrzystości lub wizualnego uporządkowania na stronie.
Wyobraźmy sobie, że mamy listę pozycji i chcemy je wyświetlić w listach, gdzie każda zawiera maksymalnie trzy pozycje.
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, przy czym każda grupa ($batch
)
zawiera do trzech pozycji. Każda grupa jest następnie wyświetlana w osobnej liście <ul>
.
Jeśli ostatnia grupa nie zawiera wystarczającej liczby elementów do osiągnięcia wymaganej liczby, drugi parametr filtra pozwala zdefiniować, czym ta grupa zostanie uzupełniona. Jest to idealne do estetycznego wyrównania elementów tam, gdzie niekompletny rząd mógłby wyglądać nieuporządkowanie.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Znacznik {iterateWhile}
Te same zadania, które rozwiązywaliśmy za pomocą filtra |group
, pokażemy z użyciem znacznika
{iterateWhile}
. Główna różnica między oboma podejściami polega na tym, że group
najpierw
przetwarza i grupuje wszystkie dane wejściowe, podczas gdy {iterateWhile}
steruje przebiegiem pętli z warunkami,
więc iteracja odbywa się stopniowo.
Najpierw wyrenderujemy 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ęść pętli, czyli renderowanie list dla każdej kategorii,
znacznik {iterateWhile}
oznacza wewnętrzną część, czyli poszczególne pozycje. Warunek w końcowym znaczniku
mówi, że powtarzanie będzie trwało, dopóki bieżący i następny element należą do tej samej kategorii
($iterator->nextValue
jest następnym elementem).
Gdyby warunek był zawsze spełniony, to w wewnętrznej pętli wyrenderowałyby się wszystkie elementy:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
Wynik będzie wyglądał tak:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Do czego jest dobre takie użycie iterateWhile? Gdy tabela będzie pusta i nie będzie zawierać żadnych elementów, nie
wypisze się puste <ul></ul>
.
Jeśli podamy warunek w otwierającym znaczniku {iterateWhile}
, to zachowanie się zmieni: warunek (i przejście
do następnego elementu) wykona się już na początku wewnętrznej pętli, a nie na końcu. Czyli podczas gdy do
{iterateWhile}
bez warunku wejdzie się zawsze, do {iterateWhile $cond}
tylko przy spełnieniu warunku
$cond
. A jednocześnie do $item
zapisze się następny element.
Co przydaje się na przykład w sytuacji, gdy będziemy chcieli pierwszy element w każdej kategorii wyrenderować w inny sposób, na przykład tak:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
Oryginalny kod zmodyfikujemy tak, że najpierw wyrenderujemy pierwszą pozycję, a następnie w wewnętrznej pętli
{iterateWhile}
wyrenderujemy kolejne pozycje 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 jednej pętli możemy tworzyć więcej wewnętrznych pętli i nawet je zagnieżdżać. W ten sposób można by grupować na przykład podkategorie itd.
Załóżmy, że w tabeli będzie jeszcze dodatkowa kolumna subcategoryId
i oprócz tego, że każda kategoria
będzie w osobnym <ul>
, każda podkategoria w osobnym <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}