9.2 Page Design

I l @ ve RuBoard

This section describes practices related to the overall design of JSP pages for an application, such as flexible solutions for a consistent look and feel, preparing for a multilingual user interface, and making the application usable for devices with different capabilities.

9.2.1 Put Shared Page Elements in Separate Files

Most web sites need to maintain a consistent look and layout for all web pages, typically with a common header, footer, and navigation menus , as well as color and font choices. For a static site, site development tools typically let you apply a common template that provides this look and feel to all pages, but they are often not up to the task if the shared parts are dynamic (e.g., a navigation menu hides or exposes submenus depending on the selected page).

Creating the layout and shared content for the first page and then copying it for all the other pages ”just changing the main content part ”is, of course, one way to provide a consistent look for all pages in an application. It's not a very efficient way, though, because all pages need to be changed when you want to modify the shared parts.

A more efficient way is to first identify all parts that should be shared by all pages and put them in separate files. Using JSP include actions and directives, you can then include them in all pages, as shown in Example 9-1.

Example 9-1. Including shared parts in a JSP page
 <%@ page contentType="text/html" %>  <%@ include file="/shared/header.htmlf" %>  <table width="90%">   <tr>     <td valign="top" align="center" bgcolor="lightblue">  <jsp:include page="/shared/navigation.jsp" />  </td>     <td valign="middle" align="center" width="80%">        The main content for this page     </td>   </tr> </table>  <%@ include file="/shared/footer.htmlf" %>  

The shared parts can contain only static content, such as the header and footer files in this example, or JSP elements, as exemplified by the navigation file. None of the files is a complete HTML page. For instance, the header.htmlf file contains only the start tags for the <html> and <body> elements, and the footer.htmlf file contains the corresponding end tags. The htmlf file extension ("f" as in " fractional ") is used here to indicate this. Note that the file for the navigation menu uses the standard .jsp extension; while it doesn't generate a complete HTML response, it's still a syntactically complete JSP page.

9.2.2 Use a Layout Page to Merge Shared Parts and Main Content

A potential problem with including shared parts in every page is that while you need to change the content of the shared parts in only one place, global layout, color, and font changes are still not easy to make. For instance, if you want to add a "news flash" section on the righthand side of all pages, you must edit every file and add this content.

A more powerful way to provide a consistent look and feel is to turn things around: instead of letting each page include the shared parts, let one layout page include both the shared parts and the main content of each page. Example 9-2 shows what such a layout page might look like.

Example 9-2. A common layout page that includes all parts of the real page (layout.jsp)
 <%@ page contentType="text/html" %>  <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>   <c:import url="/vars/default.jsp" />   <c:catch>   <c:import url="/vars/${param.page}.jsp" />   </c:catch>   <%@ include file="/shared/header.jspf" %>  <table width="90%">   <tr>     <td valign="top" align="center" bgcolor="lightblue">  <c:import url="/shared/navigation.jsp" />  </td>     <td valign="middle" align="center" width="80%">  <c:import url="/pages/${param.page}.jsp" />  </td>   </tr> </table>  <%@ include file="/shared/footer.htmlf" %>  

When this page is invoked with a URL that includes a query string parameter named page with the name of the main content page, it renders a response with all the shared portions as well as the main content for the specified page. The JSTL <c:import> action is used here in favor of the standard <jsp:include> action, primarily to be able to dynamically build the URL for the main content page based on the page request parameter without using Java scripting code.

Another detail of interest is the section at the beginning of the page that imports a page named /vars/default.jsp and another page in the /vars directory corresponding to the requested main page. These variable-setter pages add request attribute values that are used to adjust the dynamic content in the shared parts of the page for a specific page. Example 9-3 shows the header.jspf file, and Examples Example 9-4 and Example 9-5 show how the pages imported from the /vars directory set the JSTL Expression Language (EL) variables used in the header file.

Example 9-3. Shared header file (/shared/header.jspf)
 <html>   <head>     <title><c:out value="  ${mainTitle}  :  ${subTitle}  " /></title>   </head>   <body bgcolor="white">     <h1>My Site</h1> 
Example 9-4. Page setting default values for variables used in the header (/vars/default.jsp)
 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>  <c:set var="mainTitle" scope="request" value="Welcome to My Site" />  
Example 9-5. Page setting page-specific values for variables used in the header (/vars/page1.jsp)
 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>  <c:set var="subTitle" scope="request" value="Page 1" />  

To make the page-specific variable-setting page optional, place the <c:import> action within the body of a <c:catch> element; if the page doesn't exist, <c:import> throws an exception, which the <c:catch> action catches and ignores.

The rest of the layout page in Example 9-2 contains static HTML elements for the layout of the page, and JSP elements for including the shared parts as well as the request-specific main content. If a shared part is added, or if the page layout changed, only the layout.jsp needs to be changed. Font styles and colors can also be maintained in the layout.jsp page using Cascading Style Sheets (CSS).

9.2.3 Choose an Appropriate Include Mechanism

JSP provides two ways to include content in a page: the include directive ( <%@ include file=" . . . " %> ) and the include action ( <jsp:include page=" . . . " /> ). As shown earlier, JSTL adds an import action ( <c:import url=" . . . " /> ) to the mix.

