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.
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
<%@ 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 © 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.
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.
|