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}
versiune: 3.0