- Static Navigation
- Dynamic Navigation
- Advanced Navigation Issues
Dynamic Navigation
In most web applications, navigation is not static. The page flow doesn't just depend on which button you click, but also on the inputs that you provide. For example, submitting a login page may have two outcomes: success or failure. The outcome depends on a computation, namely, whether the username and password are legitimate.
To implement dynamic navigation, the submit button must have a method reference, such as
<h:commandButton label="Login" action="#{loginController.verifyUser}"/>
In our example, loginController references a bean of some class, and that class must have a method named verifyUser.
A method reference in an action attribute has no parameters and a return type String. For example, the verifyUser method should look somewhat like this:
String verifyUser() { if (...) return "success"; else return "failure"; }
The method returns an outcome string such as "success" or "failure". The navigation handler uses the returned string to look up a matching navigation rule.
An action method may return null to indicate that the same page should be redisplayed.
In summary, here are the steps that are carried out whenever the user clicks a command button whose action attribute is a method reference.
-
The specified bean is retrieved.
-
The referenced method is called.
-
The resulting string is passed to the navigation handler. (As explained on page 82, the navigation handler also receives the method reference string.)
-
The navigation handler looks up the next page.
Thus, to implement branching behavior, you supply a reference to a method in an appropriate bean class. You have wide latitude about where to place that method. The best approach is to find a class that has all of the data that you need for decision making.
Let us work through this process in an actual application. Our sample program presents the user with a sequence of quiz questions (see Figure 3-1).
Figure 3-1 A Quiz Question
When the user clicks the "Check answer" button, the application checks whether the user provided the correct answer. If not, the user has one additional chance to answer the same problem (see Figure 3-2).
Figure 3-2 One Wrong Answer: Try Again
After two wrong answers, the next problem is presented (see Figure 3-3).
Figure 3-3 Two Wrong Answers: Move On
And, of course, after a correct answer, the next problem is presented as well. Finally, after the last problem, a summary page displays the score and invites the user to start over (see Figure 3-4).
Figure 3-4 Done with the Quiz
Our application has two classes. The Problem class, shown in Listing 3-1, describes a single problem, with a question, an answer, and a method to check whether a given response is correct.
Example 3-1. javaquiz/WEB-INF/classes/com/corejsf/Problem.java
1. package com.corejsf; 2. 3. public class Problem { 4. private String question; 5. private String answer; 6. 7. public Problem(String question, String answer) { 8. this.question = question; 9. this.answer = answer; 10. } 11. 12 public String getQuestion() { return question; } 13. 14 public String getAnswer() { return answer; } 15. 16 // override for more sophisticated checking 17 public boolean isCorrect(String response) { 18 return response.trim().equalsIgnoreCase(answer); 19. } 20. }
The QuizBean class describes a quiz that consists of a number of problems. A QuizBean instance also keeps track of the current problem and the total score of a user. You will find the complete code in Listing 3-2.
In this example, the QuizBean is the appropriate class for holding the navigation methods. That bean has all the knowledge about the user's actions, and it can determine which page should be displayed next.
Have a glance at the code inside the answerAction method of the QuizBean class. The method returns one of the strings "success" or "done" if the user answered the question correctly, "again" after the first wrong answer, and "failure" or "done" after the second wrong try.
public String answerAction() { tries++; if (problems[currentProblem].isCorrect(response)) { score++; if (currentProblem == problems.length - 1) { return "done"; } else { nextProblem(); return "success"; } } else if (tries == 1) { return "again"; } else { if (currentProblem == problems.length - 1) { return "done"; } else { nextProblem(); return "failure"; } } }
We attach the answerAction method reference to the buttons on each of the pages. For example, the index.jsp page contains the following element:
<h:commandButton value="Check answer" action="#{quiz.answerAction}"/>
Here, quiz is the QuizBean instance that is defined in faces-config.xml.
Figure 3-5 shows the directory structure of the application. Listing 3-3 shows the main quiz page index.jsp. The more.jsp and failure.jsp pages are omitted. They differ from index.jsp only in the message at the top of the page.
Figure 3-5 Directory Structure of the Java Quiz Application
The done.jsp page in Listing 3-4 shows the final score and invites the user to play again. Pay attention to the command button on that page. It looks as if we could use static navigation, since clicking the "Start over" button always returns to the index.jsp page. However, we use a method reference.
<h:commandButton value="Start over" action="#{quiz.startOverAction}"/>
The startOverAction method carries out useful work that needs to take place to reset the game. It resets the score and reshuffles the response items.
public String startOverAction() { startOver(); return "startOver"; }
In general, action methods have two roles:
-
to carry out the model updates that are a consequence of the user action
-
to tell the navigation handler where to go next
As you will see in Chapter 7, you can also attach action listeners to buttons. When the button is clicked, the code in the processAction method of the action listener is executed. However, action listeners do not interact with the navigation handler.
Listing 3-5 shows the application configuration file with the navigation rules.
Because we selected our outcome strings so that they uniquely determine the successor web page, we can use a single navigation rule:
<navigation-rule> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/success.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>again</from-outcome> <to-view-id>/again.jsp</to-view-id> </navigation-case> ... </navigation-rule>
Figure 3-6 shows the transition diagram.
Figure 3-6 The Transition Diagram of the Java Quiz Application
Finally, Listing 3-6 shows the message strings.
Example 3-2. javaquiz/WEB-INF/classes/com/corejsf/QuizBean.java
1. package com.corejsf; 2. 3. public class QuizBean { 4. private int currentProblem; 5. private int tries; 6. private int score; 7. private String response; 8. private String correctAnswer; 9. 10. // here, we hardwire the problems. In a real application, 11. // they would come from a database 12. private Problem[] problems = { 13. new Problem( 14. "What trademarked slogan describes Java development? Write once, ...", 15. "run anywhere"), 16. new Problem( 17. "What are the first 4 bytes of every class file (in hexadecimal)?", 18. "CAFEBABE"), 19. new Problem( 20. "What does this statement print? System.out.println(1+\"2\");", 21. "12"), 22. new Problem( 23. "Which Java keyword is used to define a subclass?", 24. "extends"), 25. new Problem( 26. "What was the original name of the Java programming language?", 27. "Oak"), 28. new Problem( 29. "Which java.util class describes a point in time?", 30. "Date") 31. }; 32. 33. public QuizBean() { startOver(); } 34. 35. // PROPERTY: question 36. public String getQuestion() { 37. return problems[currentProblem].getQuestion(); 38. } 39. 40. // PROPERTY: answer 41. public String getAnswer() { return correctAnswer; } 42. 43. // PROPERTY: score 44. public int getScore() { return score; } 45. 46. // PROPERTY: response 47. public String getResponse() { return response; } 48. public void setResponse(String newValue) { response = newValue; } 49. 50. public String answerAction() { 51. tries++; 52. if (problems[currentProblem].isCorrect(response)) { 53. score++; 54. nextProblem(); 55. if (currentProblem == problems.length) return "done"; 56. else return "success"; 57. } 58. else if (tries == 1) { 59. return "again"; 60. } 61. else { 62. nextProblem(); 63. if (currentProblem == problems.length) return "done"; 64. else return "failure"; 65. } 66. } 67. 68. public String startOverAction() { 69. startOver(); 70. return "startOver"; 71. } 72. 73. private void startOver() { 74. currentProblem = 0; 75. score = 0; 76. tries = 0; 77. response = ""; 78. } 79. 80. private void nextProblem() { 81. correctAnswer = problems[currentProblem].getAnswer(); 82. currentProblem++; 83. tries = 0; 84. response = ""; 85. } 86. }
Example 3-3. javaquiz/index.jsp
1. <html> 2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4. 5. <f:view> 6. <f:loadBundle basename="com.corejsf.messages" var="msgs"/> 7. <head> 8. <title><h:outputText value="#{msgs.title}"/></title> 9. </head> 10. <body> 11. <h:form> 12. <p> 13. <h:outputText value="#{quiz.question}"/> 14. </p> 15. <p> 16. <h:inputText value="#{quiz.response}"/> 17. </p> 18. <p> 19. <h:commandButton value="#{msgs.answerButton}" 20. action="#{quiz.answerAction}"/> 21. </p> 22. </h:form> 23. </body> 24. </f:view> 25. </html>
Example 3-4. javaquiz/done.jsp
1. <html> 2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4. <f:view> 5. <f:loadBundle basename="com.corejsf.messages" var="msgs"/> 6. <head> 7. <title><h:outputText value="#{msgs.title}"/></title> 8. </head> 9. <body> 10. <h:form> 11. <p> 12. <h:outputText value="#{msgs.thankYou}"/> 13. <h:outputText value="#{msgs.score}"/> 14. <h:outputText value="#{quiz.score}"/>. 15. </p> 16. <p> 17. <h:commandButton value="#{msgs.startOverButton}" 18. action="#{quiz.startOverAction}"/> 19. </p> 20. </h:form> 21. </body> 22. </f:view> 23. </html>
Example 3-5. javaquiz/WEB-INF/faces-config.xml
1. <?xml version="1.0"?> 2. 3. <!DOCTYPE faces-config PUBLIC 4. "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN" 5. "http://java.sun.com/dtd/web-facesconfig_1_0.dtd"> 6. 7. <faces-config> 8. <navigation-rule> 9. <navigation-case> 10. <from-outcome>success</from-outcome> 11. <to-view-id>/success.jsp</to-view-id> 12. <redirect/> 13. </navigation-case> 14. <navigation-case> 15. <from-outcome>again</from-outcome> 16. <to-view-id>/again.jsp</to-view-id> 17. </navigation-case> 18. <navigation-case> 19. <from-outcome>failure</from-outcome> 20. <to-view-id>/failure.jsp</to-view-id> 21. </navigation-case> 22. <navigation-case> 23. <from-outcome>done</from-outcome> 24. <to-view-id>/done.jsp</to-view-id> 25. </navigation-case> 26. <navigation-case> 27. <from-outcome>startOver</from-outcome> 28. <to-view-id>/index.jsp</to-view-id> 29. </navigation-case> 30. </navigation-rule> 31. 32. <managed-bean> 33. <managed-bean-name>quiz</managed-bean-name> 34. <managed-bean-class>com.corejsf.QuizBean</managed-bean-class> 35. <managed-bean-scope>session</managed-bean-scope> 36. </managed-bean> 37. 38. </faces-config>
Example 3-6. javaquiz/WEB-INF/classes/com/corejsf/messages.properties
1. title=A Java Trivia Quiz 2. answerButton=Check Answer 3. startOverButton=Start over 4. correct=Congratulations, that is correct. 5. notCorrect=Sorry, that was not correct. Please try again! 6. stillNotCorrect=Sorry, that was still not correct. 7. correctAnswer=The correct answer was: 8. score=Your score is 9. thankYou=Thank you for taking the quiz.