Iterating in a Template
Let’s revisit part of the non-efficient HTML from the introduction, where we hard-coded coffee options in a list.
<ol>
<li>French Roast</li>
<li>Espresso</li>
<li>Kopi Luwak</li>
<li>Instant</li>
</ol>
If we want to add, remove, or edit the list items, we need to go in and change the individual tags, which is a poor use of our time. Fortunately, there is a way to streamline the process.
In Java, we use for/each
loops to iterate through the items in a data collection.
for (Type item : collectionName) {
// Code to repeat...
}
Thymeleaf gives us a similar functionality to use in our templates, but we need to learn a different syntax.
th:each
The general syntax for iterating through a collection in Thymeleaf is:
th:each = "variableName : ${collectionName}"
- The
th:each
statement gets placed INSIDE an HTML tag. collectionName
represents an ArrayList, HashMap, or other collection.variableName
represents an individual item or element within the collection.- The
${}
specifies the data provided by the controller.
We can think of a th:each
statement as saying, “For each item within ‘collectionName’, repeat this HTML tag, but use the current value of ‘variableName’.”
Let’s apply th:each
to the HTML example above. Assume that we store each of the coffee names as a string in an ArrayList called coffeeOptions
.
<ol>
<li th:each="item : ${coffeeOptions}" th:text="${item}"></li>
</ol>
Some points to note:
coffeeOptions
is passed into the template by the controller.- In the first pass through the loop,
item
takes the value of the first coffee name in the ArrayList. th:text="${item}"
displays the value ofitem
in the view, so theli
element will show the first coffee name.- Each successive iteration,
item
takes the next value in the list, and Thymeleaf adds a new<li></li>
element to display that data.
Location Matters
th:each
creates one HTML tag for each item in a collection, and only one th:each
statement can be placed in a given tag. BE CAREFUL where you place it.
Consider the following code blocks:
Option #1:
<div>
<h3>Java Types</h3>
<ol>
<li th:each="item : ${coffeeOptions}" th:text="${item}"></li>
</ol>
</div>
Option #2:
<div>
<h3>Java Types</h3>
<ol th:each="item : ${coffeeOptions}">
<li th:text="${item}"></li>
</ol>
</div>
Option #3:
<div th:each="item : ${coffeeOptions}">
<h3>Java Types</h3>
<ol>
<li th:text="${item}"></li>
</ol>
</div>
Which option produces:
- One heading, 4 ordered lists, and 4 coffee names (each name labeled as “1”)?
- One heading, one ordered list, and 4 coffee names (with the names labeled 1, 2, 3…)?
- 4 headings, 4 ordered lists, and 4 coffee names (each name labeled as “1”)?
th:block
th:block
is a Thymeleaf element rather than an attribute, and it has the general syntax:
<th:block>
<!-- Your HTML code -->
</th:block>
th:block
allows you to apply the same Thymeleaf attribute, like th:if
or th:each
to a block of code. For example, assume you have an ArrayList of coffee objects, each of which contains a name
and description
field. If you want to display this information as name/description pairs, you might try:
<h3 th:each="coffee : ${coffeeOptions}" th:text="${coffee.name}">Coffee Name</h3>
<p th:each="coffee : ${coffeeOptions}" th:text="${coffee.description}">Info</p>
However, th:each
operates on and repeats a single HTML element. Instead of alternating name/description pairs, the view in this case displays h3
headings for all of the coffee names before rendering the p
elements for the descriptions.
To replicate the h3
and p
elements as pairs, you need to wrap them inside th:block
:
<th:block th:each="coffee : ${coffeeOptions}">
<h3 th:text="${coffee.name}">Coffee Name</h3>
<p th:text="${coffee.description}">Info</p>
</th:block>
Set up this way, the code gets interpreted as, “Repeat the following HTML elements once for each item in ‘coffeeOptions’.” Each iteration of the loop renders a heading/paragraph pair using a new coffee
object from coffeeOptions
.
Note: Unlike a standard HTML tag (like div
, ul
, p
, etc.), the th:block
element does NOT get rendered in the view.
To see an example for how to use th:block
and iteration to produce a table, check the Thymeleaf docs
.
Readability and Nested Loops
th:block
helps clarify your HTML code by placing related Thymeleaf attributes into separate tags.
This code generates multiple li
elements and text by putting two attributes inside of the tag:
<ol>
<li th:each="item : ${coffeeOptions}" th:text="${item}"></li>
</ol>
The code below accomplishes the same thing, but it separates the loop command from the th:text
command:
<ol>
<th:block th:each="item : ${coffeeOptions}">
<li th:text="${item}"></li>
</th:block>
</ol>
The second format more closely mimics the appearance of a Java for/each
loop, which improves the clarity of your code.
for (Coffee item : coffeeOptions) {
// Code using the "item" variable...
}
th:block
also allows you to set up nested loops:
Assume you have a collection of different coffee shop objects, and each object stores a name
and a list of coffeeOptions
. To display a list of lists:
<th:block th:each="shop : ${shops}"> <!-- Iterate through shops -->
Check Your Understanding
Use the three code samples listed in the location matters section to answer the following questions:
Which option produces one heading, 4 ordered lists, and 4 coffee names (each name labeled as “1”)?
- Option 1
- Option 2
- Option 3
Which option produces one heading, one ordered list, and 4 coffee names (with the names labeled 1, 2, 3…)?
- Option 1
- Option 2
- Option 3
Which option produces 4 headings, 4 ordered lists, and 4 coffee names (each name labeled as “1”)?
- Option 1
- Option 2
- Option 3