Custom directives
As far as template authors are concerned, user-defined directives can be defined using the macro
directive.
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!”:
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:
But since
This will print:
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:
and then you can use this macro as:
which is similar to HTML syntax. This will print:
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
:
and then you can use this macro like:
The order of parameters is not important, so this is equivalent with the previous:
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:
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:
The <#nested>
directive executes the template fragment between the start-tag and end-tags of the directive. So if you do this:
the output will be:
The nested
directive can be called for multiple times, for example:
will print:
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:
then FreeMarker will not see this as an error, and simply prints:
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:
and will print:
The local variables of a macro are not visible in the nested content. Say, this:
will print this:
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:
and will print this:
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.
This will print:
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):
The output will be:
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:
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 help you to organize and reuse your commonly used macros.