Previous Section  < Day Day Up >  Next Section

9.1 Moving Between JSF Views

In addition to the main functionality, the sample application lets the user edit preferences through a separate set of screens. The application ignores all preference settings, but looking at how these preferences screens are implemented illustrates how JSF navigation works.

On a regular web site, links are used to navigate between different pages. A JSF web application may also use links, and there's a special action element that you should use to add a link to a page. It's the <h:outputLink> action element, and here's how it's used in the sample application's menu area:

...

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

    <h:outputText value="Preferences" />

  </h:outputLink>

...

The action uses the value attribute to render the corresponding HTML <a> element's href value, with an encoded session ID if needed for cookie-less session tracking. Nested <f:param> action elements can be used to add query string parameters (see Appendix A for details). The link text is taken from the value of one or more child components, such as the output component represented by the <h:outputText> element in this example. Figure 9-1 shows the menu area with the generated link in a browser.

Figure 9-1. Menu area with link to the first preferences screen
figs/Jsf_0901.gif

Navigation through a direct link is simple and sufficient in many cases, but not when user input must be processed before moving on to the next screen. The User Preferences feature is implemented as three screens in which the user can enter his name and address on the first screen, select a language on the second, and select font style and size preferences on the third. Clicking the buttons in each screen either saves or ignores the input, and brings up a new screen. Figure 9-2 shows all three screens.

Figure 9-2. Preferences screens
figs/Jsf_0902.gif

The user enters the first screen through the Preferences link in the menu area of the main application screen. Clicking Next saves the values of the current screen and displays the next screen, while clicking Previous returns to the previous screen without saving the values. The Cancel buttons available in all screens let the user return to the main screen at any point without saving the values in the current screen. The Done button on the final screen saves the font selections and returns to the main screen. Example 9-1 shows the initial version of the JSP page for the user information screen.

