Search the Catalog
Programming Jakarta Struts

Programming Jakarta Struts

By Chuck Cavaness
November 2002
0-596-00328-5, Order Number 3285
464 pages, $39.95 US $61.95 CA

Chapter 14
Using Tiles

Up to this point, not much has been said about how to organize and assemble the content and layout of JSP pages for a Struts application. In many ways, that is outside the scope of the topic of Struts. Many excellent books are available that provide strategies for organizing web content and the layout of pages.

In the Storefront application, we have used two different approaches to assembling web pages. The first approach, sometimes referred to as a straight JSP-based approach, is probably the most familiar to web designers. The JSP pages contain presentation logic along with HTML layout tags; there's no separation of the two. This approach is typically used for smaller, less complicated web applications.

The second approach uses the JSP include directive. It's used by developers for larger web applications, or after they realize how repetitive the first approach can be. If you have spent much time maintaining web applications, you know how frustrating it can be to update a site's look and feel. Using the JSP include directive allows for more reuse, which reduces the total development and maintenance costs.

A third approach, which is introduced in this chapter, describes a far better way to reduce the amount of redundant code a web application contains and, at the same time, allows you to separate the content from the layout better than the first two approaches.

Understanding Templates

Traditional GUI toolkits such as VisualWorks Smalltalk or Java Swing all provide some type of layout manager that dictates how content should be displayed within the frame or window. With typical web sites, the layout can undergo many changes, both small and large, over its lifetime. Using layouts and layout managers can help to encapsulate the physical areas of the pages within an application so that they can be altered with minimal impact to the rest of the application. Unfortunately, the JSP technology does not natively provide any direct support for layouts or layout managers. This is why the template-based approach was invented. The concept of templates is not a new one--it has been around for many years, in one form or another.

To understand how templates can actually simplify a web site's layout, let's compare it with an approach that uses the JSP include mechanism. The current index.jsp page of the Storefront application is shown in Example 14-1.

Example 14-1: The index.jsp page for the Storefront application
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
 
<html:html>
 <head>
 <title><bean:message key="global.title"/></title>
 <html:base/>
 <script language=javascript src="include/scripts.js"></script>
 <link rel="stylesheet" href="stylesheets/format_win_nav_main.css" type="text/css">
 </head>
 
 <body topmargin="0" leftmargin="0" bgcolor="#FFFFFF">
 
 <!-- Header information -->
 <%@ include file="include/head.inc"%>
 
 <!-- Menu bar -->
 <%@ include file="include/menubar.inc"%>
 
 <!--- Include the special offer -->
 <%@ include file="include/mainoffer.inc"%>	
 
 <!-- Featured items header row -->
 <table width="645" cellpadding="0" cellspacing="0" border="0">
  <tr>   
   <td width="21">
   <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/>
   </td>
   <td width="534">
   <html:img page="/images/week_picks.gif" altKey="label.featuredproducts"/>
   </td>
   <td width="1" bgcolor="#9E9EFF">
   <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/>
   </td>
   <td width="1"  bgcolor="#9E9EFF">
   <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/>
   </td>
   <td width="90" bgcolor="#9E9EFF" align="right">
   <html:img height="1" alt="" page="/images/spacer.gif" width="90" border="0"/>
   </td>
  </tr>
  <tr>
   <td>
   <html:img height="1" alt="" page="/images/spacer.gif" width="21" border="0"/>
   </td>
   <td colspan="4" bgcolor="#9E9EFF">
   <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/>
   </td>
  </tr>
 </table>
 
 <html:img height="10" alt="" page="/images/spacer.gif" width="1" border="0"/><br>
 
 <!--- Include the featured items -->
 <%@ include file="include/featureditems.inc"%>
 
 <!--- Include the copyright statement -->
 <%@ include file="include/copyright.inc"%>
 </body>
</html:html>

Although the main page uses the JSP include directive, the layout is mixed with content in the page. For example, notice that the page specifies explicitly that the head.inc include file comes first, then the menubar.inc file, the mainoffer.inc file, and so on, right down to the copyright.inc include at the bottom of the page. For every page that we want to have this particular layout, we need to add the same statements in the same order. If a customer wants the menu along the left side instead of across the top, every page will have to be changed.

The Storefront application uses the JSP include mechanism rather than a straight JSP approach. Although the include mechanism is a step in the right direction because it does reduce redundancy (imagine if we included the copyright content in every page!), it's still less efficient than a template-based approach.

Static Versus Dynamic Content

With JSP, there are two different kinds of content to include: static and dynamic. The include directive shown here:

<%@ include file="include/copyright.inc" %>

includes the source of the target page at translation/compile time. Therefore, it's not possible to include runtime content using the include directive. The JSP include directive treats a resource as a static object, and the context of the resource is included literally in the page.

In direct contrast, the include action shown here:

<jsp:include page="include/copyright.inc"/>

