25 September 2007

Handy tip for layouts in Freemarker

If you're writing a Java webapp and using Freemarker as your templating engine, hooray! Skip to the next section. Maybe this tip will come in handy.

If you're not using Freemarker, you should really consider it. JSP's are ridiculous - why are you compiling all your markup into Java code? Do you like it when you make a small typo in your JSP page, and get an error from the JSP engine referencing the Java code it created to render your page?? Gross, let's go read through the generated code to infer what the typo was! Do you like how the JSP pre-compilation step is annoying to set up, but if you don't, you have the dreaded first request to worry about? JSP's might be a little faster than interpreting markup on each request, but with smart caching, and the ease of adding more web server boxes, that's a dumb reason. And wow, let's give people the ability to write arbitrary controller code in the view, and then trust them to do things the right way instead.

Oh, and if you like Velocity, that's cool I guess. Freemarker is nice because it's a real stickler for correctness - a null value isn't just rendered as a blank string, because it's not (insert scary life-support app example). The model is the model, you can render it, and that's all. Just what you should be doing.

The layout tip


If you've played with (or gotten paid to work in) Ruby on Rails, you're familiar with the "layout" concept. It's just a simple template for a page, and instead of using "includes" where the template has to know what's going to be inserted, it just "yields" to the page content. It's the same philosophy as Struts Tiles - the dependencies between your markup files should go from the bottom up - the content needs a place to be inserted, and the template doesn't care what it is.

This layout method gives you a similarly easy and readable result in Freemarker.

First, let's create a page, with some wishful thinking for how we think the layouts should work:


<@layout.basic>

<a href="/customer/accounts">Browse accounts</a>

</@layout.basic>


That's saying, I'd like to use a layout named "basic" and in the place that layout puts the page content, I'd like to have my link to Browse Accounts show up there. The page is as short as can be, and readable.

Now, let's make it work. It seems I'm calling a macro "basic" and I expect it to be in the "layout" namespace. I haven't used an import macro on my page, so the code for the layouts will be auto_imported into my pages, using the "auto_import" setting in Freemarker's Configuration object. I set it to "/layouts/layouts.ftl as layout" so that the template /layouts/layouts.ftl gets imported to all my pages with the layout namespace. (It has to be that way because Freemarker can only associate one template with each namespace.) If you're using Spring to configure Freemarker, you'd create a props element containing an "auto_import" prop and pass it to the freemarkerSettings property of the FreeMarkerConfigurer bean.

Now I create the layouts.ftl template. This could contain all my layouts, but it would look strange for several of them to live in one file, so instead my layouts.ftl looks like this:


<#include "admin.ftl" />
<#include "basic.ftl" />


Clearly this is where you add additional layouts as you need them.

Finally, let's look at the contents of basic.ftl:


<#macro basic>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="header">
<p>
Cows make moo sounds
</p>
</div>
<hr/>
<div id="content">
<#nested>
</div>
</body>
</html>
</#macro>


This creates a macro named "basic" and uses the nested macro to include whatever appeared in the body of our caller's <@layout.basic> tag.

Neato!

3 comments:

Aron Rodrigues said...

Congratulations!
My name is Aron and I'm from Brazil.

your post helped me a lot.

Regards

KimK said...

Hi Alex,
Thank you for this post. I am new to Freemarker and cannot get this to work unfortunately.
My problem is here :
<#include "basic.ftl" />

I get the following error :

java.io.FileNotFoundException: Template WEB-INF/freemarker/basic.ftl not found.

Though my basic.ftl file is definitely there!
Thanks if you have any tips.

here is the contents of my layouts.ftl file
<#include "/WEB-INF/freemarker/basic.ftl" />

KimK said...

Hey, I moved the contents of my basic.ftl (definition of the macro code) into my layouts.ftl instead of importing the seperate file, and now this works.

Not sure why it didn't like the basic.ftl file!