Previous Section  < Day Day Up >  Next Section

12.1 Building a View from Many JSP Files

In the preceding chapters, we've worked with the different areas of the sample application as separate pages. Besides making it easier to explain the features one by one without overwhelming you with unrelated details, this approach also make sense for developing a real application, because you can work with smaller files that are easier to grasp and debug. At some point, though, you need to put all the pieces together. There are two ways to build up a JSF view from multiple JSP files: statically include all pieces into one file when the JSP page is transformed into a servlet and compiled, or dynamically include the files each time the main file is requested.

The JSP include directive (<%@ include file="..." %>) supports the static include option. It includes the contents of the specified file, whether it's a file with just plain markup elements or a file with JSP elements. Because the include is processed when the page is transformed into a servlet, the name of the file to include must be entered as a static value. When a JSP file is included this way, it shares the page scope (and all other scopes) and all scripting variables with the including file. The included file is rarely a syntactically complete JSP or HTML page; I recommend that you use a different file extension to highlight this fact. Any extension will do, but .jspf and .htmlf are good choices ("f" stands for "fractional" or "fragment"). The container is not required to detect changes in a file included by the include directive and, therefore, may not recompile the including file when the included file is changed. Most modern containers, such as Tomcat 5, do detect these changes, however.

The include action (<jsp:include page="..." />) as well as the JSTL import action (<c:import url="..." />) support dynamic includes. They both include the response produced by executing the specified resource (e.g., a JSP page or a servlet), which can be named by a JSP EL or Java expression evaluated at runtime. The included resource has access to the same scopes as the including file except for the page scope, and the container always detects when it's changed and recompiles it when needed. The standard include action only includes the content produced by a resource in the same web application, while the JSTL import action includes content from resources either 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.

Note that a dynamically included page can't set response headers or status codes for things like cookies or redirect requests. With JSF, that's rarely a problem because each request is always processed by a servlet (which can set these things before the JSP page is processed), but if you mix regular JSP pages and JSP pages containing JSF components, it may be an issue to consider.

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

  • Use the include directive (<% include ... %>) for files that rarely change and are known at development time, e.g., headers and footers. The including and included files are merged in the translation phase, so there's no runtime overhead. Be aware, though, that including large files may cause the generated _jspService( ) method to run into 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.

  • You may want to 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), and you must use one of these actions when the file to include isn't known until runtime. Of the two, the JSTL import action is more flexible, as it can include both local and external resources. 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 finely grained error handling. Due to its flexibility, it may be slightly less efficient than the standard include action, but not enough to matter in most cases.

