Tutto quello che avete sempre voluto sapere sul raggruppamento
Quando si lavora con i dati nei modelli, spesso si ha la necessità di raggrupparli o di visualizzarli in modo specifico in base a determinati criteri. A questo scopo, Latte offre diversi strumenti potenti.
Il filtro e la funzione |group
consentono di raggruppare efficacemente i dati in base a criteri specifici, mentre
il filtro |batch
facilita la suddivisione dei dati in lotti fissi e il tag {iterateWhile}
offre la
possibilità di un controllo del ciclo più complesso con condizioni. Ognuno di questi tag offre opzioni specifiche per lavorare
con i dati, rendendoli strumenti indispensabili per la visualizzazione dinamica e strutturata delle informazioni nei
modelli Latte.
Filtro e funzione group
Immaginate una tabella del database items
con articoli suddivisi in categorie:
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 utilizzando un modello Latte si presenterebbe come segue:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Tuttavia, se vogliamo che gli articoli siano organizzati in gruppi per categoria, dobbiamo dividerli in modo che ogni categoria abbia un proprio elenco. Il risultato sarebbe quindi simile a questo:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Il compito può essere risolto in modo semplice ed elegante utilizzando |group
. Specifichiamo
categoryId
come parametro, il che significa che gli elementi saranno suddivisi in array più piccoli in base al
valore di $item->categoryId
(se $item
fosse un array, useremmo $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Il filtro può essere usato anche come funzione in Latte, con una sintassi alternativa:
{foreach group($items, categoryId) ...}
.
Se si desidera raggruppare gli elementi in base a criteri più complessi, è possibile utilizzare una funzione nel parametro del filtro. Ad esempio, il raggruppamento degli elementi in base alla lunghezza del loro nome può avvenire in questo modo:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
È importante notare che $categoryItems
non è un normale array, ma un oggetto che si comporta come un iteratore.
Per accedere al primo elemento del gruppo, si può usare la funzione first()
la funzione.
Questa flessibilità nel raggruppamento dei dati rende group
uno strumento eccezionalmente utile per la
presentazione dei dati nei modelli Latte.
Cicli annidati
Supponiamo di avere una tabella di database con un'altra colonna subcategoryId
che definisce le sottocategorie per
ogni articolo. Vogliamo visualizzare ogni categoria principale in un elenco separato e ogni <ul>
e ogni
sottocategoria in un elenco annidato separato <ol>
separato:
{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}
Connessione con il database Nette
Mostriamo come utilizzare efficacemente il raggruppamento dei dati in combinazione con Nette Database. Supponiamo di lavorare
con la tabella items
dell'esempio iniziale, che è collegata attraverso la colonna categoryId
a questa
tabella categories
:
categoryId | name |
---|---|
1 | Frutta |
2 | Lingue |
3 | Colori |
Carichiamo i dati dalla tabella items
utilizzando il comando $items = $db->table('items')
di
Nette Database Explorer. Durante l'iterazione su questi dati, abbiamo la possibilità non solo di accedere ad attributi come
$item->name
e $item->categoryId
, ma, grazie alla connessione con la tabella
categories
, anche alla relativa riga in essa contenuta tramite $item->category
. Questa connessione
può rivelarsi interessante:
{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, utilizziamo il filtro |group
per raggruppare in base alla riga collegata
$item->category
, non solo in base alla colonna categoryId
. In questo modo si ottiene il
ActiveRow
della categoria data nella chiave variabile, consentendo di visualizzarne direttamente il nome utilizzando
{$category->name}
. Questo è un esempio pratico di come il raggruppamento possa semplificare i modelli e
facilitare la gestione dei dati.
Filtro |batch
Il filtro consente di suddividere un elenco di elementi in gruppi con un numero predeterminato di elementi. Questo filtro è ideale per le situazioni in cui si desidera presentare i dati in diversi gruppi più piccoli, ad esempio per una migliore chiarezza o organizzazione visiva della pagina.
Immaginiamo di avere un elenco di elementi e di volerli visualizzare in elenchi, ciascuno contenente un massimo di tre
elementi. L'uso del filtro |batch
è molto pratico in questo caso:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
In questo esempio, l'elenco $items
viene suddiviso in gruppi più piccoli, ciascuno dei quali
($batch
) contiene un massimo di tre elementi. Ogni gruppo viene quindi visualizzato in un elenco
<ul>
elenco separato.
Se l'ultimo gruppo non contiene abbastanza elementi per raggiungere il numero desiderato, il secondo parametro del filtro consente di definire con cosa verrà integrato questo gruppo. Questo è ideale per allineare esteticamente gli elementi in cui una riga incompleta potrebbe sembrare disordinata.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Tag {iterateWhile}
Dimostreremo gli stessi compiti che abbiamo affrontato con il filtro |group
utilizzando il tag
{iterateWhile}
. La differenza principale tra i due approcci è che group
elabora e raggruppa prima
tutti i dati in ingresso, mentre {iterateWhile}
controlla l'avanzamento dei cicli con le condizioni, quindi
l'iterazione avviene in modo sequenziale.
Per prima cosa, disegniamo una 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}
segna la parte esterna del ciclo, cioè il disegno di liste per ogni categoria, il tag
{iterateWhile}
segna la parte interna, cioè i singoli elementi. La condizione nel tag end dice che la ripetizione
continuerà finché l'elemento corrente e quello successivo appartengono alla stessa categoria
($iterator->nextValue
è l'elemento successivo).
Se la condizione fosse sempre soddisfatta, tutti gli elementi verrebbero disegnati nel ciclo interno:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
Il risultato sarà simile a questo:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
A cosa serve iterareWhile in questo modo? Quando la tabella è vuota e non contiene elementi, non viene stampato alcun
elemento vuoto. <ul></ul>
viene stampato.
Se si specifica la condizione nel tag di apertura {iterateWhile}
, il comportamento cambia: la condizione (e la
transizione all'elemento successivo) viene eseguita all'inizio del ciclo interno, non alla fine. Così, mentre si entra sempre in
{iterateWhile}
senza condizioni, si entra in {iterateWhile $cond}
solo quando la condizione
$cond
è soddisfatta. Allo stesso tempo, l'elemento successivo viene scritto in $item
.
Questo è utile, ad esempio, in una situazione in cui si vuole rendere il primo elemento di ogni categoria in modo diverso, come in questo caso:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
Modifichiamo il codice originale in modo da rendere prima il primo elemento e poi nel ciclo interno {iterateWhile}
rendiamo gli altri elementi della stessa categoria:
{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 ciclo, possiamo creare più cicli interni e persino annidarli. In questo modo, si possono raggruppare le sottocategorie, per esempio.
Supponiamo che la tabella abbia un'altra colonna subcategoryId
, e che oltre a ciascuna categoria sia in una
sezione separata <ul>
, ogni sottocategoria in un ciclo separato <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}