handles the resource as a dynamic object. The request is sent to the resource, and the result of the processing is included. Templates use a dynamic approach so that runtime expressions can be evaluated and included.

What Is a Template?

A template is a JSP page that uses a JSP custom tag library to describe the layout of a page. The template acts as a definition for what the pages of an application will look like, without specifying the content. The content is inserted into the template page at runtime. One or more pages may use the same template.

TIP: The purpose of a template is to get a consistent look and feel within an application without having to hardcode it for every page. It makes sense that most of the pages will use the same template; however, it's not uncommon to have a different look and feel for a few pages within an application and therefore to require more than one template.

Example 14-2 illustrates a template for the Storefront application.

Example 14-2: A basic template for the Storefront application
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles"%>
 
<html:html>
 <head>
  <title><bean:message key="global.title"/></title>
  <html:base/>
 </head>
 <body topmargin="0" leftmargin="0" bgcolor="#FFFFFF">
 
  <!-- Header page information -->
  <tiles:insert attribute="header"/>
 
  <!-- Menu bar -->
  <tiles:insert attribute="menubar"/>  
 
  <!-- Main body information -->
  <tiles:insert attribute="body-content"/>
 
  <!-- Copyright information -->
  <tiles:insert attribute="copyright"/>
 </body>
</html:html>

Not many new concepts are introduced in the template file in Example 14-2. The first thing that you should notice is that we are using Struts custom tag libraries. The fact that we are using the Tiles tag library as well as the HTML and Bean libraries shouldn't be too shocking; the Tiles tag library is just like any other. We'll talk in detail about the Tiles tag library later in this chapter.

The rest of the page is a mixture of HTML layout tags. You should notice that there's no content included, only insert tags where content will be inserted at runtime. You should already be familiar with the Struts tags shown here, so we won't say anything about them. The insert tag performs a role similar to that of the JSP include directive. It's basically saying that somewhere there's a variable called header, for instance, and that the attribute value of "header" should be passed to the insert tag, and the content that is produced should be inserted right here. The same thing goes for the menubar, body-content, and copyright inserts. We'll explain shortly how the "real" content is substituted for these attributes at runtime.

Notice that this layout is very similar to the one shown in Example 14-1. The only difference is that instead of explicitly including the mainoffer and featureditem includes, as Example 14-1 does, the template file includes a body-content section. This allows us to reuse the template for any page that has this generic format. Once we figure out how to supply the page-specific body content, we can reuse this template over and over again. This one file can then control the layout of multiple pages. If we need to modify the layout of the site, this is the only file we need to change--that's the real power of using a template-based approach.

The last piece of the puzzle is how the header, menubar, body-content, and copyright sections are put together to form the rendered output. The important point to remember is that the JSP page shown in Example 14-2 is the template. You still need JSP pages that supply page-specific content used by the template. For example, if we rewrite the index.jsp page from Example 14-1 using the template from Example 14-2, it will look like the one in Example 14-3.

Example 14-3: The index.jsp page for the Storefront application using a template
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
 
<tiles:insert page="/layouts/storefrontDefaultLayout.jsp" flush="true">  
  <tiles:put name="header" value="/common/header.jsp" />     
  <tiles:put name="menubar" value="/common/menubar.jsp" />     
  <tiles:put name="body-content" value="/index-body.jsp" />     
  <tiles:put name="copyright" value="/common/copyright.jsp" />     
</tiles:insert>

The first thing to notice in Example 14-3 is that the Tiles tag library is included at the top. Every page (or tile) that needs to use the Tiles tag library must include it with this line:

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>

Two tags from the Tiles library are used in Example 14-3: insert and put. (The complete set of Tiles tags and their associated attributes are discussed later in this chapter.) You already saw the insert tag in Example 14-2, but it's performing a slightly different function in Example 14-3. Two attributes are being supplied to the insert tag: page and flush. The page attribute informs the tag that this JSP page is using a particular template (or layout, in the Tiles world--layouts also are discussed later in this chapter). We are calling the template from Example 14-2 storefrontDefaultLayout.jsp. The flush attribute informs the controller to flush the page output stream before inserting content into the result page.

The put tag in Example 14-3 answers a question that we asked in the previous section: how does the page-specific content get supplied to the template? As you can see, the attributes for the put tag in this example are name and value. If you compare the values of the different name attributes, you'll see that they match up to the ones that the template file in Example 14-2 expects. When the index.jsp page from Example 14-3 is executed, the template file is processed and dynamically passed the header.jsp, menubar.jsp, index-tile.jsp, and copyright.jsp files from the put tags:

<tiles:insert page="/layouts/storefrontDefaultLayout.jsp" flush="true">  
  <tiles:put name="header" value="/common/header.jsp" />     
  <tiles:put name="menubar" value="/common/menubar.jsp" />     
  <tiles:put name="body-content" value="/index-body.jsp" />     
  <tiles:put name="copyright" value="/common/copyright.jsp" />     
</tiles:insert>

