1.5. Seam pageflow: the numberguess example

For Seam applications with freeform (ad hoc) navigation, JSF/Seam navigation rules are a good way to define page flow. However, in applications with more constrained navigation styles, especially user interfaces that are more stateful, navigation rules make understanding system flow difficult. Combining information from view pages, actions, and navigation rules makes this flow easier to understand.
With Seam, jPDL process definition can be used to define pageflow, as seen in the number guessing example that follows.

1.5.1. Understanding the code

The example uses one JavaBean, three JSP pages and a jPDL pageflow definition. We will start by looking at the pageflow:

Example 1.23. pageflow.jpdl.xml

<pageflow-definition 
    xmlns="http://jboss.com/products/seam/pageflow"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.com/products/seam/pageflow 
                        http://jboss.com/products/seam/pageflow-2.2.xsd"
    name="numberGuess">
  
  <start-page name="displayGuess" view-id="/numberGuess.jspx">                        1
    <redirect/>
    <transition name="guess" to="evaluateGuess">                                      2
      <action expression="#{numberGuess.guess}"/>                                     3
    </transition>
    <transition name="giveup" to="giveup"/>
    <transition name="cheat" to="cheat"/>
  </start-page>
  
  <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">            4
    <transition name="true" to="win"/>
    <transition name="false" to="evaluateRemainingGuesses"/>
  </decision>
  
  <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
    <transition name="true" to="lose"/>
    <transition name="false" to="displayGuess"/>
  </decision>
  
  <page name="giveup" view-id="/giveup.jspx">
    <redirect/>
    <transition name="yes" to="lose"/>
    <transition name="no" to="displayGuess"/>
  </page>
  
  <process-state name="cheat">
    <sub-process name="cheat"/>
    <transition to="displayGuess"/>
  </process-state>
  
  <page name="win" view-id="/win.jspx">
    <redirect/>
    <end-conversation/>
  </page>
  
  <page name="lose" view-id="/lose.jspx">
    <redirect/>
    <end-conversation/>
  </page>
  
</pageflow-definition>

1

The <page> element defines a wait state where the system displays a particular JSF view and waits for user input. The view-id is the same JSF view id used in plain JSF navigation rules. The redirect attribute tells Seam to use post-then-redirect when navigating to the page. (This results in friendly browser URLs.)

2

The <transition> element names a JSF outcome. The transition is triggered when a JSF action results in that outcome. Execution will then proceed to the next node of the pageflow graph, after invocation of any jBPM transition actions.

3

A transition <action> is just like a JSF action, except that it occurs when a jBPM transition occurs. The transition action can invoke any Seam component.

4

A <decision> node branches the pageflow, and determines the next node to execute by evaluating a JSF EL expression.
In the JBoss Developer Studio pageflow editor, the pageflow looks like this:
With that pageflow in mind, the rest of the application is much easier to understand.
Here is the main page of the application, numberGuess.jspx:

Example 1.24. numberGuess.jspx

<?xml version="1.0"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:s="http://jboss.com/products/seam/taglib"
          xmlns="http://www.w3.org/1999/xhtml"
          version="2.0">
  <jsp:output doctype-root-element="html" 
              doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
              doctype-system=
              "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
  <jsp:directive.page contentType="text/html"/>
  <html>
    <head>
      <title>Guess a number...</title>
      <link href="niceforms.css" rel="stylesheet" type="text/css" />
      <script language="javascript" type="text/javascript" 
              src="niceforms.js" />
    </head>
    <body>
      <h1>Guess a number...</h1>
      <f:view>
        <h:form styleClass="niceform">
          
          <div>
            <h:messages globalOnly="true"/>
            <h:outputText value="Higher!" 
                          rendered="#{
                                    numberGuess.randomNumber gt 
                                    numberGuess.currentGuess}"/>
            <h:outputText value="Lower!" 
                          rendered="#{
                                    numberGuess.randomNumber lt 
                                    numberGuess.currentGuess}"/>
          </div>
          
          <div>
            I'm thinking of a number between 
            <h:outputText value="#{numberGuess.smallest}"/> and 
            <h:outputText value="#{numberGuess.biggest}"/>. You have 
            <h:outputText value="#{numberGuess.remainingGuesses}"/> 
            guesses.
          </div>
          
          <div>
            Your guess: 
            <h:inputText value="#{numberGuess.currentGuess}" 
                         id="inputGuess" required="true" size="3" 
                         rendered="#{
                                   (numberGuess.biggest-numberGuess.smallest) gt 
                                   20}">
              <f:validateLongRange maximum="#{numberGuess.biggest}" 
                                   minimum="#{numberGuess.smallest}"/>
            </h:inputText>
            <h:selectOneMenu value="#{numberGuess.currentGuess}" 
                             id="selectGuessMenu" required="true"
                             rendered="#{
                                       (numberGuess.biggest-numberGuess.smallest) le 
                                       20 and 
                                       (numberGuess.biggest-numberGuess.smallest) gt 
                                       4}">
              <s:selectItems value="#{numberGuess.possibilities}" 
                             var="i" label="#{i}"/>
            </h:selectOneMenu>
            <h:selectOneRadio value="#{numberGuess.currentGuess}" 
                              id="selectGuessRadio" 
                              required="true"
                              rendered="#{
                                        (numberGuess.biggest-numberGuess.smallest) le 
                                        4}">
              <s:selectItems value="#{numberGuess.possibilities}" 
                             var="i" label="#{i}"/>
            </h:selectOneRadio>
            <h:commandButton value="Guess" action="guess"/>
            <s:button value="Cheat" view="/confirm.jspx"/>
            <s:button value="Give up" action="giveup"/>
          </div>
          
          <div>
            <h:message for="inputGuess" style="color: red"/>
          </div>
          
        </h:form>
      </f:view>
    </body>
  </html>
