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

Amikor sablonokban adatokkal dolgozol, gyakran találkozhatsz azzal az igénnyel, hogy azokat csoportosítani vagy bizonyos kritériumok szerint specifikusan megjeleníteni kell. A Latte erre a célra több erős eszközt is kínál.

A |group szűrő és függvény lehetővé teszi az adatok hatékony csoportosítását a megadott kritérium szerint, a |batch szűrő megkönnyíti az adatok fix méretű adagokra osztását, a {iterateWhile} tag pedig lehetőséget nyújt a ciklusok bonyolultabb vezérlésére feltételekkel. Mindegyik tag specifikus lehetőségeket kínál az adatokkal való munkához, így nélkülözhetetlen eszközökké válnak az információk dinamikus és strukturált megjelenítéséhez a Latte sablonokban.

A group szűrő és függvény

Képzelj el egy items adatbázistáblát, amely kategóriákba sorolt elemeket tartalmaz:

id categoryId name
1 1 Apple
2 1 Banana
3 2 PHP
4 3 Green
5 3 Red
6 3 Blue

Az összes elem egyszerű listája Latte sablonnal így nézne ki:

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

Ha azonban azt szeretnénk, hogy az elemek kategóriák szerint csoportosítva legyenek, úgy kell őket felosztani, hogy minden kategóriának saját listája legyen. Az eredménynek így kellene kinéznie:

<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 a categoryId-t adjuk meg, ami azt jelenti, hogy az elemek kisebb tömbökre lesznek osztva a $item->categoryId értéke alapján (ha $item tömb lenne, akkor $item['categoryId'] kerülne felhasználásra):

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

A szűrőt Latte-ban függvényként is lehet használni, ami alternatív szintaxist ad nekünk: {foreach group($items, categoryId) ...}.

Ha bonyolultabb kritériumok szerint szeretnéd csoportosítani az elemeket, használhatsz függvényt a szűrő paraméterében. Például az elemek csoportosítása a név hossza szerint így nézne ki:

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

Fontos megjegyezni, hogy a $categoryItems nem egy szokásos tömb, hanem egy objektum, amely iterátorként viselkedik. A csoport első eleméhez való hozzáféréshez használhatod a first() függvényt.

Ez a rugalmasság az adatok csoportosításában teszi a group-ot kivételesen hasznos eszközzé az adatok Latte sablonokban történő megjelenítéséhez.

Beágyazott ciklusok

Képzeljük el, hogy van egy adatbázistáblánk egy további subcategoryId oszloppal, amely az egyes elemek alkategóriáit definiálja. Szeretnénk minden fő kategóriát egy külön <ul> listában, és minden alkategóriát egy külön beágyazott <ol> listában megjeleníteni:

{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 Database-zel

Nézzük meg, hogyan lehet hatékonyan kihasználni az adatok csoportosítását a Nette Database-zel kombinálva. Tegyük fel, hogy az items táblával dolgozunk a bevezető példából, amely a categoryId oszlopon keresztül kapcsolódik ehhez a categories táblához:

categoryId name
1 Fruits
2 Languages
3 Colors

Az items tábla adatait a Nette Database Explorer segítségével olvassuk be a $items = $db->table('items') paranccsal. Ezen adatok feletti iteráció során nemcsak az olyan attribútumokhoz férhetünk hozzá, mint a $item->name és $item->categoryId, hanem a categories táblával való kapcsolatnak köszönhetően a kapcsolódó sorhoz is a $item->category-n keresztül. Ezen a kapcsolaton keresztül érdekes felhasználást demonstrálhatunk:

{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őt használjuk a kapcsolódó $item->category sor szerinti csoportosításhoz, nem csak a categoryId oszlop szerint. Ennek köszönhetően a kulcs változóban közvetlenül az adott kategória ActiveRow-ja van, ami lehetővé teszi számunkra, hogy közvetlenül kiírjuk a nevét a {$category->name} segítségével. Ez egy gyakorlati példa arra, hogyan teheti áttekinthetőbbé a csoportosítás a sablonokat és könnyítheti meg az adatokkal való munkát.

A |batch szűrő

A szűrő lehetővé teszi az elemek listájának felosztását előre meghatározott számú elemet tartalmazó csoportokra. 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 a vizuális elrendezés érdekében az oldalon.

Képzeljük el, hogy van egy listánk elemekkel, és szeretnénk őket listákban megjeleníteni, ahol mindegyik legfeljebb három elemet tartalmaz. 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 az $items lista kisebb csoportokra van osztva, ahol minden csoport ($batch) legfeljebb három elemet tartalmaz. Minden csoport ezután egy külön <ul> listában jelenik meg.

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 legyen ez a csoport kiegészítve. Ez ideális az elemek esztétikus igazításához ott, ahol egy hiányos sor rendezetlennek tűnhet.

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

A {iterateWhile} tag

Ugyanazokat a feladatokat, amelyeket a |group szűrővel oldottunk meg, megmutatjuk a {iterateWhile} tag használatával. A két megközelítés közötti fő különbség az, hogy a group először feldolgozza és csoportosítja az összes bemeneti adatot, míg a {iterateWhile} a ciklusok menetét vezérli feltételekkel, így az iteráció fokozatosan történik.

Először a kategóriákkal rendelkező táblázatot rajzoljuk ki 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 a listák kirajzolását minden kategóriához, addig a {iterateWhile} tag a belső részt jelöli, azaz az egyes elemeket. A záró tagben lévő feltétel azt mondja, hogy az ismétlés addig tart, amíg az aktuális és a következő elem ugyanabba a kategóriába tartozik ($iterator->nextValue a következő elem).

Ha a feltétel mindig teljesülne, akkor a belső ciklusban az összes 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>

Mire jó az iterateWhile ilyen használata? Ha a táblázat üres és nem tartalmaz elemeket, nem íródik ki üres <ul></ul>.

Ha a feltételt a nyitó {iterateWhile} tagben adjuk meg, akkor a viselkedés megváltozik: a feltétel (és a következő elemre való áttérés) már a belső ciklus elején végrehajtódik, nem a végén. Tehát míg a feltétel nélküli {iterateWhile}-be mindig belépünk, addig a {iterateWhile $cond}-be csak a $cond feltétel teljesülése esetén. És ezzel egyidejűleg a következő elem beíródik az $item-be.

Ez hasznos például abban a helyzetben, amikor minden kategória első elemét más módon szeretnénk kirajzolni, 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 kirajzoljuk az első elemet, majd a belső {iterateWhile} ciklusban kirajzoljuk 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ő ciklust is létrehozhatunk, és akár beágyazhatjuk őket. Így lehetne például alkategóriákat csoportosítani stb.

Tegyük fel, hogy a táblázatban van még egy subcategoryId oszlop, és amellett, hogy minden kategória külön <ul>-ben van, minden alkategória külön <ol>-ban:

{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