2

Freemarker is used as the default template engine in the ninja web framework. The framework assigns some default values to a template which are globaly available when using the ninja web framework. I have created an extension for the template which does enbales CSRF-Protection. The extension offers a function which can be used in a template, e.g.

${foo(bar)}

At the moment the function needs to be called with specific parameters, which is not very intuitiv. Using a macro I could simplify this call to

@{foo}

and the user doesn't need to worry about passing the correct (e.g. "bar") parameter. But to make this available in the ninja web framework I have to define a macro programmatically. Is that possible?

UPDATE

Sorry for the confusion. Meant <@foo/> instead of @{foo} ...

Looking at the Freemarker documentation I maybe can make more clear what I want to achieve: http://freemarker.org/docs/ref_directive_macro.html

Like I explained above I am passing a custom function to the template, enabling me to call

${foo("bar")}

What I want to do, is call this via a macro like

@<myMacro/>

But the defined macro like

<#macro myMacro>
  ${foo("bar")}
</#macro> 

should not be defined in the template but programmatically. Hope that makes it more clear.

UPDATE2 / SOLUTION

I ended up using the recommended TemplateDirectiveModel.

public class TemplateEngineFreemarkerAuthenticityTokenDirective implements TemplateDirectiveModel {
    private String authenticityToken;

    public TemplateEngineFreemarkerAuthenticityTokenDirective(Context context) {
        this.authenticityToken = context.getSession().getAuthenticityToken();
    }

    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
        if (!params.isEmpty()) {
            throw new TemplateException("This directive doesn't allow parameters.", env);
        }

        if (loopVars.length != 0) {
            throw new TemplateException("This directive doesn't allow loop variables.", env);
        }

        Writer out = env.getOut();
        out.append(this.authenticityToken);
    }
}
svenkubiak
  • 645
  • 1
  • 7
  • 19

2 Answers2

4

FreeMarker macro invocations doesn't look like @{...}. Is that some kind of Ninja-specific extension?

Anyway, if you know that there's a bar in the data-model, then your method can get it like Environment.getCurrentEnvironment().getDataModel().get("bar"), so it need not be passed in.

Also, it's maybe useful to know that FTL has two kind of "subroutines", the function-like ones, and the directive-like ones. Both can be implement both in FTL (#function, #macro) and in Java (plain Java methods, TemplateMethodModelEx, TemplateDirectiveModel). The real difference is that the function-like ones are for calculating values, and the directive-like ones are for printing values directly to the output (hence bypassing auto-escaping) and for side-effects. But all of these can reach the Environment, so there's no difference there.

ddekany
  • 29,656
  • 4
  • 57
  • 64
  • Thanks for your replay. I think thats not quiet what I want to achieve, so I updated my question. – svenkubiak Feb 12 '15 at 20:10
  • My answer basically says that you can use a `TemplateDirectiveModel` instead of a macro. – ddekany Feb 12 '15 at 21:50
  • I did try it with the TemplateDirectiveModel, but I can't figure quiet out on how to handle an empty TemplateDirectiveBody (body is null) as I just call <@myDirective/> – svenkubiak Feb 18 '15 at 07:56
  • What's there to handle? You want an empty body, so it should be `null`. – ddekany Feb 18 '15 at 20:06
3

You can call a macro "dynamically". Let's say you had a macro:

<#macro myMacro>
  ${foo("bar")}
</#macro> 

You can call it like this:

<@myMacro /> 

OR

<@.vars["myMacro"] />

So then you can do...

<#assign someVar = "myMacro" />

<@.vars[someVar] />
ratherblue
  • 2,108
  • 11
  • 14