Synopsis
The simplest form for listing a sequence (or collection) is:
<#list sequence as item>
Part repeated for each item
</#list>
and to list the key-value pairs of a hash (since 2.3.25):
<#list hash as key, value>
Part repeated for each key-value pair
</#list>
But these are just special cases of the generic forms, which
are shown below. Note that for simplicity we only show the generic
forms for sequence listing; simply replace "as
item" with
"as key,
value" to get the
generic form for hash listing.
Generic form 1:
<#list sequence as item>
Part repeated for each item
<#else>
Part executed when there are 0 items
</#list>
Where:
-
The
elsepart is optional, and is only supported since FreeMarker 2.3.23. -
sequence: Expressions evaluates to a sequence or collection of the items we want to iterate through -
item: Name of the loop variable (not an expression) -
The various "parts" between the tags can
contain arbitrary FTL (including nested
list-s)
Generic form 2 (since FreeMarker 2.3.23):
<#list sequence>
Part executed once if we have more than 0 items
<#items as item>
Part repeated for each item
</#items>
Part executed once if we have more than 0 items
<#else>
Part executed when there are 0 items
</#list>
Where: see the "Where" section of Form 1 above
(and thus the else part is optional here
too).
Description
Simplest form
Assuming users contains the
['Joe', 'Kate', 'Fred'] sequence:
<#list users as user>
<p>${user}
</#list> <p>Joe <p>Kate <p>Fred
The list directive executes the code
between the list start-tag and
list end-tag (the body of
list from now on) for each value in the
sequence (or collection) specified as its first parameter. For
each such iteration the loop variable (user in
this example) will store the value of the current item.
The loop variable (user) only exists
inside the list body. Also, macros/functions
called from within the loop won't see it (as if it were a local
variable).
Listing hashes is very similar, but you need to provide two
variable names after the as; one for the hash
key, and another for the associated value. Assuming
products is { "apple": 5, "banana":
10, "kiwi": 15 }:
<#list products as name, price>
<p>${name}: ${price}
</#list> <p>apple: 5 <p>banan: 10 <p>kiwi: 15
Note that not all hash variables can be listed, because some
of them isn't able to enumerate its keys. It's practically safe to
assume though that hashes that stand for Java
Map objects can be listed.
else directive
else inside list is
only supported since FreeMarker 2.3.23
The else directive is used if when there
are 0 items, you have to print something special instead of just
printing nothing:
<#list users as user>
<p>${user}
<#else>
<p>No users
</#list> This outputs the same as the earlier example, except when
users contains 0 items:
<p>No users
Note that the loop variable (user)
doesn't exist between the else tag and the
list end-tag, since that part is not part of
the loop.
else must be literally (means, in the
source code) inside the body of the list
directive. That is, you can't moved it out into a macro or
included template.
items directive
items exists since FreeMarker
2.3.23
The items directive is used if you have
to print (or do) something before the first list item, and after
the last list item, as far as there's at least 1 item. A typical
example:
<#list users>
<ul>
<#items as user>
<li>${user}</li>
</#items>
</ul>
</#list> <ul>
<li>Joe</li>
<li>Kate</li>
<li>Fred</li>
</ul> If there are 0 items, the above won't print anything, thus
you don't end up with an empty
<ul></ul>.
That is, when the list directive has no
as item parameter,
the body of its is executed exactly once if there's at least one
item, or not at all otherwise. It's the body of the mandatory
nested items directive that will be run for
each item, and hence it's also the items
directive that defines the loop variable with as
item, not
list.
A list directive with
items also can have an else
directive:
<#list users>
<ul>
<#items as user>
<li>${user}</li>
</#items>
</ul>
<#else>
<p>No users
</#list> Some further details:
-
The parser will check that a
listwithoutas itemparameter always has a nesteditemsdirective, and that anitemsdirective always has an enclosinglistwhich has noas itemparameter. This is checked when the template is parsed, not when the template is executed. Thus, these rules apply on the FTL source code itself, so you can't moveitemsout into a macro or included template. -
A
listcan have multipleitemsdirectives, but only one of them will be allowed to run (as far as you don't leave and re-enter the enclosinglistdirective); and further attempts to callitemswill cause error. So multipleitemscan be utilized on differentif-elsebranches for example, but not for iterating twice. -
itemsdirective can't have its own nestedelsedirective, only the enclosinglistcan have -
The loop variable (
user) only exists inside the body of theitemsdirective.
sep directive
sep exists since FreeMarker
2.3.23
sep is used when you have to display
something between each item (but not before the first item or
after the last item). For example:
<#list users as user>${user}<#sep>, </#list> Joe, Kate, Fred
Above, <#sep>, </#list> is a
shorthand for <#sep>,
</#sep></#list>; the sep
end-tag can be omitted if you would put it where the enclosing
directive is closed anyway. In the next example, you couldn't use
such abbreviation (HTML tags close nothing, as they are just raw
text to output for FreeMarker):
<#list users as user>
<div>
${user}<#sep>, </#sep>
</div>
</#list> sep is just a shorthand for
<#if
item?has_next>...</#if>.
Thus, it can be used anywhere where there's a
list or items loop variable
available, it can occur for multiple times, and it can have
arbitrary nested content.
The parser ensures that sep is only used
on a place where there's a visible loop variable. This happens
earlier than the actual execution of the template. Thus, you can't
move sep from inside the associated
list or items directive into
a macro or included template (the parser can't know where those
will be called from).
break directive
break is deprecated for most use cases,
as it doesn't work well with <#sep> and
item?has_next.
Instead, use sequence?take_while(predicate)
to cut the sequence before you list it. See also examples here.
You can exit the iteration at any point with the
break directive. For example:
<#list 1..10 as x>
${x}
<#if x == 3>
<#break>
</#if>
</#list> 1 2 3
The break directives can be placed
anywhere inside list as far as it has
as item parameter,
otherwise it can be placed anywhere inside the
items directive. However, it's strongly
recommended to place it either before or after all the other
things that you do inside the iteration. Otherwise it's easy to
end up with unclosed elements in the output, or otherwise make the
template harder to understand. Especially, avoid breaking out from
the nested content of custom directives (like <#list
...>...<@foo>...<#break>...</@foo>...</#list>),
as the author of the directive may not expect that the closing tag
(</@foo>) is never executed.
If the break is inside
items, it will only exit from
items, not from list. In
general, break will only exit from the
directive whose body is called for each item, and can only be
placed inside such directive. So for example can't use
break inside list's
else section, unless there's the
list is nested into another
break-able directive.
Using break together with
sep or ?has_next is
generally a bad idea, as these can't know if you will skip the
rest of items with a break. To solve such
situations see these
examples.
Just like else and
items, break must be
literally inside body of the directive to break out from, and
can't be moved out into a macro or included template.
continue directive
continue is deprecated for most use
cases, as it doesn't work well with
<#sep>,
item?has_next,
item?counter,
item?index,
item?item_parity,
etc. Instead, use sequence?filter(predicate)
to remove unwanted elements. See also examples here.
The continue directive exists since
FreeMarker 2.3.27.
You can skip the rest of the iteration body (the section
until the </#list> or
</#items> tag) with the
continue directive, then FreeMarker will
continue with the next item. For example:
<#list 1..5 as x>
<#if x == 3>
<#continue>
</#if>
${x}
</#list> 1 2 4 5
The continue directives can be placed
anywhere inside list as far as it has
as item parameter,
otherwise it can be placed anywhere inside the
items directive. However, it's strongly
recommended to place it before all the other things you do inside
the iteration. Otherwise it's easy to end up with unclosed
elements in the output, or otherwise make the template harder to
understand. Especially, avoid breaking out from the nested content
of custom directives (like <#list
...>...<@foo>...<#continue>...</@foo>...</#list>),
as the author of the directive may not expect that the closing tag
(</@foo>) is never executed.
When you call continue, the
sep directive will not be executed for that
iteration. Using continue together with
sep is generally a bad idea anyway, also
?has_next, ?counter,
?index, ?item_parity, etc.
will not work as you certainly wanted if you completely skip
items. To solve such situations see these examples.
Just like break,
continue must be literally inside body of the
directive whose iteration need to be "continued", and
can't be moved out into a macro or included template.
Accessing iteration state
Starting from 2.3.23, loop variable built-ins is
the preferred way of accessing current state of the iteration. For
example, here we use the counter and
item_parity loop variable built-ins (see all of
them in the
Reference):
<#list users>
<table>
<#items as user>
<tr class="${user?item_parity}Row">
<td>${user?counter}
<td>${user}
</#items>
</table>
</#list> <table>
<tr class="oddRow">
<td>1
<td>Joe
<tr class="evenRow">
<td>2
<td>Kate
<tr class="oddRow">
<td>3
<td>Fred
</table> In 2.3.22 and earlier, there were two extra loop variables to retrieve the iteration state instead (and they still exist for backward compatibility):
-
item_index(deprecated byitem?index): The index (0-based number) of the current item in the loop. -
item_has_next(deprecated byitem?has_next): Boolean value that tells if the current item is the last in the sequence or not.
so in the above example, you could replace
${user?counter} with ${user_index +
1}.
Skipping items conditionally
If you need to skip certain element in a list, it's
generally a bad idea to use if directive
for that, because then <#sep>,
item?has_next,
item?counter,
item?index,
item?item_parity,
etc., will not be usable, as FreeMarker doesn't know what items
were and will be actually displayed. Instead, you should try to
remove the unwanted items from the sequence that you will list,
and then list it (since 2.3.29). Here are some typical examples
with and without if.
Filtering
In this example, you want to show the recommended products
from products. Here's the wrong solution with
if:
<#-- WRONG solution! The row parity classes will be possibly messed up: -->
<#list products as product>
<#if product.recommended>
<div class="${product?item_parity}Row">${product.name}</div>
</#if>
</#list> Here's the good solution that uses the filter
built-in:
<#-- Good solution: -->
<#list products?filter(p -> p.recommended) as product>
<div class="${product?item_parity}Row">${product.name}</div>
</#list> Stop listing when a certain element is found
Let's say you have a list of lines in
lines, and you need to stop at the first
empty line (if there's any). Furthermore you need to
<br> between the elements. Here's the
wrong solution with if and
break:
<#-- WRONG solution! <br> might be added after the last printed line: -->
<#list lines as line>
<#if line == ''>
<#break>
</#if>
${line}<#sep><br>
</#list> Here's the good solution that uses the take_while
built-in (note that the condition is inverted compared to
the if+break
solution):
<#-- Good solution: -->
<#list lines?take_while(line -> line != '') as line>
${line}<#sep><br>
</#list> Nesting loops into each other
Naturally, list or
items can contain further
list-s:
<#list 1..2 as i>
<#list 1..3 as j>
i = ${i}, j = ${j}
</#list>
</#list> i = 1, j = 1
i = 1, j = 2
i = 1, j = 3
i = 2, j = 1
i = 2, j = 2
i = 2, j = 3 It's also allowed to use clashing loop variable names like:
<#list 1..2 as i>
Outer: ${i}
<#list 10..12 as i>
Inner: ${i}
</#list>
Outer again: ${i}
</#list> Outer: 1
Inner: 10
Inner: 11
Inner: 12
Outer again: 1
Outer: 2
Inner: 10
Inner: 11
Inner: 12
Outer again: 2 Treatment of missing (null) elements
As you know by now, the list directive
will repeat its nested content for each element of the listed
value. However, it's technically possible that you have holes
(missing values, Java null-s) in the list of
elements. The nested content will executed for these
"holes" as well, but then the loop variable (the
variable whose name you specify after the as
keyword) will be missing. When FreeMarker finds that there's no
variable with the given name in the loop variable scope, it will
just fall back to a higher scopes to find it. However, this
fallback behavior can be problematic in this case.
Consider:
<#list xs as x>
${x!'Missing'}
</#list> Here, the intent of the author is to print
"Missing" for the missing elements (hopes) of
xs. But if accidentally there's an
x variable in a higher scope, like in the
data-model, then x will evaluate to that in
case the currently listed element is missing, and so it won't
default to "Missing". To solve that, the programmers
should set the fallback_on_null_loop_variable
configuration setting to false. (Unfortunately,
the default must be true for backward
compatibility.) In that case no fallback will occur if a loop
variable is missing.
Notes for Java programmers
If classic compatible mode
list accepts a scalar too and treats it as a
single-element sequence.
If you pass a collection that
wraps an java.util.Iterator to the
list, you can iterate over its elements only
once, since Iterators are by their nature
one-off objects. When you try to list a such collection variable
for the second time, an error will abort template
processing.
