Nette Documentation Preview

syntax
Tutto quello che avreste sempre voluto sapere sul raggruppamento
****************************************************************

.[perex]
Lavorando con i dati nei template capita spesso di dover raggruppare gli elementi, dividerli in lotti oppure scorrerli in base a una condizione. Latte offre tre strumenti per farlo, ognuno adatto a una situazione leggermente diversa.

Il filtro `|group` raggruppa gli elementi secondo un criterio, il filtro `|batch` li divide in lotti di dimensione fissa e il tag `{iterateWhile}` scorre i dati passo dopo passo decidendo da sé quando interrompere il ciclo interno. Li esamineremo uno alla volta.


Filtro e funzione `group` .{data-version:3.0.16}
=================================================

Lo strumento si può usare in due forme: come filtro `$items|group: …` oppure come funzione `group($items, …)`. Dal punto di vista semantico sono equivalenti: scegliete in base alla leggibilità.

Immaginate una tabella di database `items`, i cui elementi appartengono a categorie diverse:

| id  | categoryId | name
|-----|------------|--------
| 1   | 1          | Mela
| 2   | 1          | Banana
| 3   | 2          | PHP
| 4   | 3          | Verde
| 5   | 3          | Rosso
| 6   | 3          | Blu

Un semplice elenco di tutti gli elementi con un template Latte avrebbe questo aspetto:

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

Se però volessimo gli elementi organizzati in gruppi per categoria, dobbiamo dividerli in modo che ogni categoria abbia il proprio elenco. Il risultato desiderato sarebbe questo:

```latte
<ul>
	<li>Mela</li>
	<li>Banana</li>
</ul>

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

<ul>
	<li>Verde</li>
	<li>Rosso</li>
	<li>Blu</li>
</ul>
```

Questo compito si risolve in modo semplice ed elegante con `|group`. Indichiamo `categoryId` come parametro: gli elementi verranno cioè divisi in array più piccoli in base al valore di `$item->categoryId` (se `$item` fosse un array, verrebbe usato `$item['categoryId']`):

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

Se volete raggruppare gli elementi secondo criteri più complessi, potete usare una funzione come parametro del filtro. La chiave di ogni gruppo sarà allora il valore restituito dalla funzione: per esempio, raggruppando per lunghezza del nome, sarà il numero di caratteri:

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

