Freemarker Templates: Layout / Decorators with Shared Variables in Data Model

This tutorial is about the Freemarker template engine and it explains how to build a custom Layout / decorator mechanism similar to Tiles.
I have build my own Layout mechanism similar to Tiles, because it seemed the easiest to me so far and I didn’t want to include the Tiles lib in my app to keep things simple.

Here is what I want to do:

I have 2 templates.

1. A template layout.ftl which defines the basic layout (the decorator)

1
${screen_content}

Note the ${customPageTitle} page variable which is what I am talking about here. I will come to that later.

2.A second template content.ftl which defines my content.

This is my custom content, which should be displayed in the the screen_content variable of the layout.ftl template.

This template should be able to set the customPageTitle in the layout.ftl by doing a

${assign("customPageTitle", "This is custom test title rendered inside the layout template")}

The point in this post is the expression ${assign(„customPageTitle“, „This is custom test title rendered inside the layout template“)} .

Usually in Freemarker you would have written:

<#assign customPageTitle="This is custom test title rendered inside the layout template">

But the problem is that this will not write that new *customPageTitle* variable to the underlying dataModel so that it can be used in the layout.ftl.

But it is possible. Continue with the serverside servlet code which renders the templates.

3. Render both templates in the servlet / java code
In my Servlet I am basically doing rendering of the 2 templates.
First I render the content.ftl and then the layout.ftl. The rendered output of the content.ftl is put into the data model so that it can be outputed by the layout.ftl

1
2
3
4
5
6
7
8
9
10
11
12
 
// 1. parse the content
Map dataModel = RequestContext.getInstance().getContext();
dataModel.put("assign", new DataModelModifier(dataModel)); // this is my own custom assign function.
 
String customContent = TemplateRenderer.getInstance().render("content.ftl", dataModel);
 
// 2. pass it in the model of the layout
dataModel.put("screen_content", customContent);
 
// 3. Render the complete layout
resp.getWriter().println(TemplateRenderer.getInstance().render("layouts/layout.ftl", dataModel));

Note: TemplateRenderer, RequestContext are *not* Freemarker classes. They are own helpers in my application.

You ask at this point what is actually the class DataModelModifier and why can the content.ftl do a ${assign(„customtitle“, „This is custom test title set inside a different template“)} ?

The DataModelModifier which makes a method available in Freemarker. They are used like normal variables. see Method in the Freemarker Doc.

This class basically gets are reference to our data model which we want to modify and in the exec(List arguments) method we are just adding a value to our dataModel hashMap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
protected static class DataModelModifier implements TemplateMethodModel{
 
private final Map dataModel;
 
public DataModelModifier(Map model) {
this.dataModel = model;
}
 
public Object exec(List arg0) throws TemplateModelException {
 
if (arg0.size() != 2) {
throw new TemplateModelException("Wrong arguments. key, value required");
}
 
this.dataModel.put((String) arg0.get(0), arg0.get(1));
return "";
}
 
}

Finally…

What I wanted to achieve is that the content.ftl template can assign a value to the variable „customPageTitle“ which is rendered in the layout.ftl template which is a different template.
By default FTL does NEVER modify the dataModel according to the Freemarker Documentation under: http://freemarker.sourceforge.net/docs/pgui_misc_multithreading.html

From Freemarker doc:
It is impossible to modify the data-model object or a shared variable with FTL, unless you put methods (or other objects) into the data-model that do that. We discourage you from writing methods that modify the data-model object or the shared variables. Try to use variables that are stored in the environment object instead (this object is created for a single Template.process call to store the runtime state of processing), so you don’t modify data that are possibly used by multiple threads. For more information read: Variables

But exactly this is what I am trying to do, and at the moment it sounds totally reasonable to me. I just don’t know what the pitfalls are. I know that there are some risks too:
It can be kind of dangerous and error prone practise to let FTL modify the dataModel because it can lead to accidently overwriting of values which lead to strange errors.
But on the other hand it gives you great flexibility to build web applications. I will see when I will run into first problems with this approach.

Conclusion
Here is what we need in order to build our own custom template layout / decorator:

1. 2 templates. one for the layout and one for the actual content.
2. We need to render the content template first and make the output available in the layout template.
3. we need a custom class which allows the dataModel to be modified inside the templates via FTL.
4. we need to make this class available in the dataModel as a Freemarker Method. That means this class needs to implement the TemplateMethodModel interface
5. The content template uses this custom method (based on that class): ${assign(„key“,“value“)}

I hope that this tutorial helps somebody out there too.

Dieser Beitrag wurde unter Software-Development veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Eine Antwort auf Freemarker Templates: Layout / Decorators with Shared Variables in Data Model

  1. Lucian sagt:

    Piece of writing writing is also a fun, if you know then you can write otherwise it is difficult to write.|

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

CAPTCHA-Bild

*