O'Reilly Book Excerpts: Programming Jakarta Struts
Programming Jakarta Struts: Using Tiles, Part 4
We conclude this book excerpt series on using tiles from Programming Jakarta Struts by showing you how to use definitions. You'll also find coverage on support for internationalization of tiles.
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
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:
- Screen definitions
- Centralize declaration of page description
- Avoid repetitive declaration of nearly identical pages (by using definition inheritance)
- Avoid creation of intermediate components used to pass parameters
- Specify the name of a definition as a
forwardin the Struts configuration file
- Specify the name of a definition as component parameters
- Overload definition attributes
- Use a different copy of a component, depending on the locale (I18N)
- Use a different copy of a component, depending on a key (this might be used to show different layouts depending on the client type)
In This Series
Programming Jakarta Struts: Using Tiles, Part 2
Programming Jakarta Struts: Using Tiles, Part 1
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.
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 (in the previous article). 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
<tiles:put name="body-content" value="../security/signin-body.jsp"/>
but in the XML definition, you use the
<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
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
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.
Chuck Cavaness is a graduate from Georgia Tech with degrees in computer engineering and computer science. He has built Java-based enterprise systems in the healthcare, banking, and B2B sectors. He is also the author of two O'Reilly books, Programming Jakarta Struts and Jakarta Struts Pocket Reference.
Return to ONJava.com.