Minden, amit mindig is tudni akartál a csoportosításról

Amikor sablonokban dolgozik az adatokkal, gyakran találkozik azzal az igénnyel, hogy csoportosítani vagy bizonyos kritériumok szerint megjeleníteni kell őket. Erre a célra a Latte számos hatékony eszközt kínál.

A |group szűrő és funkció lehetővé teszi az adatok hatékony csoportosítását meghatározott kritériumok alapján, míg a |batch szűrő megkönnyíti az adatok rögzített tételekre való felosztását, a {iterateWhile} címke pedig lehetőséget biztosít a feltételek segítségével történő összetettebb ciklusszabályozásra. E címkék mindegyike speciális lehetőségeket kínál az adatokkal való munkához, így nélkülözhetetlen eszközök az információk dinamikus és strukturált megjelenítéséhez a Latte sablonokban.

Szűrő és funkció group

Képzeljünk el egy items adatbázis-táblát, amelyben kategóriákba sorolt tételek találhatók:

id categoryId name
1 1 1 Apple
2 1 Banán  
3 2 PHP  
4 3 Zöld  
5 3 Piros  
6 3 Kék  

Az összes elem egyszerű listája a Latte sablon használatával így nézne ki:

<ul>
{foreach $items as $item}
	<li>{$item->name}</li>
{/foreach}
</ul>

Ha azonban azt szeretnénk, hogy az elemeket kategóriák szerint csoportosítani tudjuk, akkor úgy kell felosztanunk őket, hogy minden kategóriának saját listája legyen. Az eredmény így nézne ki:

<ul>
	<li>Apple</li>
	<li>Banana</li>
</ul>

<ul>
	<li>PHP</li>
</ul>

<ul>
	<li>Green</li>
	<li>Red</li>
	<li>Blue</li>
</ul>

A feladat könnyen és elegánsan megoldható a |group segítségével. Paraméterként megadjuk a categoryId címet, ami azt jelenti, hogy az elemeket a $item->categoryId érték alapján kisebb tömbökre osztjuk fel (ha a $item egy tömb lenne, akkor a következővel dolgoznánk $item['categoryId']):

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

A szűrő a Latte-ban függvényként is használható, így alternatív szintaxist kapunk: {foreach group($items, categoryId) ...}.

Ha összetettebb kritériumok szerint szeretnénk csoportosítani az elemeket, akkor a szűrő paraméterében használhatunk függvényt. Például az elemek csoportosítása a nevük hossza alapján így nézne ki:

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

Fontos megjegyezni, hogy a $categoryItems nem egy hagyományos tömb, hanem egy olyan objektum, amely iterátorként viselkedik. A csoport első elemének eléréséhez használhatja a first() függvényt.

Az adatok csoportosításának ez a rugalmassága teszi a group -t kivételesen hasznos eszközzé az adatok Latte sablonokban történő bemutatásához.

Beágyazott hurkok

Tegyük fel, hogy van egy adatbázis-táblánk egy másik subcategoryId oszloppal, amely az egyes tételek alkategóriáit határozza meg. Minden egyes fő kategóriát egy különálló <ul> listában, és minden alkategóriát egy külön beágyazott listában. <ol> listában:

{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}

Kapcsolat a Nette adatbázissal

Megmutatjuk, hogyan lehet hatékonyan használni az adatcsoportosítást a Nette Adatbázissal kombinálva. Tegyük fel, hogy az eredeti példában szereplő items táblával dolgozunk, amely a categoryId oszlopon keresztül kapcsolódik ehhez a categories táblához:

categoryId név
1 Gyümölcsök
2 Nyelvek
3 Színek

Az adatokat a items táblából a Nette Database Explorer $items = $db->table('items') parancsával töltjük be. Az ezeken az adatokon való iteráció során nemcsak az olyan attribútumokhoz férhetünk hozzá, mint a $item->name és a $item->categoryId, hanem a categories táblával való kapcsolatnak köszönhetően a $item->category segítségével a tábla kapcsolódó sorához is. Ez a kapcsolat érdekes felhasználási lehetőségeket mutathat fel:

{foreach ($items|group: category) as $category => $categoryItems}
	<h1>{$category->name}</h1>
	<ul>
		{foreach $categoryItems as $item}
			<li>{$item->name}</li>
		{/foreach}
	</ul>
{/foreach}