For pages containing JSF components, there are additional concerns to be aware of when you use dynamic includes, described later in this chapter. Unless you have to use dynamic includes (say, because the page to include isn't known until runtime), I recommend that you use static includes for all pages containing JSF component action elements.

12.1.1 Using Static Includes for Pages with JSF Components

Static includes for JSP pages containing JSF component actions work the same as for regular JSP pages. I use static includes to piece together the sample application, because the main reason for splitting it up in the first place is maintainability. All filenames are known at development time and using static includes avoid the runtime penalty and other issues, as described earlier.

Example 12-1 shows the top level JSP page, i.e., the one the pulls in all the other ones.

Example 12-1. The top-level page for the sample application (expense/final/reports.jsp)
<%@ page contentType="text/html" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://mycompany.com/jsftaglib" prefix="my" %>



<html>

  <head>

    <title>Expense Reports</title>

    <link rel="stylesheet" type="text/css" 

      href="${pageContext.request.contextPath}/style.css">

  </head>

  <body bgcolor="white">

    <f:view locale="#{userProfile.locale}">

      <f:loadBundle basename="labels" var="labels" />

      <table width="95%" align="center">

        <tr>

          <td colspan="2">

            <!-- Title -->

            <h:outputText value="#{labels.applicationTitle}" 

              styleClass="title" />

          </td>

        </tr>

        <tr>

          <td colspan="2" class="toolbar">

            <!-- Menu -->

            <h:form>

              <%@ include file="menuArea.jspf" %>

            </h:form>

          </td>

        </tr>

        <tr>

          <td valign="top" width="60%">

            <!-- Reports -->

            <h:form>

              <%@ include file="reportsArea.jspf" %>

            </h:form>

          </td>

          <td valign="top">

            <!-- Details -->

            <%@ include file="detailsArea.jspf" %>

          </td>

        </tr>

      </table>

      <h:messages globalOnly="true" />

    </f:view>

  </body>

</html>

At the top of the page, there's a JSP page directive that declares the content type for the response generated by the page and taglib directives for all tag libraries used in this page, as well as in the included pages for the different screen areas. Because this is the top-level page, it contains the <html>, <head>, and <body> HTML elements, including a link to the stylesheet for the application in the same way as for the styled page we looked at in Chapter 10. It also contains the <f:view> action element and an <f:loadBundle> action element, setting the locale for the view to the one selected in the preferences page and loading the localized resource bundle as I described in Chapter 11. At the bottom of the page, there's an <h:message> action for displaying possible error messages not associated with specific components, such as messages about problems accessing the report registry.

Other than that, this page uses HTML table elements for the layout of the different areas and <%@ include file="..." %> directives to include each area. Note that all the included files have a .jspf extension. As I described earlier, this means they are incomplete JSP pages, e.g., they don't have the taglib directives so they don't work if you request them directly.

12.1.1.1 Using one or multiple forms

In Example 12-1, two of the files (menuArea.jspf and reportsArea.jspf) are included within <h:form> elements, and the third (detailsArea.jspf) in turn includes two other files within <h:form> elements. Using more than one form per JSF view may cause some unwanted side effects. Forget about JSF for a bit and imagine a plain HTML page with two forms. If the user enters values in both forms and then submits one of them, the browser sends a request to the server with request parameters for all input fields in the form that was submitted. The input added in the other form isn't sent to the server at all, so it's lost. This behavior is expected in HTML because the response to the request is usually a brand new page, generated based on the data in the submitted form. With JSF, however, the same page is often redisplayed, so it's more likely that the lost input is noticeable and may cause confusion. Another thing to be aware of is that when view state is saved in the client, it's typically included as a hidden field in each form. Using multiple forms therefore increases the response size, which may be a concern for low bandwidth connections.

Sometimes using more than one form for a JSF view is preferable in order to avoid other confusing side effects related to using just one form. When you submit a form, JSF runs through the request processing lifecycle phases for all components that belong to the submitted form, including validation. Normally, this is what you want, but consider the different screen areas in the sample application. If all components belong to the same form, trying to submit a report or delete a report entry may fail due to validation errors if an unrelated value in, say, the entry form area is missing or invalid. That's why I use multiple forms in the sample application: one for the menu area, one for the whole reports area, and one each for the entry form and the entry list.

There's no right or wrong choice here. In general, though, I suggest that you try to stick to just one form to keep it simple. Use multiple forms only when the risk for failure due to unrelated validation errors outweighs the potential confusion caused by potential loss of input in the nonsubmitted forms.

12.1.1.2 The included files

Let's look at some of the files included by the top-level page. The first one is menuArea.jspf, shown in Example 12-2.

Example 12-2. The final menu area page (expense/final/menuArea.jspf)
<table cellpadding="0" cellspacing="0" width="100%">

  <tr>

    <td>

      <h:commandButton value="#{labels.newButtonLabel}" 

        disabled="#{reportHandler.newDisabled}"

        action="#{reportHandler.create}" /> 

      <h:commandButton value="#{labels.deleteButtonLabel}" 

        disabled="#{reportHandler.deleteDisabled}"

        action="#{reportHandler.delete}" />

      <h:commandButton value="#{labels.submitButtonLabel}" 

        disabled="#{reportHandler.submitDisabled}"

        action="#{reportHandler.submit}" />

      <h:commandButton value="#{labels.acceptButtonLabel}" 

        rendered="#{reportHandler.acceptRendered}"

        disabled="#{reportHandler.acceptDisabled}"

        action="#{reportHandler.accept}" />

      <h:commandButton value="#{labels.rejectButtonLabel}" 

        rendered="#{reportHandler.rejectRendered}"

        disabled="#{reportHandler.rejectDisabled}"

        action="#{reportHandler.reject}" />

    </td>

    <td align="right">

      <span class="small">

        <h:outputText value="#{labels.loggedInAs}" />

        "${pageContext.request.remoteUser}"

        [<h:outputLink value="../../logout.jsp">

           <h:outputText value="#{labels.logoutLinkLabel}" />

         </h:outputLink>]

        [<h:outputLink value="prefUser.faces">

           <h:outputText value="#{labels.prefLinkLabel}" />

         </h:outputLink>]

      </span>

    </td>

  </tr>

</table>

This should look familiar; it's the internationalized version of the page that we developed in Chapter 11, minus the tag library declarations, the <f:view> action, the <f:loadBundle> action, and the <h:form> action, which are now inherited from the reports.jsp page.

Example 12-3 shows the detailsArea.jspf page.

Example 12-3. The details area page (expense/final/detailsArea.jspf)
<table class="box" width="100%">

  <tr>

    <td>

      <h:outputText value="#{labels.detailsAreaTitle}"

        styleClass="smalltitle" />

    </td>

  </tr>

  <tr>

    <td>

      <!-- Entry form -->

      <h:form>

        <%@ include file="entryFormArea.jspf" %>

      </h:form>

    </td>

  </tr>

  <tr>

    <td>

      <!-- Entry list -->

      <h:form>

        <%@ include file="entryListArea.jspf" %>

      </h:form>

    </td>

  </tr>

</table>

It's similar to the top-level page in that it uses HTML table elements layout and includes the entry form and entry list subareas from separate files.

The reportsArea.jspf file is almost identical to the detailsArea.jspf file, and the files these two include in turn are the final versions of the pages we worked with in the preceding chapters, minus the stuff inherited from the top-level page (just as for the menuArea.jspf file). Instead of wasting trees by printing them here, I suggest that you look at them at your leisure. The end result is shown in Figure 12-1.

Figure 12-1. The complete expense report screen
figs/Jsf_1201.gif

12.1.2 Using Dynamic Includes for Pages with JSF Components

Dynamically including JSP pages containing JSF components works almost the same as dynamically including regular JSP pages, but there are a couple of important issues you need to be aware of. When you dynamically include a regular JSP page, the included page is processed and the content it produces is added to the including page's response. The same is true when you dynamically include a JSP page containing JSF components, but only for content that is not generated by JSF components. JSF component actions in the included page add components to the component tree created by the including page, and then immediately ask each component to render itself. The result is often not what you expected. Example 12-4 and 12-5 show two pages that illustrate what's happening.

Example 12-4. Top-level page dynamically including another (main.jsp)
<%@ page contentType="text/html" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<html>

  <body>

    <f:view>

      Template text at the top of the main page.

      <br>

      <h:outputText value="Text from a JSF component in the main page" />

      <br>

      <f:subview id="sv1">

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

      </f:subview>

    </f:view>

  </body>

</html>
Example 12-5. Dynamically included page with template data (included.jsp)
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

Template text at the top of the included page.

<br>

<h:outputText value="Text from a JSF component in the included page" />

When you request the page in Example 12-4, it includes the page in Example 12-5, producing the response shown in Figure 12-2.

Figure 12-2. Template text out of order
figs/Jsf_1202.gif

If you look closely, you'll see that the template text from the included page comes after the text from the output component in the same page, even though the template text appears at the top of the included page in Example 12-5.

What's going on here is caused by a difference in semantics between JSF and JSP. The JSP <jsp:include> action processes the included page, buffers all content the page produces, and then adds it to the response generated by the including page. While processing the included page, however, it invokes the JSF <h:outputText> action, which creates an output component, adds it to the component tree owned by the including page, and asks the component to render itself. And here's the twist: the component renders itself to the response of the including page, before the buffered non-JSF content from the included page is added.

To avoid this reordering of content, you must get rid of all non-JSF content in the included page. You can minimize non-JSF content by using JSF action elements like <h:panelGrid> instead of HTML tables for layout and <h:outputText> for all text. For the remainder, you must use the <f:verbatim> action element, as shown in Example 12-6.

Example 12-6. Dynamically included page with <f:verbatim> (included2.jsp)
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<f:verbatim>

  Template text at the top of the included page.

  <br>

</f:verbatim>

<h:outputText value="Text from a JSF component in the included page" />

The modified version of the included page wraps the template text within the body of an <f:verbatim> action. This action creates an output component with the value set to the content produced by non-JSF actions and template text in its body.

By default, the <f:verbatim> action leaves all text alone, but you can use the escape attribute with the value true to tell it to convert characters with special meaning (such as the less-than and greater-than signs in HTML) to the corresponding character entity code (&lt; and &gt;). The <h:outputText> action also supports the escape attribute, but with true as the default.

This type of escaping is important to minimize the risk for cross-site scripting attacks on a site that displays content entered by site visitors. A cross-site scripting attack means that a visitors enters content that, when rendered, creates problems for another visitor, e.g., causing the browser to close. You can read more about this vulnerability at http://www.cert.org/advisories/CA-2000-02.html.


With the included file modified as in Example 12-6, the content is generated in the expected order, as shown in Figure 12-3.

Figure 12-3. Template text wrapped within an <f:verbatim> element
figs/Jsf_1203.gif

Having to wrap all non-JSF content in <f:verbatim> elements is clearly not an elegant solution, and hopefully a better JSF/JSP integration strategy will be defined by future versions of the specifications. Until then, I recommend that you use static includes whenever possible.

12.1.2.1 Naming containers for included content

You must also use <f:subview> elements when dynamically including JSP pages containing JSF component actions from another JSP page with JSF component actions. In Example 12-4, the <f:subview> is used like this:

      <f:subview id="sv1">

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

      </f:subview>

Alternatively, you can put the <f:subview> element in the included page, wrapping all the other elements.

The <f:subview> element creates a naming container component to hold all the included components. You must use an id attribute value that is unique among all component IDs used for components nested within the same <f:view> element. A naming container is a component type that adjusts the markup IDs (e.g., HTML id and name attributes) generated for its children by combining its own component ID with the children's ID. For instance, the HTML name attribute value generated for an <h:inputComponent> with the id attribute value input nested within a naming container component with the id attribute value sv1 is sv1:input. The combined ID is referred to as a client ID in the specification.

Using <f:subview> elements for included components ensures that the markup elements generated for the included components have ID values that are unique within the view, even if you include the same page more than once in the same view or use the same component ID for two components in different included files.

    Previous Section  < Day Day Up >  Next Section