Example 9-1. Initial version: user information page with Next and Cancel buttons (expense/stage1/prefUser.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" %>



<f:view>

  <html>

    <body>

      <h2>User Information:</h2>

      <h:form>

        <h:panelGrid columns="2">

          <h:outputText value="First Name:" />

          <h:inputText size="30" value="#{userProfile.firstName}" />

          <h:outputText value="Last Name:" />

          <h:inputText size="30" value="#{userProfile.lastName}" />

          <h:outputText value="Street Address:"/>

          <h:inputText size="30" value="#{userProfile.street}" />

          <h:outputText value="City:" />

          <h:inputText size="30" value="#{userProfile.city}" />

          <h:outputText value="State:" />

          <h:inputText size="30" value="#{userProfile.state}" />

          <h:outputText value="ZIP:" />

          <h:inputText size="30" value="#{userProfile.zip}" />

        </h:panelGrid>



        <h:commandButton value="Next" 

          action="#{userHandler.updateProfile}" />

        <h:commandButton value="Cancel" 

          immediate="true" action="cancel" />

      </h:form>

    </body>

  </html>

</f:view>

The form contains a number of input fields for user information, bound to properties of a bean named userProfile, and two command buttons. The Next button is bound to an action method named updateProfile() in a bean named userHandler. For the Cancel button, the action attribute contains a literal string: cancel. Let's first look at how the Next button is handled. Example 9-2 shows the updateProfile() method in the class used as the userHandler bean.

Example 9-2. The UserHandler class
package com.mycompany.expense;



...

public class UserHandler {

    ...

    public String updateProfile( ) {

        return "success";

    }

    ...

}

As you may recall from Chapter 8, an action method must return a String value representing the outcome of the action. I'm cheating here and always return "success". In a real application, the values captured by the userProfile bean would be saved in a database and potential database errors would be handled by queuing a message and returning "error."

I allow myself to cheat here because what's important is how the outcome value affects navigation. Outcomes can be mapped to view identifiers by navigation rules declared in the faces-config.xml file, like this:

<faces-config>

  ...

  <navigation-rule>

    <from-view-id>/expense/stage1/prefUser.jsp</from-view-id>

    <navigation-case>

      <from-action>#{userHandler.updateProfile}</from-action>

      <from-outcome>success</from-outcome>

      <to-view-id>/expense/stage1/prefLang.jsp</to-view-id>

    </navigation-case>

  </navigation-rule>

  ...

</faces-config>

A <navigation-rule> element contains subelements that declare when the rule applies and which view to switch to when it does. The <from-view-id> element is optional. If it's specified, it must be either a complete view identifier (the context-relative path to the JSP page, when you use JSP) or a view identifier prefix ending with an asterisk, e.g., /expense/stage1/* to match all view identifiers that start with /expense/stage1/. If the <from-view-id> element is omitted, the rule matches all views identifiers. If more than one <navigation-rule> element has matching <from-view-id> elements, all of them are considered by the navigation handler.

One or more <navigation-case> elements declare how to deal with different cases within the matching view. The <from-action> element is optional and may be used to limit the rule to apply only to outcomes from the specified action method. The <from-outcome> element is also optional and limits the rule to the specified outcome value. The <to-view-id> element is mandatory, and JSF switches to the specified view when it finds a match.

So, when the Next button in Example 9-1 is clicked, the updateProfile() method is invoked and returns the "success" outcome value. This outcome matches the rule defined for this view in the faces-config.xml file, so JSF uses the /expense/stage1/prefLang.jsp view to render the response.

For the Cancel button, I specify a literal value of "cancel" instead of an action method. This comes in handy when you only want to switch to a new view, without processing any input. JSF uses the literal value just as it uses an outcome from an action method to look for a matching navigation rule. Here's the rule that matches the Cancel button's action attribute value:

  <navigation-rule>

    <from-view-id>/expense/stage1/*</from-view-id>

    <navigation-case>

      <from-outcome>cancel</from-outcome>

      <to-view-id>/expense/stage1/menuArea.jsp</to-view-id>

      <redirect/>

    </navigation-case>

  </navigation-rule>

Note that I use a prefix pattern for the <from-view-id> and no <from-action> element, so this rule applies to the Cancel button in all three preferences views.

The <h:commandButton> action elements for all Cancel buttons also have the immediate attribute described in Chapter 8 set to true, so no validation or model updates take place when the user clicks the Cancel button.

9.1.1 Choosing Between Redirect and Direct Rendering

I also use an empty <redirect> element within the <navigation-case> element for the Cancel button. This is an optional element that tells JSF to send a redirect response that asks the browser to request the specified view instead of rendering it as the response to the current request.

The visible difference between a redirect response and the direct rendering of a new view is that with a redirect, the browser adjusts its address field to show the URL for the new view, but with direct rendering, the address field remains unchanged. This, in turn, affects what happens if the user reloads or bookmarks the page: with the address in the browser unchanged, reloading and bookmarking applies to the old address.

So how do you decide if you should use a redirect? To a large extent, it's a matter of preference. I look at it like this: direct rendering is always faster, so it's the first choice. But because the URL in the browser continues to refer to the old view even though a new view is displayed, ask yourself what happens if the user decides to reload the page or bookmark it. If doing so can cause any kind of harm (e.g., submitting an order twice) or result in unexpected behavior (e.g., using a bookmark brings up the wrong page), use a redirect instead.

9.1.2 Using a Panel Component for Layout

The JSP pages for the other two views follow the same pattern as the first one, as shown in Example 9-3 and Example 9-4.

Example 9-3. Language selection page with Previous, Next and Cancel buttons (expense/stage1/prefLang.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" %>



<f:view>

  <html>

    <body>

      <h2>Language:</h2>

      <h:form>

        <h:panelGrid columns="2">

          <h:outputText value="Language:" />

          <h:selectOneRadio value="#{userProfile.locale}">

            <f:selectItem itemValue="sv" itemLabel="Swedish"/>

            <f:selectItem itemValue="en" itemLabel="English"/>

          </h:selectOneRadio>

        </h:panelGrid>



        <h:commandButton value="Previous" 

          immediate="true" action="previous" />

        <h:commandButton value="Next" 

          action="#{userHandler.updateProfile}" />

        <h:commandButton value="Cancel" 

          immediate="true" action="cancel" />

      </h:form>

    </body>

  </html>

</f:view>
Example 9-4. Font selection page with Previous, Done and Cancel buttons (expense/stage1/prefFont.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" %>



<f:view>

  <html>

    <body>

      <h2>Fonts:</h2>

      <h:form>

        <h:panelGrid columns="2">

          <h:outputText value="Font style:" />

          <h:selectOneMenu value="#{userProfile.fontStyle}">

            <f:selectItem itemValue="serif" itemLabel="Serif"/>

            <f:selectItem itemValue="san-serif" itemLabel="San-Serif"/>

            <f:selectItem itemValue="mono" itemLabel="Monospaced"/>

          </h:selectOneMenu>

          <h:outputText value="Font size:" />

          <h:selectOneMenu value="#{userProfile.fontSize}">

            <f:selectItem itemValue="small" itemLabel="Small"/>

            <f:selectItem itemValue="normal" itemLabel="Normal"/>

            <f:selectItem itemValue="large" itemLabel="Large"/>

          </h:selectOneMenu>

        </h:panelGrid>

        <h:commandButton value="Previous"

          immediate="true" action="previous" />

        <h:commandButton value="Cancel" 

          immediate="true" action="cancel" />

        <h:commandButton value="Done"

          action="#{userHandler.updateProfile}" />

      </h:form>

    </body>

  </html>

</f:view>

Navigation rules similar to the ones we looked at earlier take care of switching between the next and previous views and redirecting to the menu view when the Done button is clicked:

  <navigation-rule>

    <from-view-id>/expense/stage1/prefLang.jsp</from-view-id>

    <navigation-case>

      <from-outcome>previous</from-outcome>

      <to-view-id>/expense/stage1/prefUser.jsp</to-view-id>

    </navigation-case>

    <navigation-case>

      <from-action>#{userHandler.updateProfile}</from-action>

      <from-outcome>success</from-outcome>

      <to-view-id>/expense/stage1/prefFont.jsp</to-view-id>

    </navigation-case>

  </navigation-rule>



  <navigation-rule>

    <from-view-id>/expense/stage1/prefFont.jsp</from-view-id>

    <navigation-case>

      <from-outcome>previous</from-outcome>

      <to-view-id>/expense/stage1/prefLang.jsp</to-view-id>

    </navigation-case>

    <navigation-case>

      <from-action>#{userHandler.updateProfile}</from-action>

      <from-outcome>success</from-outcome>

      <to-view-id>/expense/stage1/menuArea.jsp</to-view-id>

      <redirect/>

    </navigation-case>

  </navigation-rule>

All three JSP pages also use a JSF custom action element we haven't talked about yet: the <h:panelGrid> element. This action element represents a panel component, acting as a container for any number of child components, with a grid renderer that renders the child components as an HTML table with the specified number of columns. In all three pages, the columns attribute is set to 2, so the first child ends up in the first column, the second in the second column, the third in the first column of the next row, and so on. For the page in Example 9-4, the result looks something like this:

<table>

  <tr>

    <td>Font style:</td>

<td>

      <select name ="_id0:_id3">

        <option value="serif">Serif</option>

        <option value="san-serif">San-Serif</option>

        <option value="mono">Monospaced</option>

      </select>

    </td>

  </tr>

  <tr>

    <td>Font size:</td>

    <td>

      <select name="_id0:_id8">

        <option value="small">Small</option>

        <option value="normal">Normal</option>

        <option value="large">Large</option>

      </select>

    </td>

  </tr>

</table>

You can, of course, use the HTML table elements explicitly for layout in a JSP page instead of using the <h:panelGrid> custom action. As with so much else, it's mostly a matter of preference, but using the custom action means a little bit less typing—and therefore a slightly better chance that there are no errors—while using HTML elements gives you more control over the layout.

    Previous Section  < Day Day Up >  Next Section