At runtime, the values of the put tags are dynamically substituted into the template file and processed. The resulting output is what gets displayed to the client.

To wrap up the discussion of templates, here is another page that uses the same template from Example 14-2 but supplies a different body-content. Example 14-4 shows the itemdetail.jsp page.

Example 14-4: The itemdetail.jsp page for the Storefront application
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
 
<tiles:insert page="../layouts/storefrontDefaultLayout.jsp" flush="true">  
  <tiles:put name="header" value="../common/header.jsp"/>
  <tiles:put name="menubar" value="../common/menubar.jsp"/>
  <tiles:put name="body-content" value="../catalog/itemdetail-body.jsp"/>  
  <tiles:put name="copyright" value="../common/copyright.jsp"/>       
</tiles:insert>

The only difference between the index.jsp page in Example 14-3 and the itemdetail.jsp page in Example 14-4 is the content supplied by the body-content attribute.

TIP: If you are still not convinced of the value of using templates, notice that the index.jsp and itemdetail.jsp pages in Example 14-3 and Example 14-4 do not specify anything about how the content should be laid out. They both reference the storefrontDefaultLayout.jsp file, which has sole responsibility for displaying the content in a prescribed format. If we want to change the layout of the site, we have to modify only the storefrontDefaultLayout.jsp file.

Installing and Configuring Tiles

Before you can use the Tiles framework, you must ensure that it's installed and properly configured within your web container. The Tiles framework is not dependent on any specific container. You will need to obtain the required files and ensure that they are placed into their proper directories within the web application.

Downloading Tiles

The Tiles framework is included with the Struts distribution. It previously was included in the contrib folder, but it is now part of the core distribution. You also can find the latest source and binary distribution, as well as other useful information, at http://www.lifl.fr/~dumoulin/tiles/index.html.

Installing the Required JARs and Misc Files

The first step is to install the required files. For Tiles, the following files must be located in the WEB-INF/lib directory:

All of these binaries should already be present if you are using Struts. You will also need to install the Tiles TLD file, tiles.tld, in the WEB-INF directory for the application.

WARNING: Don't add the tiles.jar file to the classpath of your servlet container in an attempt to avoid placing it in the WEB-INF/lib directory of each individual web application. Doing so will cause ClassNotFoundExceptions to be thrown.

You should put the tiles-config.dtd file in the WEB-INF directory, too. This DTD is used to validate Tiles definition files, which we'll discuss later in this chapter.

Adding the Tiles Tag Library

As with any other JSP tag library, you must add the Tiles library to the web application deployment descriptor before you can use it. Add the following taglib element to the web.xml file:

<taglib>
  <taglib-uri>/WEB-INF/tiles.tld</taglib-uri>
  <taglib-location>/WEB-INF/tiles.tld</taglib-location>
</taglib>

There should already be taglib elements present if you are using any of the standard Struts tag libraries. Each page that needs to use the Tiles tag library must include the following line at the top:

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>

Configuring Tiles to Work with Struts

The Tiles framework can be used with or without Struts. Depending on how you use it, there are several options for configuring it for a web application. Because this book is about Struts, we'll focus on how to bind it to a Struts application.

TIP: With earlier versions of the Tiles framework, you had to configure a special ActionServlet called ActionComponentServlet in the web.xml file. It was also necessary to configure a special RequestProcessor in the Struts controller element. This is no longer true--a Tiles plug-in is now available that will take care of all the initialization.

The Tiles plug-in is really necessary only if you are planning on using Tiles definitions. It is possible to use the Tiles libraries with Struts without configuring the plug-in. However, it doesn't hurt to configure it, and it may save you time later if you decide to use definitions.

To add the Tiles plug-in to a Struts application, add the following plug-in element to the Struts configuration file:

<plug-in className="org.apache.struts.tiles.TilesPlugin" >
  <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" />
  <set-property property="definitions-debug" value="2" />
  <set-property property="definitions-parser-details" value="2" />
  <set-property property="definitions-parser-validate" value="true" />
</plug-in>

Within the plug-in element, you can specify one or more set-property elements to pass additional parameters to the Plugin class. The definitions-config initialization parameter specifies the XML file or files containing Tile definitions. If multiple filenames are used, they must be comma-separated.

The definitions-debug parameter specifies the debug level. The allowed values are:

0

No debug information is written out.

1

Partial debug information is provided.

2

Full debug information is provided.

The default value is 0.

The definitions-parser-details parameter indicates the required level of debugging information while the definition files are being parsed. This value is passed to the Commons Digester. The allowed values are the same as those for the definitions-debug parameter. The default value is 0.

The definitions-parser-validate parameter specifies whether the parser should validate the Tiles configuration file. The allowed values are true and false. The default is true.

There is an additional parameter, not shown, called definitions-factory-class. You can create a custom definitions factory and supply the class name here. The default is org.apache.struts.tiles.xmlDefinition.I18NfactorySet.

