I have recently been working on a project where the majority of the front end is rendered via Template Toolkit (TT), a popular templating engine for Perl (and for Python). This article looks at the `FOREACH` directive and a couple of common markup patterns.
Free Loop Meta Data
Template Toolkit’s FOREACH
directive works by creating and querying a Template::Iterator
object. This is different to the plain Perl foreach
loop control. The Template::Iterator
object provides a bunch of useful loop meta information for free, accessible via the loop
keyword. Being aware of that difference is the main takeaway point from this article.
Commonly used attributes include:
first()
- Boolean: Is this the first iteration?last()
- Boolean: Is this the last iteration?parity()
- Alternatives between stringseven
andodd
.index()
- Get the current loop iteration index. Note this is from 0 tomax()
, so add one for a row count or similar.size()
- Number of items in the set.max()
- Maximum index number. This issize() - 1
.
There are other useful attributes available, but those listed above provide lots of utility for common markup patterns. They also appear to be somewhat unknown, as I see code that re-implements similar features, presumably because the developer is unaware of the attributes already available to them.
Two Examples
Example 1: Is this the last iteration?
Let’s say we want to add a class to the last item in a list to style it differently. If we were keeping track of this ourselves we might have some code similar to the following:
[%- SET thing_counter = 1 -%]
<ul>
[%- FOREACH thing IN wish_list -%]
[%- IF (thing_counter < wish_list.size) -%]
<li>[%- thing -%]</li>
[%- ELSE -%]
<li class="last-item-with-border">[%- thing -%]</li>
[%- END -%]
[%- thing_counter += 1 -%]
[%- END -%]
</ul>
If we know about loop.last
we can write something much cleaner:
<ul>
[%- FOREACH thing IN wish_list -%]
[%- UNLESS loop.last -%]
<li>[%- thing -%]</li>
[%- ELSE -%]
<li class="last-item-with-border">[%- thing -%]</li>
[%- END -%]
[%- END -%]
</ul>
We achieve the same functionality without any manual effort spent keeping track of where we are in the loop.
Example 2: Stripe Table Rows
A common visual change for unwieldly tables, we want to add alternate between two CSS classes to style the rows with different background colours. We could track this ourselves:
<table>
[%- SET flip = 0 -%]
[%- FOREACH project IN my_garden_projects -%]
<tr class="row-[%- flip ? 'even' : 'odd' -%]">
<td>[%- project.name -%]</td>
<td>[%- project.description -%]</td>
</tr>
[%- flip = !flip -%]
[%- END -%]
</table>
Or if we know about loop.parity
we get this for free:
<table>
[%- FOREACH project IN my_garden_projects -%]
<tr class="row-[%- loop.parity -%]">
<td>[%- project.name -%]</td>
<td>[%- project.description -%]</td>
</tr>
[%- END -%]
</table>
loop.parity
gives us a ready-to-use string, which is ideal to form part of a CSS class in this context.
FOREACH - What happens afterwards?
After Template Toolkit has iterated through the loop, the loop
variable stays in
scope. This is perhaps unexpected, and will overwrite any other variable you were using that happened to be called loop
.
This could be useful, as you have ready access to the loop meta data still, although I’m yet to require the loop meta data after iterating through the data in a TT template, so cannot immediately think of a non-contrived example.
If the loop
variable remaining in scope causes problems, or you want to rely on this functionality and are nesting loops, you can rename the loop variable (see the end of the FOREACH docs here).
Summary
There is a wider development tip here, which is to be aware of software modules that are easy to use. There is no reason such modules shouldn’t be easy to use, but be aware when you start to branch beyond the basic uses. At that point it is worth reading the documentation, or at least scanning the table of contents to be more aware of the structure of the library. Doing so for TT reveals all sort of good stuff: Filters, Plugins and Iterators to name a few.