Book: JavaServer Pages™, 2nd Edition
Section: Chapter 16.  Bits and Pieces



16.2 Including Page Fragments

You can use either a JSP directive or a standard action to include page fragments a JSP page. This is a useful technique when parts of all pages in an application are the same, such as headers, footers, and navigation bars.

The JSP include directive reads the content of the specified page in the translation phase (when the JSP page is converted into a servlet) and merges it with the original page:

<%@ include file="header.htmlf" %>

The file attribute is a relative path. If it starts with a slash, it's a context-relative path, interpreted relative to the context path assigned for the application. If it doesn't start with a slash, it's a page-relative path, interpreted relative to the path for the page that includes the file.

The included file can contain either only static content (such as HTML) or it can be a regular JSP page. Its content is merged with the page that includes it, and the resulting page is converted into a servlet as described in Chapter 3. This means that the main page and all included pages share all page scope data. Scripting variables declared in JSP declarations, scriptlets, or actions, such as <jsp:useBean> or custom actions that introduce scripting variables, are also shared. Consequently, if the main page declares a variable, and the same name is used for another variable in an included page, it results in a translation phase error, because the combined page can't be compiled.

One common use for the include directive is to include common declarations needed for all pages for an application. For instance, if you place all taglib directives for the libraries used in an application in a page fragment and include it in all real JSP pages, it's easy to add new libraries or change a uri attribute value if it changes due to an upgrade.

The JSP specification recommends you use a different file extension than .jsp for partial JSP pages that you include using the include directive, because they typically aren't complete, legal JSP pages. A couple of alternative extensions you can use are .jspf or .jsf ("f" as in fragment). I follow this recommendation for HTML files as well and use .htmlf as the extension for static files that aren't complete HTML pages.

What happens when the file specified by the include directive changes isn't specified by the JSP specification. With Tomcat, you must change the modification date for the main page, for example using the touch command on a Unix system, before the changes take effect. An alternative is to delete the class file (the compiled version of the page) for the page. Other JSP containers may detect changes in included files automatically and go through the translation phase just like when you modify the main JSP page.

Another thing to be aware of is that the size of the compiled Java code (bytecode) for a method is limited to 64 KB by the Java Virtual Machine specification. This is normally not a problem, but if you use the include directive to include large files, you may run into this restriction in some JSP implementations (such as Tomcat). A workaround is to use the <jsp:include> action instead.

The <jsp:include> standard action is an alternative to the include directive; it includes another resource at runtime:

<jsp:include page="navigation.jsp" />

The action is executed in the request-processing phase instead of the translation phase. The page attribute value is interpreted as a relative URI, the same way as the include directive's file attribute. The <jsp:include> action doesn't include the actual contents of the specified page; it includes the response produced by executing the page. This means you can specify any type of web resource (e.g., a servlet, a JSP page, or a static HTML page) that produces a text response. The JSP container invokes the specified resource by an internal function call. Hence, the included resource is helping to process the original request and therefore has access to all objects in the request scope as well as all original request parameters. Note, though, that it doesn't have access to any page-scope attributes or scripting variables declared in the main page.

<c:import> Versus <jsp:include>

As you may recall from Chapter 14, there's also a JSTL action named <c:import>. It can include the response produced by another application resource, just like the <jsp:include> action, but it can also include data from external resources, such as a different web application or an FTP server.

With the introduction of <c:import>, the are few reasons to use the less powerful <jsp:include>. Theoretically, it may be slightly faster because its implementation is simpler, but it's probably not a noticeable difference.

Since the page is not included until the main page is requested, you can use a request time attribute value for the page attribute to decide which page to include depending on a runtime condition, and add request parameters that can be read by the included page:

<jsp:include page="<%= pageSelectedAtRuntime %>" >
  <jsp:param name="aNewParamer" value="aStaticValue" />
  <jsp:param name="anotherParameter" value="<%= aDynamicValue %>" />
</jsp:include>

If you change the included JSP page, the new version is used immediately. This is because the included page is treated the same way as a JSP page invoked directly by a browser; the container detects that the page has been modified and goes through the translation phase before it invokes it.

Besides the page attribute, the <jsp:include> action also supports a flush attribute. It specifies whether or not the response body should be flushed before the page is included. If you have used a JSP 1.1 container, you've probably learned to always specify this attribute with the value true. This was a requirement in JSP 1.1 due to limitations in the Servlet 2.2 API, with the serious drawback that the main page couldn't set headers or forward to another page after the <jsp:include> action element. I'm happy to tell you that this limitation is gone in JSP 1.2. The flush attribute is now optional, and false is the default value.

Note that, as is true for all standard actions, dynamic attribute values for <jsp:include> and <jsp:param> must be set using request-time attribute values (JSP expressions, described in Chapter 15) as opposed to EL expressions.

Table 16-1 outlines the differences between the include directive and the <jsp:include> action.

Table 16-1. Differences between the include directive and the <jsp:include> action

Syntax

When

What

<%@ include 
  file="relativeURI" %>

Translation phase

Static text (HTML, JSP) merged with the JSP page before it's converted to a servlet.

<jsp:include 
  page="relativeURI"
  flush="true|false" />

Request processing phase

The response text generated by executing the page or servlet.

Let's look at a concrete example of how you can use the two methods for including pages. Example 16-1 shows a page that includes three other pages.