Overview of Tiles

The Tiles framework provides a templating mechanism that allows you to separate the responsibilities of layout from those of content. As with the templates described earlier in this chapter, you have the ability to establish a layout and dynamically insert the contents of your pages into that layout at runtime. This is a powerful mechanism if you need to customize your site based on such things as internationalization, user preferences, or just the look-and-feel changes that occur in every web application sooner or later. The Tiles framework provides the following features:

There has been a Template tag library within the Struts framework for quite some time. These tags allow you to use a very basic templating approach to assemble your JSP pages in a web application. Although these tags are helpful in separating the content for a web application from its prescribed layout, the Tiles framework goes much further and actually provides a superset of the Template tag library's behavior, as well as many other features.

TIP: The Tiles framework was previously called Components, but the name was changed because that term is so overused. The Tiles documentation and source code still make reference to the old name in some places.

Cedric Dumoulin created the Tiles framework to extend the concept of templates and provide developers with more flexibility and freedom when creating web applications built with JSP technology. The content for the web applications is still driven by JSP pages and JavaBeans. However, the layout is specified within a separate JSP page or, as we'll see later, in an XML file.

What Is a Tile?

A tile is an area or region within a web page. A page can consist of just one region or be broken up into several regions. Figure 14-1 illustrates an example from the Storefront application.

Figure 14-1. The regions of the Storefront application

 

A JSP page is typically made of several regions, or tiles. There's nothing too special about the page, other than the fact that it's designed to be used with the Tiles framework and makes use of the Tiles tag library.

The most important aspect of a tile is that it is reusable. This is true for layouts as well as body content. Unlike most JSP pages, tile components are reused within an application and possibly across different applications. Other than that, there's nothing that complicated about a tile. In fact, most of the examples we've seen so far can be classified as tiles, including Example 14-2 through Example 14-4.

Using a Layout Tile

In the Tiles world, a layout is what we have been referring to as a template. A layout serves the exact same purpose as a template--that is, to assemble a group of tiles to specify the format of a page. Example 14-2 is, in fact, a Tiles layout. The syntax between Tiles and a template library like the one included with Struts is almost identical.

TIP: The Tiles framework provides a superset of the functionality included with the standard Struts template tags defined by David Geary, but it takes the concept of templates even further by providing additional functionality.

Layouts also are considered tiles. JSP pages and even entire applications can reuse layouts, and it's common to build a library of layouts that are used in many different projects. The Tiles framework comes with several prebuilt layout tiles that you can reuse or modify as needed. The included layouts are:

Classic layout
Renders a header, left menu, body, and footer

Columns layout
Renders a list of tiles in multiple columns, each of which renders its tiles vertically stacked

Center layout
Renders a header, left tile, right tile, body, and footer

Menu layout
Renders a menu with links

Tabs layout
Renders several tiles in a tabs-like fashion

Vertical box layout
Renders a list of tiles in a vertical column

Because one of the main goals of Tiles is reusability, you can reuse these layouts within your application with little or no modifications. You also have the freedom to customize the layouts in any way you need.

Planning Your Layout

It's very important that you plan your layout requirements ahead of time. Trying to decide how your site is going to look after it's already built is definitely not the right approach. This decision is typically made by a human factors team, product management, or possibly the web developers themselves. In any case, you need to develop the layout (or layouts) well ahead of any actual development.

The default layout for the Storefront application was presented in Example 14-2. This layout will be used as an example throughout this chapter.

Passing Parameters to the Layout

The layout tile shown in Example 14-2 is generic. It doesn't know anything about the itemdetail.jsp content or any of the other pages, for that matter. This is intentional, as it allows us to reuse this layout for many pages. Instead of being hardcoded within the layout page, the content is supplied or "passed" as parameters to the layout page at runtime. Let's look at the signin.jsp tile for the Storefront application, shown in Example 14-5.

Example 14-5: The signin.jsp tile for the Storefront application
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
 
<tiles:insert page="../layouts/storefrontDefaultLayout.jsp" flush="true">
  <tiles:put name="header" value="../common/header.jsp"/>
  <tiles:put name="menubar" value="../common/menubar.jsp"/>
  <tiles:put name="body-content" value="../security/signin-body.jsp"/>  
  <tiles:put name="copyright" value="../common/copyright.jsp"/>        
</tiles:insert>

The purpose of the put tags in Example 14-5 is to supply the layout tile, which is specified in the enclosing insert tag, with content. The values of the name attributes in Example 14-5 (as in the other tiles shown in Example 14-3 and Example 14-4) must match the ones that the layout tile is expecting.

TIP: The insert tag optionally can include an ignore attribute that will cause the tag to not write out anything when it can't find a value for an expected attribute. By default, a runtime exception is thrown when an attribute has not been supplied.

The Tiles Tag Library

This section introduces the JSP custom tags used by the Tiles framework. Table 14-1 lists the tags available to the framework. The tags are very similar to the ones provided in any template-based framework, but Tiles contains more functionality.