Ebben az esetben a |group szűrővel a $item->category kapcsolódó sora szerint csoportosítunk, nem csak a categoryId oszlop szerint. Ezáltal a változó kulcsában megkapjuk az adott kategória ActiveRow címét, így a {$category->name} segítségével közvetlenül megjeleníthetjük a nevét. Ez egy gyakorlati példa arra, hogy a csoportosítás hogyan egyszerűsítheti a sablonokat és könnyítheti meg az adatkezelést.

Szűrő |batch

A szűrő lehetővé teszi, hogy az elemek listáját előre meghatározott számú elemet tartalmazó csoportokra ossza. Ez a szűrő ideális olyan helyzetekben, amikor az adatokat több kisebb csoportban szeretné megjeleníteni, például a jobb áttekinthetőség vagy az oldalon való vizuális rendezés érdekében.

Képzeljük el, hogy van egy elemeket tartalmazó listánk, és azokat listákban szeretnénk megjeleníteni, amelyek mindegyike legfeljebb három elemet tartalmazhat. A |batch szűrő használata ilyen esetben nagyon praktikus:

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

Ebben a példában a $items listát kisebb csoportokra osztjuk, és minden csoport ($batch) legfeljebb három elemet tartalmaz. Ezután minden csoport egy különálló <ul> listában.

Ha az utolsó csoport nem tartalmaz elegendő elemet a kívánt szám eléréséhez, a szűrő második paramétere lehetővé teszi annak meghatározását, hogy mivel egészüljön ki ez a csoport. Ez ideális az elemek esztétikai összehangolására, ahol egy hiányos sor rendezetlennek tűnhet.

{foreach ($items|batch: 3, '—') as $batch}
	...
{/foreach}

Címke {iterateWhile}

A {iterateWhile} címkével ugyanazokat a feladatokat mutatjuk be, mint amiket a |group szűrővel kezeltünk. A fő különbség a két megközelítés között az, hogy a group először feldolgozza és csoportosítja az összes bemeneti adatot, míg a {iterateWhile} a feltételekkel rendelkező ciklusok előrehaladását vezérli, így az iteráció szekvenciálisan történik.

Először egy kategóriákat tartalmazó táblázatot rajzolunk az iterateWhile segítségével:

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

Míg a {foreach} a ciklus külső részét jelöli, azaz az egyes kategóriákhoz tartozó listák rajzolását, addig a {iterateWhile} címke a belső részt, azaz az egyes elemeket. Az end tagben lévő feltétel azt mondja, hogy az ismétlés addig folytatódik, amíg az aktuális és a következő elem ugyanahhoz a kategóriához tartozik ($iterator->nextValue a következő elem).

Ha a feltétel mindig teljesülne, akkor a belső ciklusban minden elem kirajzolódna:

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

Az eredmény így fog kinézni:

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

Mi a haszna az iterateWhile-nak ebben a formában? Ha a táblázat üres, és nem tartalmaz elemeket, nincs üres <ul></ul> kerül kiírásra.

Ha a feltételt a nyitó {iterateWhile} címkében adjuk meg, a viselkedés megváltozik: a feltétel (és a következő elemre való átmenet) a belső ciklus elején történik, nem pedig a végén. Így míg a {iterateWhile} mindig feltétel nélkül lép be, addig a {iterateWhile $cond} csak akkor lép be, ha a $cond feltétel teljesül. És ezzel egyidejűleg a következő elemet írja be a $item.

Ez hasznos például olyan helyzetben, amikor az egyes kategóriák első elemét másképp akarjuk megjeleníteni, például így:

<h1>Apple</h1>
<ul>
	<li>Banana</li>
</ul>

<h1>PHP</h1>
<ul>
</ul>

<h1>Green</h1>
<ul>
	<li>Red</li>
	<li>Blue</li>
</ul>

Az eredeti kódot úgy módosítjuk, hogy először az első elemet rendereljük, majd a belső ciklusban {iterateWhile} rendereljük a többi elemet ugyanabból a kategóriából:

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

Egy cikluson belül több belső hurkot is létrehozhatunk, és akár egymásba is ágyazhatjuk őket. Így például az alkategóriákat is csoportosíthatjuk.

Tegyük fel, hogy a táblázatnak van egy másik oszlopa is: subcategoryId, és amellett, hogy minden kategória egy különálló <ul>, minden alkategória egy külön <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}
verzió: 3.0