JavaBeat
Search JavaBeat

JBoss Seam Links
JBoss Seam
Tutorials
JavaBeat
Home
Articles
Tips
Code
QnA
Forums

1.5. Seam pageflow: the numberguess example

For Seam applications with relatively freeform (ad hoc) navigation, JSF/Seam navigation rules are a perfectly good way to define the page flow. For applications with a more constrained style of navigation, especially for user interfaces which are more stateful, navigation rules make it difficult to really understand the flow of the system. To understand the flow, you need to piece it together from the view pages, the actions and the navigation rules.

Seam allows you to use a jPDL process definition to define pageflow. The simple number guessing example shows how this is done.

1.5.1. Understanding the code

The example is implemented using one JavaBean, three JSP pages and a jPDL pageflow definition. Let's begin with the pageflow:

Example 1.18. 

<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.0.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"/>
</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>

<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.

Here is what the pageflow looks like in the JBossIDE pageflow editor:

Now that we have seen the pageflow, it is very, very easy to understand the rest of the application!

Here is the main page of the application, numberGuess.jspx:

Example 1.19. 

<<?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>

Notice how the command button names the guess transition instead of calling an action directly.

The win.jspx page is predictable:

Example 1.20. 

<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 doesn't count!"
rendered="#{numberGuess.cheat}"/>
Would you like to <a href="numberGuess.seam">play again</a>?
</f:view>
</body>
</html>
</jsp:root>

As is lose.jspx (which I can't be bothered copy/pasting). Finally, the JavaBean Seam component:

Example 1.21. 

@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 (much more about that later), and specifies the pageflow definition to use for the conversation's page flow.

Example 1.22. 

<?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.0.xsd">

<page view-id="/numberGuess.jspx">
<begin-conversation join="true" pageflow="numberGuess"/>
</page>

<page view-id="/confirm.jspx">
<begin-conversation nested="true" pageflow="cheat"/>
</page>

</pages>

As you can see, this Seam component is pure business logic! It doesn't need to know anything at all about the user interaction flow. This makes the component potentially more reuseable.



JavaBeat 2006, India | javabeat home | About Us | partners directory | Advertise with us