Table 14-1: Tags within the Tiles tag library

Tag name

Description

add

Add an element to the surrounding list.

definition

Create a tile component definition.

get

Get the content from request scope that was put there by a put tag.

getAsString

Render the value of the specified tile/component/template attribute to the current JspWriter.

importAttribute

Import a tile's attribute into the specified context.

initComponentDefinitions

Initialize a tile definitions factory.

insert

Insert a tiles component.

put

Put an attribute into a tile context.

putList

Declare a list that will be passed as an attribute.

useAttribute

Use an attribute value inside a page.

The insert Tag

The insert tag is responsible for inserting content into a page. In a layout tile, the insert tag prescribes where the content will go using attribute values. In a regular, non-layout tile, the insert tag is used to retrieve a layout and allow content to be passed to the layout using put tags. Table 14-2 lists the attributes for the insert tag.

Table 14-2: Attributes for the insert tag

Attribute name

Description

attribute

The name of an attribute in the current tile/component context. The value of this attribute is passed to the name attribute.

beanName

The name of the bean used as a value. The bean is retrieved from the specified context, if any. Otherwise, the pageContext.findAttribute( ) method is used. If the beanProperty attribute is also specified, retrieve the value from the corresponding bean property.

If the bean (or bean property value) is an instance of one of the Attribute classes (Direct, Instance, etc.), the insertion is done according to the class type. Otherwise, the toString( ) method is called on the bean, and the returned String is passed to the name attribute.

beanProperty

The name of the bean property. If specified, the value is retrieved from this property.

beanScope

The context scope the bean can be found within. If not specified, the pageContext.findAttribute( )method is used. The scope can be any JSP scope, component, or template. In the two latter cases, the bean is searched in the tile/component/template context.

component

A string representing the URI of a tile or template. The template, page, and component attributes have exactly the same behavior.

controllerUrl

The URL of a controller called immediately before the page is inserted. The URL usually denotes a Struts action. The controller (action) is used to prepare data to be rendered by the inserted tile.

Only one of controllerUrl or controllerClass should be used.

controllerClass

The class type of a controller called immediately before the page is inserted. The controller is used to prepare data to be rendered by the inserted tile.

Only one of controllerUrl or controllerClass should be used. The class must implement or extend one of the following: org.apache.struts.tiles.Controller, org.apache.struts.tiles.ControllerSupport, or org.apache.struts.action.Action.

definition

The name of the definition to insert. Definitions are defined in a centralized file. For now, only definitions from a factory can be inserted with this attribute. To insert a definition defined with the definition tag, use beanName="".

flush

true or false. If true, the current page output stream is flushed before tile insertion.

ignore

If this attribute is set to true and the attribute specified by the name does not exist, simply return without writing anything. The default value is false, which will cause a runtime exception to be thrown.

name

The name of an entity to insert. The search is done in this order: definition, attribute, then page.

page

A string representing the URI of a tile or template. The template, page, and component attributes have exactly the same behavior.

role

If the user is in the specified role, the tag is taken into account; otherwise, the tag is skipped and the content is not written out.

template

A string representing the URI of a tile or template. The template, page, and component attributes have exactly the same behavior.

Several examples of the insert tag were shown earlier in this chapter.

The definition Tag

The definition tag is used to create a tile (template) definition as a bean. The newly created bean will be saved under the specified id, in the requested scope. The definition tag has the same syntax as the insert tag. The new definition can extend a definition described in the definition factory (XML file) and may overload any previously defined parameters. Table 14-3 lists the attributes supported by the definition tag.

Table 14-3: Attributes for the definition tag

Attribute name

Description

extends

The name of a parent definition that is used to initialize this new definition.The parent definition is searched in the definitions factory.

id

The name under which the newly created definition bean will be saved. This attribute is required.

page

URL of the template/component to insert. Same as template.

role

The role to check before inserting this definition. If the role is not defined for the current user, the definition is not inserted. Checking is done at insertion time, not during the definition process.

scope

The variable scope in which the newly defined bean will be created. If not specified, the bean will be created in page scope.

template

A string representing the URL of a tile/component/template (a JSP page).

The following fragment illustrates how to use the definition tag in a JSP page:

<tiles:definition 
  id="storefront.default" 
  page="/layouts/storefrontDefaultLayout.jsp" 
  scope="request">
    <tiles:put name="header" value="/common/header.jsp" />
    <tiles:put name="menubar" value="/common/menubar.jsp" />
    <tiles:put name="copyright" value="/common/copyright.jsp" />  
</tiles:definition>

A complete example is shown later, in "Declaring Definitions in a JSP Page" of this chapter.

The put Tag

The put tag is used to pass attributes to a tile component. This tag can be used only inside the insert or definition tags. The value (or content) of the put tag is specified using the value attribute or the tag body. It is also possible to specify the type of the value:

string
Content is literally translated.