</jsp:root>
Note that the command button names the guess transition instead of calling an action directly.
The win.jspx page is predictable:

Example 1.25. win.jspx

<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns="http://www.w3.org/1999/xhtml"
          version="2.0">
  <jsp:output doctype-root-element="html"
              doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
              doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
  <jsp:directive.page contentType="text/html"/>
  <html>
    <head>
      <title>You won!</title>
      <link href="niceforms.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
      <h1>You won!</h1>
      <f:view>
        Yes, the answer was 
        <h:outputText value="#{numberGuess.currentGuess}" />.
        It took you 
        <h:outputText value="#{numberGuess.guessCount}" /> guesses.
        <h:outputText value="But you cheated, so it does not count!" 
                      rendered="#{numberGuess.cheat}"/>
        Would you like to <a href="numberGuess.seam">play again</a>?
      </f:view>
    </body>
  </html>
</jsp:root>
The lose.jspx is very similar, so we have not included it here.
Finally, the application code is as follows:

Example 1.26. NumberGuess.java

@Name("numberGuess")
@Scope(ScopeType.CONVERSATION)
public class NumberGuess implements Serializable {
   
   private int randomNumber;
   private Integer currentGuess;
   private int biggest;
   private int smallest;
   private int guessCount;
   private int maxGuesses;
   private boolean cheated;
   
   @Create                                                   1
   public void begin()
   {
      randomNumber = new Random().nextInt(100);
      guessCount = 0;
      biggest = 100;
      smallest = 1;
   }
   
   public void setCurrentGuess(Integer guess)
   {
      this.currentGuess = guess;
   }
   
   public Integer getCurrentGuess()
   {
      return currentGuess;
   }
   
   public void guess()
   {
      if (currentGuess>randomNumber)
      {
         biggest = currentGuess - 1;
      }
      if (currentGuess<randomNumber)
      {
         smallest = currentGuess + 1;
      }
      guessCount ++;
   }
   
   public boolean isCorrectGuess()
   {
      return currentGuess==randomNumber;
   }
   
   public int getBiggest()
   {
      return biggest;
   }
   
   public int getSmallest()
   {
      return smallest;
   }
   
   public int getGuessCount()
   {
      return guessCount;
   }
   
   public boolean isLastGuess()
   {
      return guessCount==maxGuesses;
   }

   public int getRemainingGuesses() {
      return maxGuesses-guessCount;
   }

   public void setMaxGuesses(int maxGuesses) {
      this.maxGuesses = maxGuesses;
   }

   public int getMaxGuesses() {
      return maxGuesses;
   }

   public int getRandomNumber() {
      return randomNumber;
   }

   public void cheated()
   {
      cheated = true;
   }
   
   public boolean isCheat() {
      return cheated;
   }
   
   public List<Integer> getPossibilities()
   {
      List<Integer> result = new ArrayList<Integer>();
      for(int i=smallest; i<=biggest; i++) result.add(i);
      return result;
   }
   
}

1

The first time a JSP page asks for a numberGuess component, Seam will create a new one for it, and the @Create method will be invoked, allowing the component to initialize itself.
The pages.xml file starts a Seam conversation, and specifies the pageflow definition to use for the conversation's page flow. Refer to Chapter 8, Conversations and workspace management for more information.

Example 1.27. pages.xml

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">
  <page view-id="/numberGuess.jspx">
    <begin-conversation join="true" pageflow="numberGuess"/>
  </page>
</pages>
This component is pure business logic. Since it requires no information about the user interaction flow, it is potentially more reuseable.