Let me start off by saying I think that both SiteMesh and Tiles are great frameworks. I was a long time user and fan of Tiles, and I think it's appropriate for certain situations. However, I've been a heavy user of SiteMesh since it passed the 10 minute test. While most heavy users of SiteMesh (the Atlassian guys come to mind) say that it can do everything that Tiles can do, these features are largely undocumented. This is my attempt to document a cool feature.
In a site I recently helped develop, we needed a couple of features:
- A tabbed menu that highlighted the current tab based on which page you were on.
- A bunch of "panels" on the right sidebar that changed according to the page.
To make this work, we used the meta tag functionality that SiteMesh provides.
Funny side/related note, I just googled for this tag and found this howto, which is similar to this one.
In our pages, we added the meta tags to set the active menu, as well as which panels to show in the sidebar:
<head>
<title><fmt:message key="authorList.title"/></title>
<meta name="menu" content="Authors"/>
<meta name="panels" content="administration,blogs,events"/>
</head>
Then, in our decorator, we interpret these separately. First, we used Struts Menu (with Velocity) for the navigation system:
<c:set var="currentMenu" scope="request">
<decorator:getProperty property="meta.menu"/>
</c:set>
<c:import url="/WEB-INF/pages/menu.jsp">
<c:param name="template" value="/template/menu/tabs.html"/>
</c:import>
The menu.jsp page takes "template" as a parameter so we display the same menu links using a different Velocity template (for example, links at the bottom of the page).
<menu:useMenuDisplayer name="Velocity" config="${param.template}" permissions="rolesAdapter">
Then our tabs.html Velocity template uses the "currentMenu" attribute to determine which menu to highlight.
## displayMenu is defined in WEB-INF/classes/globalMacros.vm
#macro( menuItem $menu $level )
#set ($title = $displayer.getMessage($menu.title))
#if ($menu.url)
#if ($menu.name == $currentMenu)
<span class="current">
#end
<a href="$!menu.url" title="$title"><span>$title</span></a>
#if ($menu.name == $request.getAttribute('currentMenu'))
</span>
#end
#end
#end
#if ($displayer.isAllowed($menu))
#displayMenu($menu 0)
#end
As far as the panel injection goes, that's processed using the following logic in our decorator:
<c:set var="panels"><decorator:getProperty property="meta.panels"/></c:set>
<!-- No panels set, use default set of panels -->
<c:if test="${empty panels}"><c:set var="panels" value="different,partners"/></c:if>
<c:forEach var="panel" items="${panels}">
<c:import url="/WEB-INF/pages/panels/${panel}.jsp"/>
</c:forEach>
Since this site used WebWork, the <ww:action> tag made it easy to give each panel independence. That is, each panel could load on its own, supply its own data, and not worry about the data being prepared beforehand. Here's an example:
<%@ include file="/common/taglibs.jsp"%>
<h2>Author Blogs</h2>
<ww:action name="'authors'" id="authors" namespace="default"/>
<div class="item">
<ww:iterator value="#authors.authors" status="index">
<a href="<ww:property value="blog.feedUrl"/>">
<img src="${ctxPath}/images/icons/xml.gif" alt="XML Feed"
style="margin-right: 5px; vertical-align: middle"/></a>
<a href="<ww:property value="blog.url"/>"><ww:property value="name"/></a>
<br />
</ww:iterator>
</div>
Of course, now that you can use Tiles with WebWork, Struts, Spring MVC and JSF - you could use Tiles for the injection and SiteMesh for the decoration.
Now if we could just get someone to write a JSF Decorator for SiteMesh, like Erik Hatcher did for Tapestry.
This evening, I created a TilesResult for WebWork that allows you to use Tiles with WebWork. For the following to work in your application, you'll need a nightly build of Tiles, commons-digester (which Tiles requires) and this patch for WebWork. For your convenience, I've posted a patched webwork-2.2.2.jar (with TilesResult).
I also posted a webwork-tiles.war that you can try and download yourself. It's based on Equinox, so you will need to setup PostgreSQL and an "equinox" database - or you can just change the database settings in WEB-INF/lib/jdbc.properties.
On to the instructions:
1. In your web.xml file, you need to add a servlet entry for TilesServlet to load the tiles definitions into the ServletContext.
<servlet>
<servlet-name>tiles</servlet-name>
<servlet-class>org.apache.tiles.servlets.TilesServlet</servlet-class>
<init-param>
<param-name>definitions-config</param-name>
<param-value>/WEB-INF/tiles-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
2. In xwork.xml, use type="tiles" on your <result>.
<action name="editUser" class="userAction" method="edit">
<result name="success" type="tiles">userForm</result>
<result name="input" type="tiles">userList</result>
</action>
I'm sure WebWork has a way of making this result type the default, I just haven't found it yet.
Hat tip to Spring's TilesView (source) for showing how to make this work.
Update: While I'm a happy SiteMesh user, I've recently had some clients who were more interested in Tiles. This largely inspired me to see if WebWork + Tiles was possible.
Update 2: It looks like TilesResult will be included in WebWork 2.2.2. Now if we could just get the Tiles team to cut a release.