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}