Decorating the Web with SiteMesh   previous    print     download     next

Decorating the Web with SiteMesh
R. Kevin ColeFebruary 16, 2007

This is a demonstration of the SiteMesh page decorating package. A simple, three page, web application is constructed that includes a functional navigation bar, a header and a footer. The web application began life as sitemesh-blank.war which is available at the sitemesh home page at www.opensymphony.com. The sitemesh-blank application is not required to use the example application that accompanies this article. The complete source of the demonstration application is availble in SiteMeshDemo.zip.

Separating Presentation from Content

Separating the content layer from the presentation layer is a fundamental problem in web applications development. Efficiently separating the layers and maintaining a consistent look and feel over the entire site is an industry best practice and a proven design pattern.

A typical website requires header, navigation, footer and content sections. This structure should be applied consistently across all pages on the site. Applying these structural elements independently to a large number of pages is both time-consuming and difficult when page updates are required.

There are many ways to approach this problem. One option would be to encode your content as structured XML and run them through an XSLT processor to create your web pages. This requires complex XSL stylesheets and lacks support from the current generation of web page editors. Another option would be to implement the structural components of the site in CSS and apply the stylesheets to all pages. CSS-positioning can cause versioning issues with the current generation of browsers, particularly where page layout elements are concerned.

The SiteMesh Solution

SiteMesh provides an elegant solution to the page layout problem by enabling you to create plain HTML content that has no knowledge of how it will be decorated. The result is clean separation between presentation and content.

SiteMesh is written in Java for use in J2EE applications and uses configuration files expressed in XML. To use SiteMesh you'll need a servlet specification 2.3 container since it is implemented as a page filter. Sitemesh performs additional processing on a page after the page is rendered by the application container and before it is displayed in a client's browser,

Where to get SiteMesh

SiteMesh is delivered in a single jar file that can be acquired from the opensymphony.com at their SiteMesh downloads site. This article was written using sitemesh-2.1.jar. Although not strictly required to build a sitemesh application, the Java Standard Template Library (JSTL) is used to handle a dynamic navigation panel in the demo so you will also need jstl.jar and standard.jar.

SiteMesh in Four Steps:

In the following discussion, WebRoot refers to the root directory in your source tree where your web pages are stored. It is the same directory that would contain your welcome page, index.jsp. Your WEB-INF directory would be referred to as WebRoot/WEB-INF.

  1. Setup the page filter. Define the SiteMesh page filter in your web.xml file.

  2. Compose a simple web page without formatting. SiteMesh is not limited to JSP. PHP, Perl, XSL and straight HTML can be decorated using the page filter.

  3. Create a JSP decorator page. A number of custom tags are provided that enable you to inform the SiteMesh process of where headers, footer, navigation panels, etc will appear on the page.

  4. Map the decorator. The configuration file supports URL patterns, so you can apply different decorators to the various parts of your site. You can exclude decorators based on URL pattern as well.

Setup the SiteMesh Filters

As stated earlier, Sitemesh is implemented as a servlet filter and therefore it must be declared inas such in web.xml.

<filter>
<filter-name>sitemesh</filter-name>
<filter-class>
com.opensymphony.module.sitemesh.filter.PageFilter
</filter-class>
<init-param>
<param-name>debug.pagewriter</param-name>
<param-value>true</param-value>
</init-param>
</filter>

.
.
.
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

We are telling the container that all requests to this web application should go through PageFilter. The PageFilter class is included in the sitemesh-2.1.jar, which you can download from the SiteMesh downloads page. Once downloaded, you should copy sitemesh-2.1.jar to the /WEB-INF/lib folder.

Create a page decorator configuration file

The decorators.xml file is used to declare the decorators that will be applied to your application pages. In this file, each <decorator> tag defines one decorator. The name attribute is used to give the decorator a name, while the page attribute maps the JSP page template that should be used for decorating. The <pattern> child element limits the scope of the decorator and specifies when it should be applied.

SiteMesh decoration is configured by the master decorator file WEB-INF/decorators.xml.

<?xml version="1.0" encoding="ISO-8859-1"?> 

