Everything You Always Wanted to Know About Grouping
When working with data in templates, you often encounter the need to group them or display them specifically according to certain criteria. For this purpose, Latte offers several powerful tools.
The filter and function |group
allow for efficient data grouping based on specified criteria, while the
|batch
filter facilitates splitting data into fixed batches and the {iterateWhile}
tag provides the
possibility of more complex cycle control with conditions. Each of these tags offers specific options for working with data,
making them indispensable tools for dynamic and structured display of information in Latte templates.
Filter and function group
Imagine a database table items
with items divided into categories:
id | categoryId | name |
---|---|---|
1 | 1 | Apple |
2 | 1 | Banana |
3 | 2 | PHP |
4 | 3 | Green |
5 | 3 | Red |
6 | 3 | Blue |
A simple list of all items using a Latte template would look like this:
However, if we wanted the items to be organized into groups by category, we need to divide them so that each category has its own list. The result would then look like this:
The task can be easily and elegantly solved using |group
. We specify categoryId
as the parameter,
meaning that the items will be divided into smaller arrays based on the $item->categoryId
value (if
$item
were an array, we would use $item['categoryId']
):
The filter can also be used as a function in Latte, giving us an alternative syntax:
{foreach group($items, categoryId) ...}
.
If you want to group items according to more complex criteria, you can use a function in the filter parameter. For example, grouping items by the length of their name would look like this:
It’s important to note that $categoryItems
is not a regular array, but an object that behaves like an iterator.
To access the first item in the group, you can use the first()
function.
This flexibility in data grouping makes group
an exceptionally useful tool for presenting data in Latte
templates.
Nested Loops
Let's say we have a database table with another column subcategoryId
that defines subcategories for each item. We
want to display each main category in a separate <ul>
list and each subcategory in a separate nested
<ol>
list:
Connection with Nette Database
Let's show how to effectively use data grouping in combination with Nette Database. Suppose we are working with the
items
table from the initial example, which is connected through the categoryId
column to this
categories
table:
categoryId | name |
---|---|
1 | Fruits |
2 | Languages |
3 | Colors |
We load data from the items
table using the Nette Database Explorer command
$items = $db->table('items')
. During the iteration over these data, we have the opportunity not only to access
attributes like $item->name
and $item->categoryId
, but thanks to the connection with the
categories
table, also to the related row in it via $item->category
. This connection can demonstrate
interesting uses:
In this case, we use the |group
filter to group by the connected row $item->category
, not just by
the categoryId
column. This gives us the ActiveRow
of the given category in the variable key, allowing
us to directly display its name using {$category->name}
. This is a practical example of how grouping can simplify
templates and facilitate data handling.
Filter |batch
The filter allows you to split a list of elements into groups with a predetermined number of elements. This filter is ideal for situations where you want to present data in several smaller groups, for example, for better clarity or visual organization on the page.
Imagine we have a list of items and want to display them in lists, each containing a maximum of three items. Using the
|batch
filter is very practical in such a case:
In this example, the list $items
is divided into smaller groups, each group ($batch
) containing up to
three items. Each group is then displayed in a separate <ul>
list.
If the last group does not contain enough elements to reach the desired number, the second parameter of the filter allows you to define what this group will be supplemented with. This is ideal for aesthetically aligning elements where an incomplete row might look disordered.
Tag {iterateWhile}
We will demonstrate the same tasks we addressed with the |group
filter using the {iterateWhile}
tag.
The main difference between the two approaches is that group
first processes and groups all input data, while
{iterateWhile}
controls the progress of cycles with conditions, so the iteration occurs sequentially.
First, we draw a table with categories using iterateWhile:
While {foreach}
marks the outer part of the cycle, i.e., drawing lists for each category, the
{iterateWhile}
tag marks the inner part, i.e., individual items. The condition in the end tag says that repetition
will continue as long as the current and next element belong to the same category ($iterator->nextValue
is next item).
If the condition were always met, all elements would be drawn in the inner cycle:
The result will look like this:
What is the use of iterateWhile in this way? When the table is empty and contains no elements, no empty
<ul></ul>
is printed.
If we specify the condition in the opening {iterateWhile}
tag, the behavior changes: the condition (and transition
to the next element) is performed at the beginning of the inner cycle, not at the end. Thus, while you always enter
{iterateWhile}
without conditions, you enter {iterateWhile $cond}
only when the condition
$cond
is met. And at the same time, the next element is written into $item
.
This is useful, for example, in a situation where we want to render the first element in each category differently, like this:
We modify the original code so that we first render the first item and then in the inner cycle {iterateWhile}
we
render the other items from the same category:
Within one cycle, we can create multiple inner loops and even nest them. This way, subcategories could be grouped, for example.
Suppose the table has another column subcategoryId
, and besides each category being in a separate
<ul>
, each subcategory in a separate <ol>
: