Table of contents6 iterations
We have currently discussed and created a homepage, a user profile page, and a page that allows users to subscribe to our newsletter ... but what about our products? To do this, we need a way to iterate through the elements in the collection.
6.1 Basics of Iteration
To display products on our page /WEB-INF/templates/product/list.html we use a table. Each of the products is displayed in a line (<td> element), so we create such a line and instruct Thymeleaf to repeat it once for each product.
The standard dialect offers a special attribute:
th: each .
')
Use th: eachFor the product list page, you need the controller method, which extracts the product list from the service level and adds it to the template context:
public void process( final HttpServletRequest request, final HttpServletResponse response, final ServletContext servletContext, final ITemplateEngine templateEngine) throws Exception { ProductService productService = new ProductService(); List<Product> allProducts = productService.findAll(); WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); ctx.setVariable("prods", allProducts); templateEngine.process("product/list", ctx, response.getWriter()); }
And then we will use
th: each in our template to iterate through the product list:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" media="all" href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" /> </head> <body> <h1>Product list</h1> <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> </tr> <tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> </table> <p> <a href="../home.html" th:href="@{/}">Return to home</a> </p> </body> </html>
The
prod attribute
: $ {prods} , which you see above, means "for each element, the result of executing
$ {prods} , repeat this pattern fragment using the current variable element called prod." Let's give the name of each of the things we see:
- We will call $ {prods} an iterated expression or an iterated variable
- We will call the prod variable iteration or just the variable iter
- Notice that the prod variable is bound to the <td> element, which means that it is available for internal tags, such as <td>
Iterable valuesThe java.util.List class is not the only one that can be used for iteration in Thymeleaf. There is a fairly large set of objects that are suitable for the
th: each attribute:
- Any object implementing java.util.Iterable
- Any object implementing java.util.Enumeration
- Any object that implements java.util.Iterator whose values ​​will be used as they are returned by the iterator, without the need to cache all values ​​in memory
- Any object that implements java.util.Map. When iterating maps, iter variables will have the class java.util.Map.Entry
- Any array
- Any other object will be processed as if it were a unique list containing the object itself.
6.2 Saving the iteration status
When using
th: each , Thymeleaf offers a mechanism useful for tracking the state of your iteration: a state variable.
State variables are defined within the
th: each attribute and contain the following data:
- The current iteration index starting at 0. This is the index property.
- Current iteration index starting from 1. This property is count
- The total number of elements in the iterated variable. This property is size.
- Variable iter for each iteration. This is the current property.
- Will the current iteration be even or odd? These are even / odd boolean properties.
- Is the current iteration first. This is the first boolean property.
- Is the current iteration last. This is the last property of the boolean
Let's see how we can use iterative properties:
<table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> </tr> <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> </table>
The state variable (
iterStat in this example) is defined in the
th: each attribute, writing its name after the iter variable itself, separated by a comma. Just as an iterative variable, the status variable is tied to a code fragment defined by a tag containing the
th: each attribute.
Let's look at the result of processing our template:
<!DOCTYPE html> <html> <head> <title>Good Thymes Virtual Grocery</title> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> <link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" /> </head> <body> <h1>Product list</h1> <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> </tr> <tr class="odd"> <td>Fresh Sweet Basil</td> <td>4.99</td> <td>yes</td> </tr> <tr> <td>Italian Tomato</td> <td>1.25</td> <td>no</td> </tr> <tr class="odd"> <td>Yellow Bell Pepper</td> <td>2.50</td> <td>yes</td> </tr> <tr> <td>Old Cheddar</td> <td>18.75</td> <td>yes</td> </tr> </table> <p> <a href="/gtvg/" shape="rect">Return to home</a> </p> </body> </html>
Note that our iteration
status variable works fine, setting an odd CSS class for odd lines only.
Unless you explicitly specify a status variable, Thymeleaf will always create it for you by means of the
Stat suffix to the name of the iteration variable:
<table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> </tr> <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> </table>
6.3 Optimization by lazy data extraction
Sometimes we may need to optimize the crawling of data collections (for example, from a database) so that these collections are retrieved only if they are actually used.
In fact, this is something that can be applied to any part of the data, but given the size that the collection can have in memory, retrieving collections to be repeated is the most common case for this scenario.
To support this mechanism, Thymeleaf offers a toolkit for
lazy loading of context variables. Context variables that implement the ILazyContextVariable interface, most likely by extending its default implementation of LazyContextVariable, will be resolved at the time of execution. For example:
context.setVariable( "users", new LazyContextVariable<List<User>>() { @Override protected List<User> loadValue() { return databaseRepository.findAllUsers(); } });
This variable can be used without knowing whether it is lazy, for example:
<ul> <li th:each="u : ${users}" th:text="${u.name}">user name</li> </ul>
But at the same time, it will never be initialized (the loadValue () method will never be called) if the condition is evaluated as false in the code, for example:
<ul th:if="${condition}"> <li th:each="u : ${users}" th:text="${u.name}">user name</li> </ul>