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:
· 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 forward in 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)
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.
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.
<%@ 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.
<%@ 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.
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.
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.
<!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.
|
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.
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.
<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.
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.