page or template
Content is included from specified URL. Name is used as URL.

definition
Content comes from specified definition (from factory). Name is used as definition name.

If the type attribute is used, it is taken into account by the get or insert tags inside the inserted tile. If the type attribute is not specified, the content is untyped, unless it comes from a typed bean.

TIP: Setting direct="true" is equivalent to setting type="string".

Table 14-4 lists the attributes for the put tag.

Table 14-4: Attributes for the put tag

Attribute name

Description

beanName

The name of the bean used to retrieve the value. The bean is retrieved from the specified context, if any. Otherwise, the pageContext.findAttribute( )method is used. If beanProperty is specified, retrieve the value from the corresponding bean property.

beanProperty

The property name in the bean. If specified, the value is retrieved from this property.

beanScope

The scope used to search for the bean. If not specified, the pageContext.findAttribute( ) method is used. The scope can be any JSP scope, "tile", "component", or "template". In the three later cases, the bean is searched in the tile/component/template context.

content

Content that's put into tile scope. This attribute is equivalent to the value attribute and was added for compatibility with the JSP template tags.

direct

How the content is handled: true means content is printed directly; false means content is included. false is the default. This is another way to specify content type. If direct="true", content is "string"; if direct="false", content is "page". This attribute was added for compatibility with the JSP template tags.

name

The name of the attribute.

role

If the user is in the specified role, the tag is taken into account. Otherwise, the tag is skipped and the content is not written out.

type

The content type. Valid values are "string", "page", "template", or "definition".

value

The attribute value--can be a String or an Object. The value can come from a direct assignment (value="aValue") or from a bean. One of value, content, or beanName must be present.

The putList Tag

The putList tag creates a list that will be passed as an attribute to a tile. The list elements are added using the add tag. This tag can be used only inside the insert or definition tag. Table 14-5 lists the attribute for the putList tag.

Table 14-5: Attribute for the putList tag

Attribute name

Description

name

The name of the List. This attribute is required.

The add Tag

The add tag adds an element to the surrounding list. This tag can be used only inside the putList tag. The value can come from a direct assignment (value="aValue") or from a bean. One of value or beanName must be specified. Table 14-6 lists the attributes for the add tag.

Table 14-6: Attributes for the add tag

Attribute name

Description

beanName

The name of the bean used to retrieve the value. The bean is retrieved from the specified context, if any. Otherwise, the pageContext.findAttribute( ) method is used. If beanProperty is specified, retrieve the value from the corresponding bean property.

beanProperty

The bean property name. If specified, the value is retrieved from this property.

beanScope

The scope used to search for the bean. If not specified, the pageContext.findAttribute( ) method is used. The scope can be any JSP scope, "component", or "template". In the two latter cases, the bean is searched in the tile/component/template context.

content

The value is the same as the value attribute. This attribute was added for compatibility with the JSP template tags.

direct

How the content is handled: true means content is printed directly; false means content is included. false is the default.

role

If the user is in the specified role, the tag is taken into account; otherwise, the tag is ignored (skipped). The role isn't taken into account if the add tag is used in a definition.

type

The content type: "string", "page", "template", or "definition". If the type attribute is not specified, content is untyped, unless it comes from a typed bean.

value

The value to be added. Can be a String or Object.

The get Tag

The get tag retrieves content from the tile context and includes it in the page. Table 14-7 lists the attributes for the get tag.

Table 14-7: Attributes for the get tag

Attribute name

Description

flush

true or false. If true, the current page output stream is flushed before tile insertion.

ignore

If this attribute is set to true and the attribute specified by the name does not exist, simply return without writing anything. The default value is false, which will cause a runtime exception to be thrown.

name

The name of the content to get from the tile scope. This attribute is required.

role

If the user is in the specified role, the tag is taken into account; otherwise, the tag is ignored.

The getAsString Tag

The getAsString tag retrieves the value of the specified tile attribute property and renders it to the current JspWriter as a String. The usual toString( ) conversion is applied to the value. If the named value is not found, a JSPException will be thrown. Table 14-8 lists the attributes for the getAsString tag.

Table 14-8: Attributes for the getAsString tag

Attribute name

Description

ignore

If this attribute is set to true and the attribute specified by the name does not exist, simply return without writing anything. The default value is false, which will cause a runtime exception to be thrown.

name

The attribute name. This attribute is required.

role

If the user is in the specified role, the tag is taken into account; otherwise, the tag is ignored.

The useAttribute Tag

The useAttribute tag declares a Java variable and an attribute in the specified scope, using the tile's attribute value. The variable and attribute will have the name specified by id, or the original name if not specified. Table 14-9 lists the attributes for the useAttribute tag.

Table 14-9: Attributes for the useAttribute tag

Attribute name

Description

classname

The class of the declared variable.

id

The declared attribute and variable name.

ignore

If this attribute is set to true and the attribute specified by the name does not exist, simply return without error. The default value is false, which will cause a runtime exception to be thrown.

