Almost every web application has more than one page. And more often that not, you want to apply a consistent theme or layout across all those pages (or perhaps distinct layouts for each subset of pages). In the very beginning a common approach was to write JSPs like this:
This worked reasonably well for simple cases, but runs into problems. It's also easy to screw up the formatting by not including the JSPs at exactly the right point. Or maybe, because you wanted to insert scripts or styles into the HTML <head> you ended up having each JSP define the <head> and open and close the <body> etc. The more you do the easier it is to become inconsistent. Not to mention that you have to change every page in the site if you want to change your layout (C-Clamp looking a little dated?). And that it's now virtually impossible to have pages render in different layouts without modifying the page.
I'm not going to pretend this problem hasn't been solved. It has. Tools exist like Tiles and Sitemesh. But I don't think it's been solved in a way that is ridiculously easy to use! Tiles, while very full featured, is monstrously complex for how 99% of people use it. I like Sitemesh a lot, and it provides some things that Stripes' layout tags (which we'll get to in a second) don't. But it requires some XML config, and it requires another library etc. etc. So Stripes provides a very simple solution to the problem, that is quite powerful. If it meets your needs I'd recommend you use it. If not, take a look at Sitemesh.
Stripes includes three simple tags for creating and using layouts. They are:
- <stripes:layout-definition>: defines a re-usable layout
- <stripes:layout-component>: defines a component within a layout
- <stripes:layout-render>: renders a layout within a page
Stripes uses JSPs to both define and render the layouts. The following is an example of a really simple layout, with a consistent header and footer, using the Stripes layout tags:
When this layout is rendered the following things happen:
- Content outside of the <stripes:layout-definition> is blithely ignored
- Any content inside the <stripes:layout-definition> but outside of a <stripes:layout-component> is always rendered
- When a <stripes:layout-component> is encountered its body is rendered unless it has been overridden, in which case the overridden value is rendered instead
Let's take a look at a Hello World page that uses this layout.
Notice how the "name" of the layout is simply the path to the JSP that defines the layout. In this example we use a <stripes:layout-component> tag again, but this time to override the value of the "pageContent" component of the layout. Any number of components in the layout can be overridden. In the example layout above there is a component called "html_head". A page can override that component, not to replace the html head in the layout, but to contribute to it (perhaps to add a <script> block or some meta tags).
|Use absolute path names|
Always use absolute path names, starting with a forward slash /, in the name= attribute of the stripes:layout-render tag.
|Use valid Java identifiers as component names|
Although using a component name such as "body-content" (with a hyphen in the name) might work in some situations, it can cause problems that are hard to track down. Please consider using valid Java identifiers as component names as a best practice. In particular, use underscores instead of hyphens, as in "body_content", do not use spaces in the name, and so on.
Already this is pretty useful, but say you want your pages to look even more standard. Perhaps the page title should always be displayed in a particular style, and always in the same place. Perhaps you want to make the window title include the page title too. Well, you could force your users to provide <stripes:layout-components> for each little thing like the page title, but that starts to feel clunky.
For just this reason the <stripes:layout-render> tag accepts dynamic attributes. This means you can supply any value you like as an attribute to the <stripes:layout-render> tag. All such attributes are made available to the layout definition as page context attributes. And since you can refer to page context attributes using EL, and all sorts of tags, that's pretty handy. Imagine we modified our JSP example above to:
we could then use the pageTitle in our layout definition:
But suppose in some cases the page title isn't quite so simple? Perhaps we want to include the user's name and do some formatting or internationalization? Well, it just so happens that layout-components are also made available in the page context under their name. So we could happily define another JSP that passes the pageTitle this way:
There are a few points worth noting here:
- You can pass as many parameters to a layout as you like
- The parameters can be either attributes of the render tag, or components within the render tag
- But: only components can be pulled into the layout using the <stripes:layout-component> tag - render attributes are only available as page context attributes
- While layout components must generate a String, render attributes can be of any type (e.g. you can pass a List or a User to the layout)
All of the examples thus far have focused on using a single layout to lay out an entire page. In this section we'll cover using layouts for page fragments and nested or inherited layouts.
This might be obvious, but you can also use a layout to control how a small piece of a page renders. Using the layout tags in this way is very similar to using the new JSP tag files which allow you to write custom tags using JSP fragments. With on important difference. While you can pass the result of JSP fragments to a tag file, those fragments cannot contain any scriptlets. Often this isn't a problem, but sometimes it can get in the way. Anyway, you can imagine that perhaps you want to always display images in a standard way, with a caption. You might define a layout like:
And then use that in your JSP to give your images a consistent look and feel:
Now when your client suddenly decides they want captions on the side instead of below images, you can just go change the layout, and not have to update every JSP page in the entire site. Excellent.
Now suppose you've come this far and your site looks great, all the navigation is consistent, but you have a bunch of pages that all do searches for various kinds of things, but they all look slightly differently. Some have the search criteria above the results, some below. Some have the buttons left aligned, some right aligned. Etc. You could define a layout for searches, and re-use the global layout! Perhaps it would start out like:
In this case we see one layout definition that uses another layout (search uses the default layout) but enforces additional structure on the pageContent component of the default layout.
|Because we have a <stripes:layout-render> tag nested inside a <stripes:layout-definition> tag, any <stripes:layout-component> tags inside the render tag will be bound to the render tag - not the definition tag. That's why, in this example, we use EL expressions (inputs, buttons and results) to import the components from page context instead of <stripes:layout-component> tags.|