The standard way to resolve views in a Spring web application is to turn each view resolution request into the name of a web page. But sometimes we don't necessarily want to do this. This example explores a situation I encountered where I wanted to use a single template for a whole site and the individual view requests would all resolve to the same template name with the request being translated into the name of a page to include in the template.
Lets clarify this. Here is the template I wanted to use for my whole site. It's called index.ftl (I'm using using FreeMarker here. You could just as easily use Velocity) and resides in the root of my template directory:
[#ftl]
[#import "spring.ftl" as spring /]
[#import "macros.ftl" as lp /]
<html>
<head>
[#-- include the themes style sheet --]
<title>[@spring.message "application.title" /]</title>
<link rel="stylesheet" xhref="[@lp.themedir "style" /]" type="text/css" />
</head>
<body>
<table cellspacing="0" cellpadding="0">
<tr>
<td id="headerblock" colspan="2">
[#include "header.ftl" /]
</td>
</tr>
<tr>
<td id="menublock" >
[#include "menu.ftl" /]
</td>
<td id="contentblock" >
[#include module /]
</td>
</tr>
</table>
</body>
</html>
Quickly, the [#ftl] simply lets FreeMarker know to use square brackets [] instead of the default ones (<>). I find this makes things easier to read. It's a personal preference
The important tag to note here is the [#include module /] tag. This expects to find the name of the ftl template to be included in a variable called "module" in the FreeMarker data model. The concept of this template is quite good becuase it means that the core boiler plate code for each web page in the site is sourced in one place and each working page (module) only has to contain the code it needs to work.
The problem with this is that the FreeMarker view resolver is designed to take in a request for a view and translate it directly to a template name. For example if my controller requested "accounts/accountsearch" as a view, the FreeMarker resolver would resolve /accounts/accountsearch.ftl and return it. What I wanted was to return index.ftl with "/accounts/accountsearch.ftl" stored in the FreeMarker model unders the name "module" so that index.ftl could find it.
So I needed to modify the way that FreeMarker resolved views. I also needed a way to also setup the name of the default template to use. I experimented with modding the resolver itself but had problems with it getting into recursive loop when it was rendering the pages. So I decided to change the FreeMarker view which the resolver generates. Here's the solution I worked out. First I needed a way to setup the name of the default template. I tried a few things and ended up with setting it as an attribute of the resolver. Here's the bean definition from the *-servlet.xml file:
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<!-- Override the view to use our custom view. -->
<property name="viewClass" value="com.lp.commissions.spring.views.CommonTemplateFreeMarkerView" />
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
<!-- Required for including spring.ftl and for access to theme settings. -->
<property name="exposeSpringMacroHelpers" value="true" />
<!--
Using attributes we can communicate various settings to the view.
Refer to the view class for what these settings do.
-->
<property name="attributes">
<props>
<prop key="baseTemplateName">/index.ftl</prop>
</props>
</property>
</bean>
You can see that when compared to the standard bean definition in the manual (section 14.2.2, PDF page 176) I've added the viewClass to use to generate the view and set a value in the resolvers attributes. I have not fully sorted what attributes are used for but it looks like they are merely a way to pass information to the resolver. Anyway, so now when the resolver is asked for a view, it will generate one of our custom view classes instead of the default FreeMarkerView class.
Now here is the code for the custom view class:
package com.lp.commissions.spring.views;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
/**
* This view overrides the freemarker view. It makes the following process
* changes
*
* 1. Loads a pre-allocated freemarker template instead of the requested file.
* 2. Stores the requested file under the name "module" in the datamodel.
*
* The aim of this is to allow for sites where we want to use a standard layout
* template which loads different views. This means that only one template needs
* boilerplate code to include menus, etc and each module template just contains
* the code it needs.
*
* @author DerekC
*/
public class CommonTemplateFreeMarkerView extends FreeMarkerView {
@Override
protected void doRender(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
super.logger.debug("Starting rendering of " + this.getBeanName());
model.put("module", this.getBeanName() + ".ftl");
this.setUrl((String) this.getAttributesMap().get("baseTemplateName"));
super.doRender(model, request, response);
}
}
The requested view is stored as the bean name so it's a simple matter to extend the standard FreeMarker view to store the view name in the FreeMarker data model and update the view to generate from the base template. Then all I had to do was make a clall to the super classes doRender method to start generating the resulting HTML.
Thanks for the solution - this solves a related problem I was facing.
Curious - it sounds like you're using Freemarker to address both templating and structural layout. Would it be easier to separate the problems by using SiteMesh or Tiles for the latter, keeping FTLs for the lowest-level templates?