20 Jul 2017

Template Toolkit – FOREACH Directive

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 strings even and odd.
  • index() - Get the current loop iteration index. Note this is from 0 to max(), so add one for a row count or similar.
  • size() - Number of items in the set.
  • max() - Maximum index number. This is size() - 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.

Dev Perl
Back to posts