As far as template authors are concerned, user-defined
directives can be defined using the macro
directive. Java programmers who want to
implement directives in Java Language, rather than in a template,
should use
freemarker.template.TemplateDirectiveModel
(see
more
here...).
Basics
A macro is a template fragment associated with a variable. You can use that variable in your template as a user-defined directive, so it helps in repetitive tasks. For example, this creates a macro variable that prints a big "Hello Joe!":
<#macro greet> <font size="+2">Hello Joe!</font> </#macro>
The macro
directive itself does not print
anything; it just creates the macro variable, so there will be a
variable called greet
. Things between the
<#macro greet>
and
</#macro>
(called macro definition body) will be executed only
when you use the variable as directive. You use user-defined
directives by writing @
instead of
#
in the FTL tag. Use the variable name as the
directive name. Also, the end-tag for user-defined directives is
mandatory. So you use greet
like this:
<@greet></@greet>
But since
<anything></anything>
is equivalent with
<anything/>
you
should use this shorter form (that is familiar for you if you know
XML):
<@greet/>
This will print:
<font size="+2">Hello Joe!</font>
But macros can do much more, since the thing between
<#macro ...>
and
</#macro>
is a template fragment, thus it
can contain interpolations
(${...}
) and FTL tags
(e.g. <#if
...>...</#if>
).
Programmers will say on
<@...>
that
you call the macro.
Parameters
Let's improve the greet
macro so it can use
arbitrary name, not only "Joe". For this purpose you
can use parameters. You define the
parameters after the name of the macro in the
macro
directive. Here we define one parameter for
the greet
macro,
person
:
<#macro greet person> <font size="+2">Hello ${person}!</font> </#macro>
and then you can use this macro as:
<@greet person="Fred"/> and <@greet person="Batman"/>
which is similar to HTML syntax. This will print:
<font size="+2">Hello Fred!</font> and <font size="+2">Hello Batman!</font>
As you can see, the actual value of the macro parameter is
accessible in the macro definition body as a variable
(person
). As with predefined directives,
the value of a parameter (the right side of =
) is
an FTL expression. Thus,
unlike with HTML, the quotation marks around
"Fred"
and "Batman"
are not
optional. <@greet person=Fred/>
would mean
that you use the value of variable Fred
for the
person
parameter, rather than the string
"Fred"
. Of course parameter value need not be a
string, it can be number, boolean, hash, sequence, etc., also you
can use complex expression on the right side of =
(e.g. someParam=(price + 50)*1.25
).
User-defined directives can have multiple parameters. For
example, add a new parameter color
:
<#macro greet person color> <font size="+2" color="${color}">Hello ${person}!</font> </#macro>
and then you can use this macro like:
<@greet person="Fred" color="black"/>
The order of parameters is not important, so this is equivalent with the previous:
<@greet color="black" person="Fred"/>
When you call the macro, you can use only parameters that you
have defined in the macro
directive (in this
case: person
and color
). So if
you try <@greet person="Fred" color="black"
background="green"/>
then you will get an error, since
you haven't mentioned parameter background
in the
<#macro
...>
.
Also, you must give value for all parameters that you have
defined for the macro. So if you try <@greet
person="Fred"/>
then you will get an error, since you
forgot to specify the value of color
. However, it
often happens that you would specify the same value for a parameter
in most cases, so you want to specify the value only when you want a
different value for it than the usual. This can be achieved if you
specify the parameter in the macro
directive as
param_name=usual_value
.
For example, you want to use "black"
for
color
if you don't specify value for that
parameter when you use the greet
directive:
<#macro greet person color="black"> <font size="+2" color="${color}">Hello ${person}!</font> </#macro>
Now <@greet person="Fred"/>
is OK,
since it is equivalent with <@greet person="Fred"
color="black"/>
, thus the value of
color
parameter is known. If you want
"red"
for color
, then you
write <@greet person="Fred" color="red"/>
,
and this value will override the usual value specified with the
macro
directive, so the value of
color
parameter will be
"red"
.
Also, it is important to realize that -- according to the
already explained FTL expression
rules -- someParam=foo
and
someParam="${foo}"
are very different. In the
fist case, you use the value of variable foo
as
the value of the parameter. In the second case, you use a string literal
with interpolation, so the value of the parameter will be a
string -- in this case, the value of foo
rendered
to text -- regardless of the type (as number, date, etc.) of
foo
. Or, another example:
someParam=3/4
and
someParam="${3/4}"
are different. If the
directive wants a numerical value for someParam
,
it will not like the second variation. Do not exchange these.
A very important aspect of macro parameters is that they are local variables. For more information about local variables please read: Defining variables in the template
Nested content
Custom directive can have nested content, similarly as
predefined directives like <#if
...>nested
content</#if>
can have. For example,
this creates a macro that draws borders around its nested
content:
<#macro border> <table border=4 cellspacing=0 cellpadding=4><tr><td> <#nested> </tr></td></table> </#macro>
The <#nested>
directive executes the
template fragment between the start-tag and end-tags of the
directive. So if you do this:
<@border>The bordered text</@border>
the output will be:
<table border=4 cellspacing=0 cellpadding=4><tr><td> The bordered text </td></tr></table>
The nested
directive can be called for
multiple times, for example:
<#macro do_thrice> <#nested> <#nested> <#nested> </#macro> <@do_thrice> Anything. </@do_thrice>
will print:
Anything. Anything. Anything.
If you don't use the nested
directive, then
the nested content will not be executed. Thus, if you accidentally
use the greet
directive like this:
<@greet person="Joe"> Anything. </@greet>
then FreeMarker will not see this as an error, and simply prints:
<font size="+2">Hello Joe!</font>
and the nested content will be ignored, since the
greet
macro never uses nested
directive.
The nested content can be anything that is valid FTL, including other user-defined directives. Thus this is OK:
<@border> <ul> <@do_thrice> <li><@greet person="Joe"/> </@do_thrice> </ul> </@border>
and will print:
<table border=4 cellspacing=0 cellpadding=4><tr><td> <ul> <li><font size="+2">Hello Joe!</font> <li><font size="+2">Hello Joe!</font> <li><font size="+2">Hello Joe!</font> </ul> </tr></td></table>
The local variables of a macro are not visible in the nested content. Say, this:
<#macro repeat count> <#local y = "test"> <#list 1..count as x> ${y} ${count}/${x}: <#nested> </#list> </#macro> <@repeat count=3>${y!"?"} ${x!"?"} ${count!"?"}</@repeat>
will print this:
test 3/1: ? ? ? test 3/2: ? ? ? test 3/3: ? ? ?
because the y
, x
and
count
are the local (private) variables of the
macro, and are not visible from outside the macro definition.
Furthermore, a different set of local variables is used for each
macro call, so this will not cause confusion:
<#macro test foo>${foo} (<#nested>) ${foo}</#macro> <@test foo="A"><@test foo="B"><@test foo="C"/></@test></@test>
and will print this:
A (B (C () C) B) A
Macros with loop variables
Predefined directives like list
can use
so-called loop variables; you should read Defining variables in the template to understand loop variables.
User-defined directives can also have loop variables. For
example, let's extend the do_thrice
directive of
the earlier examples so it exposes the current repetition number as
a loop variable. As with the predefined directives (as
list
) the name of loop
variables is given when you call the directive (as
foo
in <#list foos as
foo>...</#list>
),
while the value of the variables is set by the
directive itself.
<#macro do_thrice> <#nested 1> <#nested 2> <#nested 3> </#macro> <@do_thrice ; x> <#-- user-defined directive uses ";" instead of "as" --> ${x} Anything. </@do_thrice>
This will print:
1 Anything. 2 Anything. 3 Anything.
The syntactical rule is that you pass the actual value of the
loop variable for a certain "loop" (i.e. repetition of the nested
content) as the parameter of nested
directive (of
course the parameter can by arbitrary expression). The name of the
loop variable is specified in the user-defined directive open tag
(<@...>
) after the parameters and a
semicolon.
A macro can use more the one loop variable (the order of variables is significant):
<#macro repeat count> <#list 1..count as x> <#nested x, x/2, x==count> </#list> </#macro> <@repeat count=4 ; c, halfc, last> ${c}. ${halfc}<#if last> Last!</#if> </@repeat>
The output will be:
1. 0.5 2. 1 3. 1.5 4. 2 Last!
It is not a problem if you specify different number of loop
variables in the user-defined directive start-tag (that is, after
the semicolon) than with the nested
directive. If
you specify less loop variables after the semicolon, then simply you
will not see the last few values that the nested
directive provides, since there is no loop variable to hold those
values. So these are all OK:
<@repeat count=4 ; c, halfc, last> ${c}. ${halfc}<#if last> Last!</#if> </@repeat> <@repeat count=4 ; c, halfc> ${c}. ${halfc} </@repeat> <@repeat count=4> Just repeat it... </@repeat>
If you specify more variables after the semicolon than with
the nested
directive, then the last few loop
variables will not be created (i.e. will be undefined in the nested
content).
More about user-defined directives and macros
Now you may read the relevant parts of the FreeMarker Reference:
You can define methods in FTL as well, see the function
directive.
Also, you may interested in namespaces: Namespaces. Namespaces help you to organize and reuse your commonly used macros.
Java programmers might want to know that directives (macros
are directives) and methods (function-like things) can also be
written in Java language, by implementing the
TemplateDirectiveModel
or
TemplateMethodModelEx
interfaces, respectively.
Then you can pull in the Java implementations into the template like
<#assign foo =
"com.example.FooDirective"?new()>
or
<#assign foo =
"com.example.FooMethod"?new()>
on the same place where
you would have <#macro foo
...>
or
<#function foo
...>
otherwise.