Tot ce ați dorit vreodată să știți despre grupare
Când lucrați cu date în șabloane, puteți întâlni adesea nevoia de a le grupa sau de a le afișa specific în funcție de anumite criterii. Latte oferă în acest scop mai multe instrumente puternice.
Filtrul și funcția |group
permit gruparea eficientă a datelor conform criteriului specificat, filtrul
|batch
facilitează împărțirea datelor în loturi fixe, iar tag-ul {iterateWhile}
oferă
posibilitatea unui control mai complex al desfășurării buclelor cu condiții. Fiecare dintre aceste tag-uri oferă opțiuni
specifice pentru lucrul cu datele, devenind astfel instrumente indispensabile pentru afișarea dinamică și structurată a
informațiilor în șabloanele Latte.
Filtrul și funcția group
Imaginați-vă un tabel de bază de date items
cu elemente împărțite în categorii:
id | categoryId | name |
---|---|---|
1 | 1 | Apple |
2 | 1 | Banana |
3 | 2 | PHP |
4 | 3 | Green |
5 | 3 | Red |
6 | 3 | Blue |
O listă simplă a tuturor elementelor folosind un șablon Latte ar arăta astfel:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Dacă am dori însă ca elementele să fie aranjate în grupuri după categorie, trebuie să le împărțim astfel încât fiecare categorie să aibă propria listă. Rezultatul ar trebui să arate astfel:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Sarcina poate fi rezolvată ușor și elegant folosind |group
. Ca parametru specificăm categoryId
,
ceea ce înseamnă că elementele vor fi împărțite în array-uri mai mici în funcție de valoarea
$item->categoryId
(dacă $item
ar fi un array, s-ar folosi $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Filtrul poate fi folosit în Latte și ca funcție, ceea ce ne oferă o sintaxă alternativă:
{foreach group($items, categoryId) ...}
.
Dacă doriți să grupați elementele după criterii mai complexe, puteți folosi o funcție în parametrul filtrului. De exemplu, gruparea elementelor după lungimea numelui ar arăta astfel:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
Este important de reținut că $categoryItems
nu este un array obișnuit, ci un obiect care se comportă ca un
iterator. Pentru a accesa primul element al grupului, puteți utiliza funcția first()
.
Această flexibilitate în gruparea datelor face din group
un instrument excepțional de util pentru prezentarea
datelor în șabloanele Latte.
Bucle imbricate
Să ne imaginăm că avem un tabel de bază de date cu o coloană suplimentară subcategoryId
, care definește
subcategoriile elementelor individuale. Dorim să afișăm fiecare categorie principală într-o listă separată
<ul>
și fiecare subcategorie într-o listă imbricată separată <ol>
:
{foreach ($items|group: categoryId) as $categoryItems}
<ul>
{foreach ($categoryItems|group: subcategoryId) as $subcategoryItems}
<ol>
{foreach $subcategoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ol>
{/foreach}
</ul>
{/foreach}
Conexiunea cu Nette Database
Să vedem cum să utilizăm eficient gruparea datelor în combinație cu Nette Database. Presupunem că lucrăm cu tabelul
items
din exemplul introductiv, care este legat prin coloana categoryId
de acest tabel
categories
:
categoryId | name |
---|---|
1 | Fruits |
2 | Languages |
3 | Colors |
Datele din tabelul items
le încărcăm folosind Nette Database Explorer cu comanda
$items = $db->table('items')
. În timpul iterației peste aceste date, avem posibilitatea de a accesa nu numai
atribute precum $item->name
și $item->categoryId
, ci, datorită legăturii cu tabelul
categories
, și rândul corespunzător din acesta prin $item->category
. Pe această legătură se
poate demonstra o utilizare interesantă:
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
În acest caz, folosim filtrul |group
pentru a grupa după rândul legat $item->category
, nu doar
după coloana categoryId
. Datorită acestui fapt, în variabila cheie $category
avem direct obiectul
ActiveRow
al categoriei respective, ceea ce ne permite să afișăm direct numele său folosind
{$category->name}
. Acesta este un exemplu practic despre cum gruparea poate clarifica șabloanele și facilita
lucrul cu datele.
Filtrul |batch
Filtrul permite împărțirea unei liste de elemente în grupuri cu un număr predeterminat de elemente. Acest filtru este ideal pentru situațiile în care doriți să prezentați datele în mai multe grupuri mai mici, de exemplu, pentru o mai bună claritate sau aranjare vizuală pe pagină.
Să ne imaginăm că avem o listă de elemente și dorim să le afișăm în liste, unde fiecare conține maximum trei
elemente. Utilizarea filtrului |batch
este în acest caz foarte practică:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
În acest exemplu, lista $items
este împărțită în grupuri mai mici, fiecare grup ($batch
)
conținând până la trei elemente. Fiecare grup este apoi afișat într-o listă <ul>
separată.
Dacă ultimul grup nu conține suficiente elemente pentru a atinge numărul dorit, al doilea parametrul al filtrului permite definirea cu ce va fi completat acest grup. Acest lucru este ideal pentru alinierea estetică a elementelor acolo unde un rând incomplet ar putea părea dezordonat.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Tag-ul {iterateWhile}
Aceleași sarcini pe care le-am rezolvat cu filtrul |group
le vom arăta folosind tag-ul
{iterateWhile}
. Diferența principală între cele două abordări este că group
procesează și
grupează mai întâi toate datele de intrare, în timp ce {iterateWhile}
controlează desfășurarea buclelor cu
condiții, astfel încât iterația are loc treptat.
Mai întâi vom randa tabelul cu categoriile folosind iterateWhile
:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
În timp ce {foreach}
marchează partea exterioară a buclei, adică randarea listelor pentru fiecare categorie,
tag-ul {iterateWhile}
marchează partea interioară, adică elementele individuale. Condiția din tag-ul de
închidere spune că repetarea va continua atâta timp cât elementul curent și cel următor aparțin aceleiași categorii
($iterator->nextValue
este elementul următor).
Dacă condiția ar fi îndeplinită întotdeauna, atunci în bucla interioară s-ar randa toate elementele:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile true}
</ul>
{/foreach}
Rezultatul va arăta astfel:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
La ce este bună o astfel de utilizare a iterateWhile
? Când tabelul va fi gol și nu va conține niciun element,
nu se va afișa un <ul></ul>
gol.
Dacă specificăm condiția în tag-ul de deschidere {iterateWhile}
, atunci comportamentul se schimbă: condiția
(și trecerea la elementul următor) se execută deja la începutul buclei interioare, nu la sfârșit. Deci, în timp ce în
{iterateWhile}
fără condiție se intră întotdeauna, în {iterateWhile $cond}
se intră doar la
îndeplinirea condiției $cond
. Și, în același timp, în $item
se scrie elementul următor.
Ceea ce este util, de exemplu, în situația în care vom dori să randăm primul element din fiecare categorie într-un mod diferit, de exemplu astfel:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
Vom modifica codul original astfel încât să randăm mai întâi primul element și apoi, în bucla interioară
{iterateWhile}
, să randăm celelalte elemente din aceeași categorie:
{foreach $items as $item}
<h1>{$item->name}</h1>
<ul>
{iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
În cadrul unei singure bucle putem crea mai multe bucle interioare și chiar le putem imbrica. Astfel s-ar putea grupa, de exemplu, subcategoriile etc.
Să presupunem că în tabel va mai exista o coloană subcategoryId
și, pe lângă faptul că fiecare categorie
va fi într-un <ul>
separat, fiecare subcategorie va fi într-un <ol>
separat:
{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}