Todo lo que siempre quiso saber sobre la agrupación
Al trabajar con datos en plantillas, a menudo se encuentra con la necesidad de agruparlos o mostrarlos específicamente según ciertos criterios. Para ello, Latte ofrece varias herramientas potentes.
El filtro y la función |group
permiten agrupar eficazmente los datos en función de criterios especificados,
mientras que el filtro |batch
facilita la división de los datos en lotes fijos y la etiqueta
{iterateWhile}
ofrece la posibilidad de un control de ciclos más complejo con condiciones. Cada una de estas
etiquetas ofrece opciones específicas para trabajar con los datos, lo que las convierte en herramientas indispensables para la
visualización dinámica y estructurada de la información en las plantillas Latte.
Filtro y función group
Imagine una tabla de base de datos items
con artículos divididos en categorías:
id | categoryId | name |
---|---|---|
1 Manzana | ||
2 1 Plátano | ||
3. 2. PHP | ||
4 3 Verde | ||
5. 3. Rojo | ||
6 3 Azul |
Una lista simple de todos los elementos utilizando una plantilla Latte tendría este aspecto:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Sin embargo, si quisiéramos que los artículos estuvieran organizados en grupos por categorías, tendríamos que dividirlos de forma que cada categoría tuviera su propia lista. El resultado sería el siguiente
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
La tarea puede resolverse fácil y elegantemente utilizando |group
. Especificamos categoryId
como
parámetro, lo que significa que los elementos se dividirán en matrices más pequeñas en función del valor de
$item->categoryId
(si $item
fuera una matriz, utilizaríamos $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
El filtro también se puede utilizar como una función en Latte, lo que nos da una sintaxis alternativa:
{foreach group($items, categoryId) ...}
.
Si desea agrupar elementos según criterios más complejos, puede utilizar una función en el parámetro filtro. Por ejemplo, agrupar elementos por la longitud de su nombre tendría este aspecto:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
Es importante tener en cuenta que $categoryItems
no es una matriz normal, sino un objeto que se comporta como un
iterador. Para acceder al primer elemento del grupo, puede utilizar la función first()
para acceder al primer elemento del grupo.
Esta flexibilidad en la agrupación de datos hace de group
una herramienta excepcionalmente útil para presentar
datos en plantillas Latte.
Bucles anidados
Supongamos que tenemos una tabla de base de datos con otra columna subcategoryId
que define subcategorías para
cada artículo. Queremos mostrar cada categoría principal en una lista <ul>
y cada subcategoría en una lista
anidada separada <ol>
anidada:
{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}
Conexión con la base de datos Nette
Veamos cómo utilizar eficazmente la agrupación de datos en combinación con Nette Database. Supongamos que estamos trabajando
con la tabla items
del ejemplo inicial, que está conectada a través de la columna categoryId
a esta
tabla categories
:
categoryId | name |
---|---|
1 Frutas | |
2 Idiomas | |
3 Colores |
Cargamos los datos de la tabla items
utilizando el comando de Nette Database Explorer
$items = $db->table('items')
. Durante la iteración sobre estos datos, tenemos la oportunidad no sólo de acceder
a atributos como $item->name
y $item->categoryId
, sino gracias a la conexión con la tabla
categories
, también a la fila relacionada en ella a través de $item->category
. Esta conexión puede
demostrar usos interesantes:
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
En este caso, utilizamos el filtro |group
para agrupar por la fila conectada $item->category
, no
sólo por la columna categoryId
. Esto nos da la ActiveRow
de la categoría dada en la clave variable,
permitiéndonos mostrar directamente su nombre usando {$category->name}
. Este es un ejemplo práctico de cómo la
agrupación puede simplificar las plantillas y facilitar el manejo de los datos.
Filtro |batch
El filtro permite dividir una lista de elementos en grupos con un número predeterminado de elementos. Este filtro es ideal para situaciones en las que desee presentar los datos en varios grupos más pequeños, por ejemplo, para una mayor claridad u organización visual en la página.
Imaginemos que tenemos una lista de elementos y queremos mostrarlos en listas, cada una de las cuales contiene un máximo de
tres elementos. Utilizar el filtro |batch
es muy práctico en un caso así:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
En este ejemplo, la lista $items
se divide en grupos más pequeños, cada uno de los cuales ($batch
)
contiene un máximo de tres elementos. Cada grupo se muestra en una lista <ul>
separada.
Si el último grupo no contiene suficientes elementos para alcanzar el número deseado, el segundo parámetro del filtro permite definir con qué se completará este grupo. Esto es ideal para alinear estéticamente elementos en los que una fila incompleta podría parecer desordenada.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Etiqueta {iterateWhile}
Demostraremos las mismas tareas que abordamos con el filtro |group
utilizando la etiqueta
{iterateWhile}
. La principal diferencia entre los dos enfoques es que group
primero procesa y agrupa
todos los datos de entrada, mientras que {iterateWhile}
controla el progreso de los ciclos con condiciones, por lo
que la iteración se produce secuencialmente.
En primer lugar, dibujamos una tabla con categorías utilizando iterateWhile:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
Mientras que {foreach}
marca la parte exterior del ciclo, es decir, dibujar listas para cada categoría, la
etiqueta {iterateWhile}
marca la parte interior, es decir, los elementos individuales. La condición en la etiqueta
final dice que la repetición continuará mientras el elemento actual y el siguiente pertenezcan a la misma categoría
($iterator->nextValue
es el siguiente elemento).
Si la condición se cumpliera siempre, todos los elementos se dibujarían en el ciclo interior:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
El resultado tendrá este aspecto:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
¿Para qué sirve iterateWhile de esta manera? Cuando la tabla está vacía y no contiene elementos, no se imprime ningún
empty <ul></ul>
se imprime.
Si especificamos la condición en la etiqueta de apertura {iterateWhile}
, el comportamiento cambia: la condición
(y la transición al siguiente elemento) se realiza al principio del ciclo interno, no al final. Así, mientras que siempre se
entra en {iterateWhile}
sin condiciones, sólo se entra en {iterateWhile $cond}
cuando se cumple la
condición $cond
. Y al mismo tiempo, el siguiente elemento se escribe en $item
.
Esto es útil, por ejemplo, en una situación en la que queremos renderizar el primer elemento de cada categoría de forma diferente, así:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
Modificamos el código original para que primero rendericemos el primer elemento y luego en el ciclo interno
{iterateWhile}
rendericemos los demás elementos de la misma categoría:
{foreach $items as $item}
<h1>{$item->name}</h1>
<ul>
{iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
Dentro de un mismo ciclo, podemos crear varios bucles internos e incluso anidarlos. De esta forma, se podrían agrupar subcategorías, por ejemplo.
Supongamos que la tabla tiene otra columna subcategoryId
, y además de que cada categoría está en una separada
<ul>
cada subcategoría en un <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}