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
else
part 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).
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 }
:
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.
The
else
directive is used if when there
are 0 items, you have to print something special instead of just
printing nothing:
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.
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:
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:
items
directive, and that an
items
directive
always has an enclosing
list
which has no
as
item
parameter. 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 move
items
out into a macro or included
template.
A
list
can have multiple
items
directives, but only one of them will
be allowed to run (as far as you don't leave and re-enter the
enclosing
list
directive); and further
attempts to call
items
will cause error. So
multiple
items
can be utilized on different
if
-
else
branches for
example, but not for iterating twice.
items
directive can't have its own
nested
else
directive, only the enclosing
list
can have
The loop variable (
user
) only exists
inside the body of the
items
directive.
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:
<#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):
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
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:
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.
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:
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.
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
):
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
by
item
?index
): The
index (0-based number) of the current item in the loop.
item
_has_next
(
deprecated
by
item
?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 +
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
.
In this example, you want to show the recommended products
from
products
. Here's the wrong solution with
Template
<#-- 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>
</#list>