The include directive and the include action both include content from resources that belong to the same web application as the including file. The directive includes the source of the specified resource, either a static file or a file with JSP elements. When a JSP file is included this way, it shares the page scope (and all other scopes) as well as all scripting variables with the including file. The included file is rarely a syntactically complete JSP or HTML page, and I recommend that a different file extension be used to highlight this fact. Any extension will do, but good choices are .jspf and .htmlf , in which "f" stands for "fractional" (as described earlier). The JSP 1.2 specification leaves detecting changes in a file included by the include directive as an optional feature. In a container that doesn't detect these changes, changes to an included file have no effect until you force a recompile of the including file (e.g., by removing the corresponding class file or "touching" the file).

The include action, on the other hand, includes the response produced by executing the specified resource, either a JSP page or a servlet. The included resource has access to the same scopes as the including file (except for the page scope), and the container always detects when the including file changed and recompiles it when needed.

Finally, the JSTL import action includes content from resources in the same web application, a separate web application in the same container, or an external server accessible through a supported protocol (e.g., HTTP or FTP). For a resource in the same web application, it works exactly like the standard include action.

The following rules of thumb help you pick the most appropriate include mechanism:

  • Use the include directive ( <% include . . . %> ) for a file that rarely changes, which is typically true for things such as headers and footers. Because the including and included files are merged in the translation phase, there's no runtime overhead. Be aware, though, that including large files can cause the generated _jspService( ) method to exceed the size limit for a Java method (64 KB) in some containers. Even in a container with a more sophisticated code-generation algorithm, using the include directive means the file is replicated in the class files for all JSP pages that include it, increasing the overall memory requirements for the application.

  • Use the include directive ( <% include . . . %> ) if the included file must set response headers, or redesign the application so that included pages never have to do this. Section 9.3.2 later in this chapter describes this issue in more detail.

  • Use either the standard include action ( <jsp:include> ) or the JSTL import action ( <c:import> ) for a file that is likely to change ”for instance, a navigation menu or a file containing a shared part with lots of layout and style options (such as a "news flash" box). Of the two, the JSTL import action is more flexible, as it can include both local and external resources, and it supports JSTL EL expressions. It's also more strictly defined in terms of what happens when the include fails, so it can safely be combined with JSTL <c:catch> for fine-grained error handling. Due to its flexibility, it can be slightly less efficient than the standard include action, but not enough to matter in most cases.

9.2.4 Consider Using a Shared Configuration File

Besides shared content, an include directive can also include a file that contains shared page attributes, such as a page directive with an errorPage attribute and taglib directives for all libraries used in the application:

 <%@ page errorPage="/shared/error.jsp" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %> <%@ taglib prefix="ora" uri="orataglib" %> 

This way, global changes can be made in one place ”for instance, changing a taglib directive's uri attribute when upgrading to a later version.

9.2.5 Consider Using Filters to Simplify URLs

One drawback with the simple templating solution implemented by a layout page is that all links to the JSP pages must include the layout page name plus a query string ”e.g., layout.jsp?page=page1 . Example 9-6 shows parts of the navigation menu page with this type of link.

Example 9-6. Navigation page with links to layout page
 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>     <table bgcolor="lightblue">   <tr>     <td>       <c:choose>         <c:when test="${param.page =  = 'page1'}">           <b>Page 1</b>         </c:when>         <c:otherwise>           <a href="<c:url value="  layout.jsp?page=page1  " />">Page 1</a>         </c:otherwise>       </c:choose>     </td>   </tr>    . . .  </table> 

If you want to be able to use more traditional URLs instead, you can use a filter. The filter component type was introduced in the Servlet 2.3 specification. A filter is invoked before a request is delivered to the target servlet or JSP page and is a powerful tool for applying request and response processing to a group of web application resources. The filter shown in Example 9-7 converts URLs of the form pageName.jsp to URLs of the form layout.jsp?page=pageName , which are behind the scenes at runtime.