name

The tile's attribute name. This attribute is required.

scope

The scope of the declared attribute. Defaults to "page".

The importAttribute Tag

The importAttribute tag imports the attribute from the tile to the requested scope. The name and scope attributes are optional. If not specified, all tile attributes are imported in page scope. Once imported, an attribute can be used like any other bean from the JSP context. Table 14-10 lists the attributes for the importAttribute tag.

Table 14-10: Attributes for the importAttribute tag

Attribute name

Description

ignore

If this attribute is set to true and the attribute specified by the name does not exist, simply return without error. The default value is false, which will cause a runtime exception to be thrown.

name

The tile's attribute name. If not specified, all attributes are imported.

scope

The scope into which the attribute is imported. Defaults to "page".

The initComponentDefinitions Tag

The initComponentDefinitions tag initializes the definitions factory. Table 14-11 lists the attributes for the tag.

Table 14-11: Attributes for the initComponentDefinitions tag

Attribute name

Description

classname

If specified, the classname attribute of the factory to create and initialize.

file

The definition file's name. This attribute is required.

Using Definitions

The tiles shown so far add value to an application because they organize the layout of a page in a single resource, the layout JSP page. This can save development time and, more importantly, the time it takes to change the layout for an application. However, there is a problem with the approach used in the Storefront application shown earlier. In each of the non-layout tiles, there is redundant code that specifies what content to use for the header, menubar, and copyright content--the same attributes are being passed in every page. This may not always be the case, but in general, these values will be constant throughout an application. For instance, the same copyright is typically shown on every page.

It's redundant to have to specify these in every tile. Ideally, we could declare these attributes in one place, and the tiles could include just the page-specific attributes where needed. Tiles definitions provide just such functionality. A definition allows you to statically specify the attributes that are used by a template, which in turn allows you to specify only the page-specific attributes in your tiles. Definitions enable you to:

Definitions can be declared in a JSP page or an XML file. Either way, you should strive to keep the definitions in a central place. If you are planning on using a JSP page to specify your definitions, put all the definitions for your application in a single page. Don't spread your definitions throughout your site, as that will only make maintenance more difficult.

Declaring Definitions in a JSP Page

As was previously mentioned, there are two locations in which you can specify definitions: a JSP page or an XML file. We'll discuss the JSP page approach first.

To use the JSP approach, create a JSP page and declare all of your definitions in that file. For the Storefront application, we've created a file called storefront-defs.jsp and put the default definition in it, as Example 14-6 shows.

Example 14-6: Declaring tiles definitions in a JSP page
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
 
<tiles:definition 
  id="storefront.default" 
  page="/layouts/storefrontDefaultLayout.jsp" 
  scope="request">
    <tiles:put name="header" value="/common/header.jsp" />
    <tiles:put name="menubar" value="/common/menubar.jsp" />
    <tiles:put name="copyright" value="/common/copyright.jsp" />  
</tiles:definition>

The definition in Example 14-6 uses the same layout tile used earlier. The common files that were spread through the various tiles are now located in the definition file. This makes changing their values much easier. For instance, if we wanted to specify a different copyright page, the only place to change it would be in the definition file; we would not have to modify every JSP page.

The definition tag syntax looks very similar to the syntax for the insert tags shown earlier. We just need to provide an id attribute and switch the path attribute to the page attribute. Also, the default scope for the definition tag is page. It was set to request scope here to give it a little broader scope.

To take advantage of the definition, the tile components need to be able to access it. Because we have given the definition request scope and it will exist only for the lifetime of a request, we need a way to include the definition in the various JSP pages. Fortunately, we already know how to include a JSP page in another page using the JSP include directive. Example 14-7 shows what the signin.jsp page looks like using the JSP definition file.

Example 14-7: The signin.jsp page using a tile definition
<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>
 
<%@include file="../common/storefront-defs.jsp" %>
 
<tiles:insert beanName="storefront.default" beanScope="request">  
  <tiles:put name="body-content" value="../security/signin-body.jsp"/>  
</tiles:insert>

With this approach, the tile components have to insert only the page-specific content. Compare Example 14-7 to Example 14-5. Notice that the signin.jsp file using the definition needs to provide only the page-specific content, the sign-body.jsp file.

Declaring Definitions in a Configuration File

You also have the option of declaring definitions in a centralized XML file. Whether you use the JSP or the XML alternative really depends on your requirements. With the XML approach, you won't need to use the include directive shown earlier.

Creating a definition configuration file

To use the XML approach, create an XML file that follows the syntax of the tiles-config.dtd file. The definition XML file should be placed in the WEB-INF directory, as with the other application meta-information. The DTD should also be placed in the WEB-INF directory. Example 14-8 shows an example of a definition XML file.

Example 14-8: The Storefront definition XML file
<!DOCTYPE tiles-definitions PUBLIC
  "-//Apache Software Foundation//DTD Tiles Configuration//EN"
  "http://jakarta.apache.org/struts/dtds/tiles-config.dtd">
 