<decorators defaultdir="/decorators">
<decorator name="layout" page="layout.jsp">
<pattern>/*</pattern>
</decorator>
</decorators>

This files instructs SiteMesh to find the master decorator file layout.jsp in the /decorators folder. The file pattern /* indicates that all files on this site are to be processed using the layout.jsp decorator.

Create a Page Template

The page template used by sitemesh to format the web pages in the demonstration app is decorators/layout.jsp. Recall that the location and name of this file was indicated in WEB-INF/decorators.xml. A template is just a jsp page that uses some custom sitemesh tags to indicates where elements should be placed on the page. The layout.jsp template below also includes a stylesheet section that will be applied to all pages.

<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %> 
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
<title>rkcole.com - <decorator:title default="Welcome!"/></title>
<style>
body {
margin: 2em;
color: white;
}
a {
color: #CCCCCC;
}
#page-container {
width: 685px;
border: 2px solid black;
background-color: #5E3732;
}
#content-container {
height: 100%;
text-align: left;
padding: 2em;
padding-top:0;
background-color: white;
color: black;
}
#page-header {
width: 100%;
padding-top:1em;
border-bottom: 1px solid gray;
border-top: 1px solid gray;
text-align: center;
}
#nav-container {
height: 300px;
width: 15%;
border-right: 1px solid #cccccc;
padding-top: 20px;
padding-left: 10px;
vertical-align: top;
}
#nav-container a {
text-decoration: none;
color: white;
}
.selected {
padding: 2px;
color: black;
border: 1px yellow dotted;
font-style: italic;
font-weight: bold;
}
#nav-container a:visited {
color: lightGray;
}
#nav-container a:hover {
color: yellow;
}
#page-footer {
width: 100%;
padding: 5px;
font-size: x-small;
border-top: 1px solid #cccccc;
text-align: center;
}
</style>

<decorator:head/>
</head>

<body>
<table id="page-container" cellpadding="0" cellspacing="0">
<tr>
<td colspan="2" id="page-header">
<%@ include file="/includes/header.jsp"%>
</td>
</tr><tr>
<td id="nav-container">
<%@ include file="/includes/navigation.jsp" %>
</td>
<td id="content-container">
<decorator:body/>
</td>
</tr><tr>
<td colspan="2" id="page-footer">
<%@ include file="/includes/footer.jsp" %>
</td>
</tr></table>
</body>
</html>

Note: The id names assigned to the table elements in layout.jsp have nothing to do with sitemesh. They are simply there to map to style elements in the stylesheet.

The layout is under the control of an HTML table element. The first row in the table is the header and is populated by includes/header.jsp. The next row has a navigation panel populated by includes/navigation.jsp in the left-hand cell and a content panel which is populated by the various content pages in the right-hand cell. The content panel is populated by the pages included in the demonstration app (ie index.jsp, page1.jsp and page2.jsp). Finally, the last row is the page footer and is populated with includes/footer.jsp.

In the demo application, when a user requests one of the jsp pages (one of index.jsp, page1.jsp or page2.jsp), SiteMesh intercepts the request, applies the layout.jsp template, and forwards it to the browser.

That's all there is to it. All the pages in a web application can be decorated in the same manner using the setup presented above.

The next section will examine a special reequirement of a navigation panel. The navigation panel needs to know the identity of the currently displayed page so that it can respond by highlighting one of it's links. Communication between the pages is examined in the next section.

Passing Meta-Tag Contents to the Decorator

One of the more powerful abilities of SiteMesh is the use of ordinary HTML meta tag to pass information from the target page (the one being decorated) to the decorator. The demonstration app uses this technique to pass a string from the target page to the navigation panel. In this case, the string indicates which of the links in the navigation panel is to be highlighted.

Each of the pages in the demonstration application includes a meta-tag to indicate itself. For example, here is page1.jsp

<html> 
<head>
<title>Page 1</title>
<meta name="selection" content="page1">
</head>

<body>
<h2>page1.jsp</h2>
<p> The programmer, like the poet, works only slightly removed from pure
thought-stuff. He builds his castles in the air, from air, creating by exertion
of the imagination. Few media of creation are so flexible, so easy to polish and
rework, so readily capable of realizing grand conceptual structures.
</p>
<p><i>Fred Brooks</i></p>
</body>
</html>

The navigation panel includes/navigation.jsp looks for this meta tag, and if present, highlights the appropriate link .

<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %> 
<decorator:usePage id="thePage" />

<% String selection = thePage.getProperty("meta.selection"); %>

<p>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<% if(selection != null && "index".equals(selection)){ %>
<a href="index.jsp" class="selected">Main</a>
<% } else { %>
<a href="index.jsp">Main</a>
<% } %>
</td>

</tr><tr>
<td>
<% if(selection != null && "page1".equals(selection)){ %>
<a href="index.jsp" class="selected">Page 1</a>
<% } else { %>
<a href="page1.jsp">Page 1</a>
<% } %>
</td>
</tr><tr>
<td>
<% if(selection != null && "page2".equals(selection)){ %>
<a href="page2.jsp" class="selected">Page 2</a>
<% } else { %>
<a href="page2.jsp">Page 2</a>
<% } %>
</td>
</tr>
</table>
</p>

ScreenShot: The Demo App

Addendum: Passing Content to the Decorator

Sitemesh includes a mechanism for passing arbitrary data between a JSP pages and the SiteMesh decorator through <content> tags. For example, on page2.jsp, the quote and author could have been passed to layout.jsp using two content tags like this:

    <content tag="quote"> When a cat is dropped, it always lands on its feet, 
and when toast is dropped... </content>
<content tag="author">John Frazee</content>

The quote and author could be accessed in layout.jsp or in the included pages by using the following:

    <decorator:getProperty property="page.quote"/>
<decorator:getProperty property="page.author"/>

Warning: SiteMesh and Large HTML Pages

Sitemesh will attempt to store the entire contents of an html page in memory prior to decorating it. Very large pages could cause memory problems.

Warning: The SiteMesh REQUEST Trap

If SiteMesh is ignoring the "excludes" directive for a particular file, it is probably because you have included "REQUEST" in the filter-mapping. REMOVE IT!!

<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>

Warning: SiteMesh and the DOJO Library

If you're using the DOJO JavaScript widgets library in a "decorator" you may find that, the widget isn't displayed (no error, just not there). The problem stems from the different root directories used by SiteMesh and DOJO. The root SiteMesh directory is /decorators while DOJO uses /script/ (depending on your naming conventions).

Fix this by adding the following line to call the setBaseScriptUri() function:

<script src="/script/dojo.js" type="text/javascript" />
<script language="JavaScript" type="text/javascript"/>
...
dojo.hostenv.setBaseScriptUri("/script");
...
<script>