Example 9-7. Filter that allows the layout page to be invoked with regular URLs
 package com.ora.j2eebp.jsp;     import java.io.*; import java.net.*; import javax.servlet.*; import javax.servlet.http.*;     public class URLConversionFilter implements Filter {         private static final String LAYOUT_PAGE = "/layout.jsp";     private static final String PAGE_PARAM = "page";         private FilterConfig config;         public void init(FilterConfig config) throws ServletException {         this.config = config;     }         /**      * Converts the request URL and forwards to the real resource URL      */     public void doFilter(ServletRequest request, ServletResponse response,         FilterChain chain) throws IOException, ServletException {             HttpServletRequest httpReq = (HttpServletRequest) request;         HttpServletResponse httpResp = (HttpServletResponse) response;             String currURL = httpReq.getServletPath(  );         // Strip off the leading slash.         currURL = currURL.substring(1);         String pagePath =              currURL.substring(0, currURL.lastIndexOf(".jsp"));         String queryString = httpReq.getQueryString(  );         StringBuffer realURL = new StringBuffer(LAYOUT_PAGE);         realURL.append("?").append(PAGE_PARAM).append("=").             append(URLEncoder.encode(pagePath));         if (queryString != null) {             realURL.append("&").append(queryString);         }             ServletContext context = config.getServletContext(  );         RequestDispatcher rd =              context.getRequestDispatcher(realURL.toString(  ));         if (rd =  = null) {             httpResp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,                                 "Layout page doesn't exist");         }         rd.forward(request, response);         return;     }         public void destroy(  ) {     } } 

The filter gets the URL for the request, extracts the JSP page path, creates a URL for the layout page with the page path as a query string parameter, and forwards to the layout page. Because all this happens on the server, the URL shown in the browser remains unchanged.

The filter must be declared and mapped to the *.jsp pattern in the web application deployment descriptor ( web.xml ), like this:

 <?xml version="1.0" encoding="ISO-8859-1"?>     <!DOCTYPE web-app   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"   "http://java.sun.com//dtd/web-app_2_3.dtd">     <web-app>   <filter>     <filter-name>urlConversion</filter-name>     <filter-class>       com.ora.j2eebp.jsp.URLConversionFilter     </filter-class>   </filter>       <filter-mapping>     <filter-name>urlConversion</filter-name>     <url-pattern>*.jsp</url-pattern>   </filter-mapping> </web-app> 

With this filter configuration in place, any request for a JSP page (such as page1.jsp ) goes through the filter, causing the layout.jsp page to be invoked with the original path as the value of the page parameter.

The solution presented here works fine for any web application, but if you use an MVC framework, a templating solution with additional features can be part of the package. For instance, Apache Struts includes a templating package named Tiles. While the principles are the same, Tiles uses an XML configuration file for the type of dynamic values handled by variable-setter pages in the solution presented here, and the tight integration with Struts offers a different solution to the URL naming problem I solved with the filter.

9.2.6 Consider Internationalization from the Start

An Internet web application, and even some extranet and intranet applications, often benefit from internationalization (i18n). If i18n is not considered up front, it's usually a huge effort to retrofit the application later.

As for any application, i18n of a web application starts with identifying all user interface pieces that need to be handled differently for each locale: text, images, number and date formats, etc. The application is then developed to use external, localized versions of these resources instead of hardcoding them in the application. Supporting a new locale simply means adding a new set of localized resources.

When you use JSP for the user interface, you can approach i18n in two ways: use a common set of pages for all locales, dynamically grabbing the localized content from an appropriate resource bundle, or use a separate page for each locale, in effect treating each JSP page as a localized resource. The first approach is preferred for pages with small amounts of content but lots of presentation logic because it's easier to maintain. Using separate pages is a good choice when the layout differs dramatically between different locales, perhaps for Western languages and Asian languages, and for pages with lots of content but little presentation logic. Most sites benefit from a combination of these two approaches ”for instance, using separate pages for mostly static content (e.g., product descriptions and white papers) and shared pages for everything else.

Use a Controller servlet to select the locale to be used in the response to each request. It can do so based on information received with the request, such as a parameter or a cookie, or based on profile information in a database for a logged-in user. When separate pages are used for each locale, the Controller forwards to the appropriate page for the selected locale. For a page shared by all locales, the Controller uses a request attribute to record the selected locale and forwards to the internationalized page. The page can use the JSTL i18n actions to get localized text and image URLs from a matching resource bundle, and to format dates and numbers according to the rules for the selected locale.

If an internationalized, shared page needs to contain links to separate pages for each locale, you can use a resource bundle with keys for each locale-specific page and the localized page names as the values, like this:

 copyright=copyright_sv.jsp privacy=privacy_sv.jsp 

You can then use the bundle with the JSTL i18n actions to create a link to the correct page for the current locale:

 <a href="<fmt:message key="copyright" bundle="${pagesBundle}"/>">   <fmt:message key="copyright" bundle="${textBundle}" /> </a> 

For more information about internationalization of a J2EE application, see Chapter 8.

9.2.7 Enable URL Rewriting

Session tracking is necessary for any application that depends on information submitted through multiple requests . While a session ID cookie is usually sufficient for the web container to manage session tracking, it's not guaranteed to work for all clients ; some users disable cookie support in their browsers, and cookie support is not a given in browsers for small devices, such as a WML browser.

To handle cookie-less session tracking, web containers provide URL rewriting as a backup mechanism. URL rewriting works by embedding the session ID in all URLs in the generated response, ensuring that the session ID is returned to the container when the user clicks a link or submits a form embedded in the response.

URL rewriting requires the page author to encode all URLs pointing back to the web application. It's easy to do with the JSTL <c:url> action:

 <a href="<c:url value="details.do?id=${prod.id}"/>">   <c:out value="${prod.name}"/></a> 

Even for an application in which you can make demands on the browsers being used (e.g., an intranet application), it's a good idea to do this up front to prepare for the day when you no longer have this control.

I l @ ve RuBoard

The OReilly Java Authors - JavaT Enterprise Best Practices
The OReilly Java Authors - JavaT Enterprise Best Practices
Year: 2002
Pages: 96

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net