Free Trial

Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.

  • Create BookmarkCreate Bookmark
  • Create Note or TagCreate Note or Tag
  • PrintPrint

Loops

It is very common to want to repeat parts of a template. You might want to produce similar output for every item in a list, or you might want to repeat a piece of content a set number of times. The Template Toolkit provides two loop directives that deal with both of these situations—FOREACH (also spelled FOR) and WHILE.

Use FOREACH in cases where you know the size of the data set over which you are iterating, or in cases where you need access to loop metadata, such as the next or previous element, the index of the iteration, or the size of the data set. WHILE is useful for performing an action until a condition is true, for looping over a very large data set, or when termination of the loop depends on a condition external to the data set. Both directives are discussed in the sections that follow.

FOREACH

The FOREACH directive defines a block, up to the corresponding END tag, that is processed repeatedly for each item in a list. The basic syntax is:

[% FOREACH item IN list %]
   # content of block
[% END %]

You can also use = in place of IN if you find that more natural:

[% FOREACH item = list %]
   # content of block
[% END %]

FOREACH loops over each element in a list and creates an alias to the current item:

[% numbers = [ 1 .. 5 ] %]

[% FOREACH num IN numbers %]
    * [% num %]
[% END %]

In this example, numbers is an array of five elements, the numbers 1 through 5. In the FOREACH loop, these elements are assigned to num, one at a time, in the order that they occur in numbers:

* 1
* 2
* 3
* 4
* 5

Complex data

The elements of the array can be any kind of complex data:

[% fabfour = [
     {
       name       = "John Lennon"
       instrument = "guitar"
     }
     {
       name       = "Paul McCartney"
       instrument = "bass guitar"
     }
     {
       name       = "George Harrison"
       instrument = "lead guitar"
     }
     {
       name       = "Ringo Starr"
       instrument = "drums"
     }
   ]
%]
[% FOREACH beatle IN fabfour -%]
    [% beatle.name %] played [% beatle.instrument %].
[% END %]

The beatle variable is aliased to each hash in the fabfour list, and through it we can access the various elements:

John Lennon played guitar.
Paul McCartney played bass guitar.
George Harrison played lead guitar.
Ringo Starr played drums.

The original array is not modified, but the elements of the array can be modified within the FOREACH loop.

Importing hash array items

When the FOREACH directive is used without specifying a target variable, any iterated values that are hash references will be automatically imported:

[% FOREACH fabfour -%]
    [% name %] played [% instrument %].
[% END %]

This particular usage creates a localized variable context to prevent the imported hash keys from overwriting any existing variables. The imported definitions and any other variables defined in such a FOREACH loop will be lost at the end of the loop, when the previous context and variable values are restored.

Iterating over entries in a hash array

The FOREACH directive can also be used to iterate over the entries in a hash array. Each entry in the hash is returned in sorted order (based on the key) as a hash array containing “key” and “value” items.

[% users = {
     tom   = 'Thomas'
     dick  = 'Richard'
     larry = 'Lawrence'
   }
%]

[% FOREACH user IN users %]
   * [% user.key %] : [% user.value %]
[% END %]

The previous example generates the following output:

* dick : Richard    
* larry : Lawrence    
* tom : Thomas

To iterate over the keys of a hash, use the keys virtual method on the hash:

[% FOREACH key IN hash.keys %]
    * [% key %] : [% hash.$key %]
[% END %]

The loop iterator object

The underlying implementation of the FOREACH directive involves the creation of a special object called an iterator, which maintains metadata about the data set being processed. This object can be accessed within the body of the FOREACH using the special variable loop:

[% FOREACH item IN items %]
    [% IF loop.first %]
      <ul> 
    [% END %]
        <li>[% item %] ([% loop.count %] of [% loop.size %])</li>
    [% IF loop.last %]
      </ul>
    [% END %]
[% END %]

The iterator defines several useful methods that return information about the current loop:

size

Returns the size of the data set, or returns undef if the dataset has not been defined

max

Returns the maximum index number (i.e., the index of the last element), which is equivalent to size - 1

index

Returns the number of the current item, in the range 0 to max

count

Returns the current iteration count in the range 1 to size, equivalent to index + 1

first

Returns a Boolean value to indicate whether the iterator is currently on the first iteration of the set

last

Returns a Boolean value to indicate whether the iterator is currently on the last iteration of the set

prev

Returns the previous item in the data set, or returns undef if the iterator is on the first item

next

Returns the next item in the data set, or undef if the iterator is on the last item

An iterator plugin is available that enables you to control how an iterator is created; if an iterator object is passed to a FOREACH loop, it is used as is (a new iterator is not created).

[% USE all_data = iterator(list_one.merge(list_two)) %]
[% FOREACH datum = all_data %]
    ...
[% END %]

Nested FOREACH loops

Nested loops will work as expected, with the loop variable correctly referencing the innermost loop and being restored to any previous value (i.e., an outer loop) at the end of the loop:

[% FOREACH group IN grouplist;
       # loop => group iterator
       "Groups:\n" IF loop.first;

       FOREACH user IN group.userlist;
           # loop => user iterator
           "$loop.count: $user.name\n";
       END;

       # loop => group iterator
       "End of Groups\n" IF loop.last;
   END 
%]

The iterator plugin can also be used to explicitly create an iterator object. This can be useful within nested loops where you need to keep a reference to the outer iterator within the inner loop. The iterator plugin effectively allows you to create an iterator by a name other than loop. See the manpage for Template::Plugin::Iterator for further details.

[% USE giter = iterator(grouplist) %]

[% FOREACH group IN giter %]
   [% FOREACH user IN group.userlist %]
         user #[% loop.count %] in
         group [% giter.count %] is
         named [% user.name %]
   [% END %]
[% END %]

WHILE

WHILE loops are used to repeatedly process a template block. This block is enclosed within [% WHILE (test) %] ... [% END %] blocks and can be arbitrarily complex. The test condition follows the same rules as those for IF blocks.

[%  total = 0;
    WHILE total <= 100 %]
        Total: [% total;
        total = total + 1;
    END;
%]

An assignment can be enclosed in parentheses to evaluate the assigned value:

[% WHILE (user = next_user) %]
   [% user.name %]
[% END %]

The Template Toolkit uses a fail-safe counter to limit the number of loop iterations to prevent runaway loops that never terminate. If the loop exceeds 1,000 iterations, an undef exception will be thrown, reporting the error:

WHILE loop terminated (> 1000 iterations)

This number can be adjusted from within Perl by setting the $Template::Directive::WHILE_MAX variable.

Flow control: NEXT and LAST

The NEXT directive starts the next iteration in a FOREACH or WHILE loop:

[% FOREACH user IN userlist %]
   [% NEXT IF user.isguest %]
   Name: [% user.name %]    Email: [% user.email %]
[% END %]

The LAST directive can be used to prematurely exit the loop. BREAK is also provided as an alias for LAST.

[% FOREACH match IN results.nsort('score').reverse %]
   [% LAST IF match.score < 50 %]
   [% match.score %] : [% match.url %]
[% END %]

See the section titled Section 4.11 later in this chapter for more details.

  • Safari Books Online
  • Create BookmarkCreate Bookmark
  • Create Note or TagCreate Note or Tag
  • PrintPrint