È importante notare che ogni gruppo (compreso `$categoryItems`) non è un array normale, ma un oggetto che si comporta come un iteratore: non potete quindi accedere agli elementi per indice, per esempio `$categoryItems[0]`. Potete però contare gli elementi con `count($categoryItems)` e, per accedere al primo elemento del gruppo, usare la funzione [`first()` |latte:functions#first()].

Questa flessibilità fa di `|group` uno strumento eccezionalmente utile per presentare i dati.


Cicli annidati
--------------

Immaginiamo che la nostra tabella di database abbia un'ulteriore colonna `subcategoryId`, che definisce le sottocategorie di ogni elemento. Vogliamo mostrare ogni categoria principale in un elenco `<ul>` separato e ogni sottocategoria di quella categoria principale in un elenco `<ol>` annidato a parte:

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


Insieme a Nette Database
------------------------

Mostriamo come usare efficacemente il raggruppamento dei dati insieme a Nette Database. Supponiamo di lavorare con la tabella `items` dell'esempio introduttivo, collegata tramite la colonna `categoryId` a questa tabella `categories`:

| categoryId | name       |
|------------|------------|
| 1          | Frutta     |
| 2          | Linguaggi  |
| 3          | Colori     |

Carichiamo i dati dalla tabella `items` con Nette Database Explorer usando il comando `$items = $db->table('items')`. Scorrendo questi dati possiamo accedere non solo ad attributi come `$item->name` e `$item->categoryId`, ma anche, grazie alla relazione con la tabella `categories`, alla riga collegata tramite `$item->category`. Questa relazione permette applicazioni interessanti:

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

In questo caso usiamo il filtro `|group` per raggruppare secondo la riga collegata `$item->category`, non solo secondo la colonna `categoryId`. Di conseguenza la chiave (`$category`) contiene direttamente l'oggetto `ActiveRow` di quella categoria, il che ci permette di mostrarne il nome con `{$category->name}` e di accedere a qualsiasi altra colonna senza eseguire una query separata su `categories`.


Filtro `|batch`
===============

Il filtro divide un elenco di elementi in lotti di dimensione fissa. Torna utile per i layout a griglia, per la disposizione in colonne o per qualsiasi tipo di raggruppamento visivo.

Immaginiamo di voler mostrare gli elementi in elenchi contenenti al massimo tre elementi ciascuno:

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

In questo esempio l'elenco `$items` viene diviso in gruppi più piccoli, dove ogni gruppo (`$batch`) contiene fino a tre elementi. Ogni lotto viene poi mostrato in un elenco `<ul>` separato.

Se l'ultimo gruppo non contiene abbastanza elementi per raggiungere il numero desiderato, il secondo parametro del filtro permette di definire con cosa completarlo. È l'ideale per allineare esteticamente gli elementi, quando una riga incompleta rischierebbe di apparire disordinata.

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


Tag `{iterateWhile}`
====================

Mostriamo gli stessi compiti risolti con il filtro `|group` usando il tag `{iterateWhile}`. La differenza principale tra i due approcci è che `|group` elabora e raggruppa prima tutti i dati in ingresso, mentre `{iterateWhile}` governa l'avanzamento del ciclo tramite una condizione e l'iterazione procede in sequenza.

Per prima cosa disegniamo la tabella con le categorie usando `{iterateWhile}`:

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

Mentre `{foreach}` delimita la parte esterna del ciclo, cioè il disegno degli elenchi di ciascuna categoria, il tag `{iterateWhile}` delimita la parte interna, cioè i singoli elementi. La condizione nel tag di chiusura dice che la ripetizione continuerà finché l'elemento corrente e il successivo appartengono alla stessa categoria (`$iterator->nextValue` è l'[elemento successivo |/tags#$iterator]; per l'ultimo elemento il ciclo interno termina perché non esiste un elemento successivo: Latte controlla `$iterator->hasNext()` prima di valutare la condizione, quindi non si arriva mai al confronto con `null`).

Se la condizione fosse sempre vera, tutti gli elementi verrebbero disegnati dentro il primo `<ul>`:

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

Il risultato sarebbe questo:

```latte
<ul>
	<li>Mela</li>
	<li>Banana</li>
	<li>PHP</li>
	<li>Verde</li>
	<li>Rosso</li>
	<li>Blu</li>
</ul>
```

Qual è il vantaggio di usare `{iterateWhile}` in questo modo? Poiché il `<ul>` si trova dentro il `{foreach}` esterno, quando l'ingresso è vuoto non viene disegnato proprio nulla: nessun `<ul></ul>` solitario. Senza `{iterateWhile}` dovreste gestire lo stesso caso con un `{if}` prima del tag di apertura oppure con `{foreachelse}`.

Se indichiamo la condizione nel tag di apertura `{iterateWhile}`, il comportamento cambia: la condizione (e il passaggio all'elemento successivo) viene eseguita all'inizio del ciclo interno, non alla fine. Mentre quindi in `{iterateWhile}` senza condizione si entra sempre, in `{iterateWhile $cond}` si entra solo quando la condizione `$cond` è soddisfatta. E allo stesso tempo in `$item` viene scritto l'elemento successivo.

Questo è utile nelle situazioni in cui vogliamo disegnare il primo elemento di ogni categoria diversamente dagli altri, per esempio così:

```latte
<h1>Mela</h1>
<ul>
	<li>Banana</li>
</ul>

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

<h1>Verde</h1>
<ul>
	<li>Rosso</li>
	<li>Blu</li>
</ul>
```

(Il `<ul></ul>` vuoto della categoria PHP illustra soltanto il meccanismo: in codice reale gestireste il disegno del `<ul>` con un `{if}`.)

Modifichiamo il codice originale in modo da disegnare prima l'elemento come intestazione e usare poi il ciclo interno `{iterateWhile}` per disegnare gli elementi successivi della stessa categoria come voci dell'elenco:

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

All'interno di un unico ciclo possiamo creare più cicli interni e perfino annidarli. In questo modo potete raggruppare su più livelli contemporaneamente, per esempio le sottocategorie sotto le categorie.

Supponiamo che la tabella abbia un'altra colonna `subcategoryId` e che, oltre a ogni categoria in un `<ul>` separato, ogni sottocategoria vada in un `<ol>` separato:

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

Tutto quello che avreste sempre voluto sapere sul raggruppamento

Lavorando con i dati nei template capita spesso di dover raggruppare gli elementi, dividerli in lotti oppure scorrerli in base a una condizione. Latte offre tre strumenti per farlo, ognuno adatto a una situazione leggermente diversa.

Il filtro |group raggruppa gli elementi secondo un criterio, il filtro |batch li divide in lotti di dimensione fissa e il tag {iterateWhile} scorre i dati passo dopo passo decidendo da sé quando interrompere il ciclo interno. Li esamineremo uno alla volta.

Filtro e funzione group

Lo strumento si può usare in due forme: come filtro $items|group: … oppure come funzione group($items, …). Dal punto di vista semantico sono equivalenti: scegliete in base alla leggibilità.

Immaginate una tabella di database items, i cui elementi appartengono a categorie diverse:

id categoryId name
1 1 Mela
2 1 Banana
3 2 PHP
4 3 Verde
5 3 Rosso
6 3 Blu

Un semplice elenco di tutti gli elementi con un template Latte avrebbe questo aspetto:

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

Se però volessimo gli elementi organizzati in gruppi per categoria, dobbiamo dividerli in modo che ogni categoria abbia il proprio elenco. Il risultato desiderato sarebbe questo:

<ul>
	<li>Mela</li>
	<li>Banana</li>
</ul>

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

<ul>
	<li>Verde</li>
	<li>Rosso</li>
	<li>Blu</li>
</ul>

Questo compito si risolve in modo semplice ed elegante con |group. Indichiamo categoryId come parametro: gli elementi verranno cioè divisi in array più piccoli in base al valore di $item->categoryId (se $item fosse un array, verrebbe usato $item['categoryId']):

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

Se volete raggruppare gli elementi secondo criteri più complessi, potete usare una funzione come parametro del filtro. La chiave di ogni gruppo sarà allora il valore restituito dalla funzione: per esempio, raggruppando per lunghezza del nome, sarà il numero di caratteri:

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

È importante notare che ogni gruppo (compreso $categoryItems) non è un array normale, ma un oggetto che si comporta come un iteratore: non potete quindi accedere agli elementi per indice, per esempio $categoryItems[0]. Potete però contare gli elementi con count($categoryItems) e, per accedere al primo elemento del gruppo, usare la funzione first().

Questa flessibilità fa di |group uno strumento eccezionalmente utile per presentare i dati.

Cicli annidati

Immaginiamo che la nostra tabella di database abbia un'ulteriore colonna subcategoryId, che definisce le sottocategorie di ogni elemento. Vogliamo mostrare ogni categoria principale in un elenco <ul> separato e ogni sottocategoria di quella categoria principale in un elenco <ol> annidato a parte:

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

Insieme a Nette Database

Mostriamo come usare efficacemente il raggruppamento dei dati insieme a Nette Database. Supponiamo di lavorare con la tabella items dell'esempio introduttivo, collegata tramite la colonna categoryId a questa tabella categories:

categoryId name
1 Frutta
2 Linguaggi
3 Colori

Carichiamo i dati dalla tabella items con Nette Database Explorer usando il comando $items = $db->table('items'). Scorrendo questi dati possiamo accedere non solo ad attributi come $item->name e $item->categoryId, ma anche, grazie alla relazione con la tabella categories, alla riga collegata tramite $item->category. Questa relazione permette applicazioni interessanti:

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

In questo caso usiamo il filtro |group per raggruppare secondo la riga collegata $item->category, non solo secondo la colonna categoryId. Di conseguenza la chiave ($category) contiene direttamente l'oggetto ActiveRow di quella categoria, il che ci permette di mostrarne il nome con {$category->name} e di accedere a qualsiasi altra colonna senza eseguire una query separata su categories.

Filtro |batch

Il filtro divide un elenco di elementi in lotti di dimensione fissa. Torna utile per i layout a griglia, per la disposizione in colonne o per qualsiasi tipo di raggruppamento visivo.

Immaginiamo di voler mostrare gli elementi in elenchi contenenti al massimo tre elementi ciascuno:

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

In questo esempio l'elenco $items viene diviso in gruppi più piccoli, dove ogni gruppo ($batch) contiene fino a tre elementi. Ogni lotto viene poi mostrato in un elenco <ul> separato.

Se l'ultimo gruppo non contiene abbastanza elementi per raggiungere il numero desiderato, il secondo parametro del filtro permette di definire con cosa completarlo. È l'ideale per allineare esteticamente gli elementi, quando una riga incompleta rischierebbe di apparire disordinata.

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

Tag {iterateWhile}

Mostriamo gli stessi compiti risolti con il filtro |group usando il tag {iterateWhile}. La differenza principale tra i due approcci è che |group elabora e raggruppa prima tutti i dati in ingresso, mentre {iterateWhile} governa l'avanzamento del ciclo tramite una condizione e l'iterazione procede in sequenza.

Per prima cosa disegniamo la tabella con le categorie usando {iterateWhile}:

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

Mentre {foreach} delimita la parte esterna del ciclo, cioè il disegno degli elenchi di ciascuna categoria, il tag {iterateWhile} delimita la parte interna, cioè i singoli elementi. La condizione nel tag di chiusura dice che la ripetizione continuerà finché l'elemento corrente e il successivo appartengono alla stessa categoria ($iterator->nextValue è l'elemento successivo; per l'ultimo elemento il ciclo interno termina perché non esiste un elemento successivo: Latte controlla $iterator->hasNext() prima di valutare la condizione, quindi non si arriva mai al confronto con null).

Se la condizione fosse sempre vera, tutti gli elementi verrebbero disegnati dentro il primo <ul>:

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

Il risultato sarebbe questo:

<ul>
	<li>Mela</li>
	<li>Banana</li>
	<li>PHP</li>
	<li>Verde</li>
	<li>Rosso</li>
	<li>Blu</li>
</ul>

Qual è il vantaggio di usare {iterateWhile} in questo modo? Poiché il <ul> si trova dentro il {foreach} esterno, quando l'ingresso è vuoto non viene disegnato proprio nulla: nessun <ul></ul> solitario. Senza {iterateWhile} dovreste gestire lo stesso caso con un {if} prima del tag di apertura oppure con {foreachelse}.

Se indichiamo la condizione nel tag di apertura {iterateWhile}, il comportamento cambia: la condizione (e il passaggio all'elemento successivo) viene eseguita all'inizio del ciclo interno, non alla fine. Mentre quindi in {iterateWhile} senza condizione si entra sempre, in {iterateWhile $cond} si entra solo quando la condizione $cond è soddisfatta. E allo stesso tempo in $item viene scritto l'elemento successivo.

Questo è utile nelle situazioni in cui vogliamo disegnare il primo elemento di ogni categoria diversamente dagli altri, per esempio così:

<h1>Mela</h1>
<ul>
	<li>Banana</li>
</ul>

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

<h1>Verde</h1>
<ul>
	<li>Rosso</li>
	<li>Blu</li>
</ul>

(Il <ul></ul> vuoto della categoria PHP illustra soltanto il meccanismo: in codice reale gestireste il disegno del <ul> con un {if}.)

Modifichiamo il codice originale in modo da disegnare prima l'elemento come intestazione e usare poi il ciclo interno {iterateWhile} per disegnare gli elementi successivi della stessa categoria come voci dell'elenco:

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

All'interno di un unico ciclo possiamo creare più cicli interni e perfino annidarli. In questo modo potete raggruppare su più livelli contemporaneamente, per esempio le sottocategorie sotto le categorie.

Supponiamo che la tabella abbia un'altra colonna subcategoryId e che, oltre a ogni categoria in un <ul> separato, ogni sottocategoria vada in un <ol> separato:

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