<tiles-definitions>
 <definition name="storefront.default" path="/layouts/storefrontDefaultLayout.jsp">    
  <put name="header" value="/common/header.jsp" />
  <put name="menubar" value="/common/menubar.jsp" />
  <put name="copyright" value="/common/copyright.jsp" />  
 </definition>
</tiles-definitions>

There's not much difference between the definition format specified in the JSP file in Example 14-6 and the XML file in Example 14-8. The XML file uses a slightly different syntax, but it's still very similar.

WARNING: The two formats are just similar enough to cause problems. Notice that in the JSP definition, you use the put tag:

<tiles:put name="body-content" value="../security/signin-body.jsp"/>

but in the XML definition, you use the put element:

 <put 
  name="header" 
  value="/common/header.jsp" />

Make sure that you don't get these two confused, as this can be a difficult bug to track down.

Each definition should have a unique name. JSP tags and pages use the name to retrieve the definition. It can't be used as a URL, however; it's only a logical name for the definition.

Extending Tile Definitions

One of the most powerful features of tile definitions is the ability to create new definitions by extending existing ones. All attributes and properties of the parent definition are inherited, and you can override any attribute or property. To extend a definition, add the extends attribute. Example 14-9 shows an example of a definition named storefront.custom extending the storefront.default definition.

Example 14-9: Definitions can extend other definitions
<tiles-definitions>
 <definition name="storefront.default" path="/layouts/storefrontDefaultLayout.jsp">    
  <put name="header" value="/common/header.jsp" />
  <put name="menubar" value="/common/menubar.jsp" />
  <put name="copyright" value="/common/copyright.jsp" />  
 </definition>
</tiles-definitions>
 
<tiles-definitions>
 <definition name="storefront.custom" extends="storefront.default">    
  <put name="copyright" value="/common/new-copyright.jsp" />  
 </definition>
</tiles-definitions>

In Example 14-9, all of the attributes in the storefront.default definition are inherited. However, the storefront.customer definition overrides the value for the copyright attribute with an alternate copyright page. This is a very powerful feature. If you have multiple child definitions all extending a root definition, changing a value in the root definition changes it for all the children. Thus, you can change the layout in the root definition and have it changed for all child definitions.

Using Definitions as Forwards in Struts

Tiles definitions can also be used as Struts forwards, instead of actual URLs. To use definitions in this manner, you first create the definitions:

<tiles-definitions>  
  <definition name="storefront.default" path="/layouts/storefrontDefaultLayout.jsp">    
    <put name="header" value="/common/header.jsp" />
    <put name="menubar" value="/common/menubar.jsp" />
    <put name="copyright" value="/common/copyright.jsp" />  
  </definition>
  
  <definition name="storefront.superuser.main" extends="storefront.default">    
    <put name="header" value="/common/super_header.jsp" />
    <put name="menubar" value="/common/super_menubar.jsp" />
    <put name="copyright" value="/common/copyright.jsp" />  
  </definition>  
</tiles-definitions>

This fragment shows two definitions, the standard default definition and a second one that defines the layout for a "superuser." A superuser might be someone that frequents the site and places many orders, and such a user might be given more options on the menu bar to facilitate faster ordering.

In the Struts configuration file, we need to define the forwards that use these definitions:

<global-forwards>
 <forward name="Super_Success" path="storefront.superuser.main" />   
</global-forwards>

You can then use the Super_Success forward to send the user to the storefront.superuser.main definition just as you would for any other forward.

Internationalization Support with Tiles

Although the Struts framework provides certain I18N capabilities that can be used with Tiles, Tiles also provides the ability to select a particular tile based on the user's locale. To support this feature in your application, you need to create a different Tiles definition file for each locale that you need to support. For example, if you need to support a set of definitions for the U.S. locale and a separate set for the German locale, you must create two separate definition files:

The suffix naming conventions follow the ones set by the java.util.ResourceBundle, which is also used by the resource bundles for Struts. When a request for a definition is made, the correct definition is determined by the included locale.

TIP: As with regular Java resource bundles, you should always provide a base definition that is used when no locale is provided or when an unsupported locale is used. No language or country suffix is appended to the name of the Tiles base definition file.

Once you have created the locale-specific definition files and placed them in the WEB-INF directory, the only other necessary step is to ensure that a Locale is stored in the user's HttpSession. The Tiles framework depends on the same Locale instance that Struts uses to determine which definition file to use. You will need to ensure that the Struts framework is storing the user's Locale in the session. This is accomplished by setting the locale attribute to true in the controller element. See for more information on the controller element.

That's all there is to it. Note that you should still rely on the Struts resource bundle for locale-sensitive resources such as text, messages, and images. The I18N support in Tiles should be used for differences in layout based on the locale. Struts and Tiles work very well together to provide complete I18N support.

Back to: Programming Jakarta Struts


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com