Example 16-1. Including pages (page1.jsp)
<%@ page contentType="text/html" %>
<%@ include file="header.htmlf" %>
<table width="90%">
  <tr>
    <td valign="top" align="center" bgcolor="lightblue">
      <jsp:include page="navigation.jsp" />
    </td>
    <td valign="middle" align="center" width="80%">
      This is page 1
    </td>
  </tr>
</table>
<%@ include file="footer.htmlf" %>

The example application contains two more main pages, page2.jsp and page3.jsp, that differ from page1.jsp only in the text they contain (i.e., "This is page 2", "This is page 3"). The common header and footer for all pages in the example application consist of static HTML, shown in Examples 16-2 and 16-3. The include directive is used to include the header and footer files in each main page.

Example 16-2. Header (header.htmlf)
<html>
  <head>
    <title>Welcome to My Site</title>
  </head>
  <body bgcolor="white">
    <h1>My Site</h1>

Note that the header.htmlf file is not a complete HTML page. It contains only the start tags for the <html> and <body> elements.

Example 16-3. Footer (footer.htmlf)
<hr>
  Copyright &copy; 2002 My Company
  </body>
</html>

The end tags for the <body> and <html> tags are included in the footer.htmlf file. Merging the header.htmlf, one of the main pages, and the footer.htmlf files results in a complete HTML page.

Each page in the example application also has a navigation bar, with labels for all pages in the application. The labels are links to the corresponding pages, except for the current page, which is just written as plain text as shown in Figure 16-1.

Figure 16-1. A page composed by including other pages
figs/Jsp2_1601.gif

The JSP code for the navigation bar is separated out into its own file, shown in Example 16-4, and included in each page with the <jsp:include> action as shown earlier in Example 16-1.

Example 16-4. Navigation bar with JSTL actions (navigation_jstl.jsp)
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
  
<c:set var="uri" value="${pageContext.request.servletPath}" />
<table bgcolor="lightblue">
  <tr>
    <td>
      <c:choose>
        <c:when test="${uri == '/ch16/page1.jsp'}">
          <b>Page 1</b>
        </c:when>
        <c:otherwise>
          <a href="page1.jsp">Page 1</a>
        </c:otherwise>
      </c:choose>
    </td>
  </tr>
  <tr>
    <td>
      <c:choose>
        <c:when test="${uri == '/ch16/page2.jsp'}">
          <b>Page 2</b>
        </c:when>
        <c:otherwise>
          <a href="page2.jsp">Page 2</a>
        </c:otherwise>
      </c:choose>
    </td>
  </tr>
  <tr>
    <td>
      <c:choose>
        <c:when test="${uri == '/ch16/page3.jsp'}">
          <b>Page 3</b>
        </c:when>
        <c:otherwise>
          <a href="page3.jsp">Page 3</a>
        </c:otherwise>
      </c:choose>
    </td>
  </tr>
</table>

The navigation bar page first saves the context-relative path for the current page in a variable named uri with a <c:set> action and an EL expression that gets the path by reading the servletPath property from the request object accessed through the implicit pageContext variable. This works because the request object reflects the information about the page that includes the navigation bar page, not about the included page. An HTML table is then built with one cell for each main page in the application. In each cell, a <c:choose> block tests if the cell represents the current page or not. If it does, the page name is written as bold text; otherwise, it's written as an HTML link.

Example 16-4 can be simplified with a custom action that does all the testing and generates the appropriate HTML instead, as shown in Example 16-5.

Example 16-5. Navigation bar with custom action (navigation.jsp)
<%@ page contentType="text/html" %>
<%@ taglib prefix="ora" uri="orataglib" %>
<table bgcolor="lightblue">
  <tr>
    <td>
      <ora:menuItem page="page1.jsp">
        <b>Page 1</b>
      </ora:menuItem>
    </td>
  </tr>
  <tr>
    <td>
      <ora:menuItem page="page2.jsp">
        <b>Page 2</b>
      </ora:menuItem>
    </td>
  </tr>
  <tr>
    <td>
      <ora:menuItem page="page3.jsp">
        <b>Page 3</b>
      </ora:menuItem>
    </td>
  </tr>
</table>

The <ora:menuItem> action inserts the HTML found in its body into the page. If the page specified by the page attribute is not the current page, the HTML is inserted as-is. Otherwise, it's embedded in an HTML link element, in the same way as the <c:choose> block in Example 16-4. But unlike the JSTL version of this page, the <ora:menuItem> action also performs URL rewriting on the HTML link URL if needed (this includes the session ID in the URL).

You may wonder why I use the include directive for the header and footer and the <jsp:include> action for the navigation bar. Either one will do for all files in this example, but I chose the action for the navigation bar because this page needs to be updated as new pages are added to the application. Using the action guarantees that the new version of the file is used immediately. I picked the directive for the header and footer pages because there's a slight performance penalty with using the action (the container must make a function call at request time). In this example, I assumed that both the header and footer contain stable information. In the rare event that they change I'm willing to force the JSP container to go through the translation phase by deleting the class files corresponding to each main page or changing the modification date for each page as described earlier.

    [http://safari.oreilly.com/059600317X/jserverpages2-CHP-16-SECT-2]


    Copyright © 2002 O'Reilly & Associates, Inc. All rights reserved.
    1005 Gravenstein Highway North
    Sebastopol, CA 95472