Latte 2.10 a novinka {iterateWhile}
- David Grudl
- Nette Core | 8218
Přidal jsem do Latte značku s pracovním názvem
{iterateWhile}
(původní název byl innerForeach
,
post jsem pak upravil). Hodí se na různé chytrystyky ve foreach cyklech.
Máme takovouto tabulku položek:
id | catId | name |
---|---|---|
1 | 1 | Lorem |
2 | 1 | ipsum |
3 | 2 | dolor |
4 | 3 | sit |
5 | 3 | amet |
6 | 3 | consectetuer |
Budu je vykreslovat v cyklu foreach, ale chtěl bych, aby každá kategorie byla v samostatné tabulce.
Tohle se dá řešit v různých šablonovacích systémem (dosaďte si jakýkoliv) zhruba tímto způsobem:
{var $prevCatId = null}
{foreach $items as $item}
{if $item->catId !== $prevCatId}
{* uzavřeme minulou kategorii *}
{if $prevCatId !== null}
</tbody>
</table>
{/if}
{* otevřeme novou kategorii *}
{do $prevCatId = $item->catId}
<table>
<thead>...</thead>
<tbody>
{/if}
<tr>
<td>{$item->name}
</tr>
{/foreach}
{if $prevCatId !== null} {* uzavřeme poslední kategorii *}
</tbody>
</table>
{/if}
</table>
Latte má výhodu v tom, že jeho $iterator
zná následující
položku, takže se dá odstranit duplicitní uzavírání tabulky a výsledek
vypadá srozumitelněji.
{var $prevCatId = null}
{foreach $items as $item}
{* otevřeme novou kategorii *}
{if $item->catId !== $prevCatId}
{do $prevCatId = $item->catId}
<table>
<thead>...</thead>
<tbody>
{/if}
<tr>
<td>{$item->name}
</tr>
{* uzavřeme minulou kategorii *}
{if !$iterator->hasNext() || $iterator->nextValue->catId !== $item->catId}
</tbody>
</table>
{/if}
{/foreach}
Ale stále nevypadá tak, abych s tím byl spokojen :)
Vzniká tu nerovnovážná situace, kdy se HTML značky otevírají v bloku
podmínky {if}
, ale zavírají se v jiném bloku. Kvůli tomu
nepůjde ani ve značce <table>
používat n:atributy.
Celkově to nepůsobí moc blbuvzdorně.
{iterateWhile}
Pomocí iterateWhile můžeme nechat iteraci probíhat po určitou dobu (např. dokud je stejná kategorie) ve vnitřním cyklu:
{foreach $items as $item}
{* otevřeme novou kategorii *}
<table>
<thead>...</thead>
<tbody>
{iterateWhile}
<tr>
<td>{$item->name}
</tr>
{/iterateWhile $iterator->nextValue->catId === $item->catId}
{* uzavřeme minulou kategorii *}
</tbody>
</table>
{/foreach}
Nepotřebujeme žádné pomocné proměnné jako $prevCatId
,
není tu nerovnováha v podobě otevíracích značek v jiném bloku než jsou
uzavírací, hezky sedí odsazení, je to blbuvzdorné.
Samozřejmě je potřeba se naučit tento zápis číst, bez toho může vypadat komplikovaně. Možná by pomohl i lepší název než iterateWhile, napadá vás?
Pokud uvedeme podmínku v otevírací značce {iterateWhile}
,
tak se chování mění obdobně jako v cyklech do {} while
a
while {}
. Tedy podmínka (a přechod na další prvek) se vykoná
na začátku inner cyklu. Zatímco do {iterateWhile}
se vstoupí
vždy, do {iterateWhile $cond}
jen při splnění podmínky a
$item
se mění.
Takže v tomto příkladě se první položka kategorie vykreslí uvnitř
<thead>
a další až v <tbody>
:
{foreach $items as $item}
{* otevřeme novou kategorii *}
<table>
<thead>
<tr>
<td>{$item->name}
</tr>
</thead>
<tbody>
{* pokracujeme následujícím prvek pokud je ve stejné kategorii *}
{iterateWhile $iterator->nextValue->catId === $item->catId}
<tr>
<td>{$item->name}
</tr>
{/iterateWhile}
{* uzavřeme minulou kategorii *}
</tbody>
</table>
{/foreach}
Aby se v případě jediného prvku v kategorii nevypsalo prázdné
<tbody></tbody>
, lze mu doplnit podmínku do
n:if
.
Také je možné podmínku vůbec neuvádět. Začíná se tedy značkou
{foreach}
, pak celý cyklus proběhne v
{iterateWhile} ... {/iterateWhile}
, pak se pokračuje k
{/foreach}
a konec.
{foreach $items as $item}
<table>
<thead>...{$item->name}...</thead>
<tbody>
{iterateWhile}
<tr>
<td>{$item->name}
</tr>
{/iterateWhile}
</tbody>
</table>
{/foreach}
Jak se to liší od toho, že bychom použili uvnitř rovnou
{foreach}
? Jednak tím, že pokud je pole $items
prázdné, nevykreslí se tabulka. A dále $item
s prvním prvek
máme k dispozici už dříve, viz <thead>
Vnořené smyčky
V rámci jednoho cyklu můžeme vytvářet více inner-smyček a dokonce je zanořovat. Takže by se daly ohraničit třeba podkategorie atd.
Představme si, že v tabulce je ještě sloupec subCatId
.
Každá kategorie bude v samostatném <table>
, každá
podkategorie v samostatném <tbody>
:
{foreach $items as $item}
<table>
{iterateWhile}
<tbody>
{iterateWhile}
<tr>
<td>{$item->name}
</tr>
{/iterateWhile $iterator->nextValue->subCatId === $item->subCatId}
</tbody>
{/iterateWhile $iterator->nextValue->catId === $item->catId}
</table>
{/foreach}
Filtr |batch
Seskupování lineárních položek řeší i filtr batch
,
který je spojuje po určitém počtu:
<table>
{foreach ($items|batch:3) as $row}
<tr>
{foreach $row as $column}
<td>{$column}</td>
{/foreach}
</tr>
{/foreach}
</table>
Lze jej nahradit za iterateWhile tímto způsobem:
<table>
{foreach $items as $item}
<tr>
{iterateWhile}
<td>{$item}</td>
{/iterateWhile $iterator->counter0 % 3}
</tr>
{/foreach}
</table>
Rozdíl je opět v tom, že $item
s prvním prvek máme
k dispozici vně vnitřního cyklu, na rozdíl od $column
.
- Gappa
- Nette Blogger | 207
Přidal jsem do Latte značku s pracovním názvem
{innerForeach}
. Hodí se na různé chytrystyky ve foreach cyklech.
Čistě co se názvu týče, tak ten pracovní by se „třískal“ s
n:inner-foreach
(atributovou variantou zápisu „obyčejného“
foreach).
Návrhy:
- subForeach
- partialForeach
- localForeach
- David Grudl
- Nette Core | 8218
V názvu by spíš foreach byt vůbec nemělo, protože to „each“ není pravda, to spíš forsome :-)
- Marek Bartoš
- Nette Blogger | 1260
Kolega kodér tohle prý už v pár desítkách případů potřeboval a z navrhnutých variant se mu zdá nejjasnější nestedLoop. Ideální název
- David Grudl
- Nette Core | 8218
filsedla napsal(a):
Ve všech příkladech je chyba, je tam vždy
</table>
navíc, ne?
V jednom nebylo :-) Díky, opravil jsem.
- David Grudl
- Nette Core | 8218
Přemýšlím, do jaké míry brainstorming ovlivňuje to, že většina příkladů se týká seskupování. Takhle to používám, ale možná je užití mnohem širší, jen mě teď nenapadají příklady.
- GEpic
- Člen | 566
Mohlo by to řešit i případ, kdy chcete vypisovat obrázky od shora dolů
do sloupců a né do řádků.
Výsledkem je poté masonry grid:
{varType Nette\Database\Table\ActiveRow[] $images}
{default int $columns = 4}
{* foreach + nestedLoop *}
<div class="row row-cols-{$columns}">
{foreach $images as $image}
<div>
{nestedLoop}
<img src="{$image->path}" alt="{$image->name}" class="img-fluid">
{/nestedLoop $iterator->counter % ceil(count($images) / $columns) === 0)}
</div>
{/foreach}
</div>
I přesto, že podmínka je matematicky trošku složitější (a snad jsem jí zapsal správně), výsledný kód je daleko přehlednější.
Editoval GEpic (18. 1. 2021 16:16)
- David Grudl
- Nette Core | 8218
GEpic napsal(a):
I přesto, že podmínka je matematicky trošku složitější (a snad jsem jí zapsal správně), výsledný kód je daleko přehlednější.
Mělo by tam být spíš $iterator->counter0 % xxx !== 0
.
Na tu dělitelnost by se tam mohla dát i nějaká srozumitelnější
funkci. Něco jako {nestedLoop $iterator->step(xxx)}
nebo tak.
- GEpic
- Člen | 566
@DavidGrudl smazal jsem ten příklad, jak bych to řešil teď –
byl totiž špatně. A upřímně ať přemýšlím, jak přemýšlím tak
nevím, jak bych ten případ bez nestedLoop
vyřešil bez
využítí funkce jako např. array_chunk
Editoval GEpic (18. 1. 2021 16:18)
- dakur
- Člen | 493
filsedla napsal(a):
Proč se to vlastně nejmenuje do…while (nebo něco od toho odvozeného)?
Mám spíš na mysli se zeptat, v jakých konkrétních aspektech se to liší od té klasické konstrukce do-while?
Mě napadlo totéž – funguje to jako (do) while
, vypadá to
jako (do) while
, proč ne (do) while
? 🙂
- David Matějka
- Moderator | 6445
v jakých konkrétních aspektech se to liší od té klasické konstrukce do-while?
samo to posouvá iterátor.
Tak co iterateWhile nebo prostě jen iterate?
- David Grudl
- Nette Core | 8218
iterateWhile je dobrý, proože tam je jasně naznačená ta existence podmínky