By Ed Burns, March 2007 |
|
<!---->
This article shows how to use Project Dynamic Faces, included in the new Sun Web Developer Pack, to add first-class Ajax support to your JavaServer Faces technology-based application.
Beginning with an existing sample application, Virtual Trainer, from the book that the article's author wrote with Chris Schalk, JavaServer Faces: The Complete Reference, this article will show you how to add Ajax behavior to two of that application's pages. This example will illustrate two usage patterns for Project Dynamic Faces and also provide a springboard for discussing the Ajax techniques that Dynamic Faces employs.
First, this article will go through the Virtual Trainer application and call out the places where you will add Ajax features.
The application's welcome page contains two links at the upper right: Register and Login. The Sign Up Today! link at the bottom of the page points to the same page as the Register page. Click Register.
The screen capture in Figure 1 shows the non-Ajax version of the registration page. As you can see, this is a garden-variety JavaServer Faces technology-based page. Fill out the form but use jake
for the Userid text field, and submit the form.
|
Once you get past the validation errors, you will see the message Userid jake already exists! Please choose another.
Wouldn't it be nice if there were a button next to the Userid field that would allow the user to fire an Ajax request off to the server, sending only the user ID and checking only whether that user ID is available? With a few lines of code, you can create this functionality.
Now click the Login link in the application, and use jake
as both the user ID and the password. Once logged in, you will see a table of training events, as shown in Figure 2. Choose one training event by clicking the Select link at the right of the desired row.
|
You now see a page allowing the trainer, jake, to advise the trainee on how to train for the selected event. For every training session for which the user has selected Completed in the table, the user will be able to type in the Personal Notes field only if the Completed checkbox is not checked. If you deselect a Completed checkbox and click the Update Event button, the Personal Notes field becomes editable.
But why bother with the extra step of clicking the Update Event button? With a few lines of code, you can remove the Update Event button and enable the application to update the form with Ajax whenever the user selects the checkbox.
Now that you know what you want to do, start by configuring your software stack and environment.
Download the Java Platform, Enterprise Edition (Java EE) SDK or the Platform Edition of Sun Java System Application Server version 9.0 or 9.1, and install and configure it according to the instructions that come with the download. Also download the Sun Web Developer Pack (SWDP) and follow the instructions to install it on top of the SDK or Application Server. The rest of this article assumes you are using NetBeans IDE 5.5 to build and run the example, though you need not do so.
For maximum ease, install the NetBeans IDE modules that come with the SWDP according to the instructions provided in the SWDP download. Doing this will make it easy to add Dynamic Faces support to your application.
Now download the source bundle for this article and the original source for the Virtual Trainer application. Once you have the NetBeans IDE and the SWDP modules installed, open the Virtual Trainer NetBeans project from the article download. Right click on the Virtual Trainer project and choose Properties. Highlight the Libraries tab and make sure that SWDP-jsf-extensions
is in the list.
Finally, modify the application's web.xml
file to enable the Ajax life cycle supplied with Dynamic Faces. Locate the <servlet>
declaration for the <servlet-class>
javax.faces.webapp.FacesServlet
. In this declaration, add the following XML:
<!----><!---->
<init-param> <param-name>javax.faces.LIFECYCLE_ID</param-name> <param-value>com.sun.faces.lifecycle.PARTIAL</param-value> </init-param> |
This advises the FacesServlet
to use the Ajax life cycle instead of the standard JavaServer Faces request processing life cycle. The Ajax life cycle decorates the standard one and adds the handling necessary for Ajax.
register.jsp
Page
Now comes the fun part. Let's start with the first task, the Check for ID Availability button. Open the register.jsp
page from within the Web Pages node of the Virtual Trainer project. After the two JavaServer Faces taglib
declarations, add the following:
<!----><!---->
<%@ taglib uri="http://java.sun.com/jsf/extensions/dynafaces" prefix="jsfExt"%> |
<!---->
This is line 3 in Listing 1. If you have correctly installed the NetBeans SWDP modules, you should see autocompletion in the uri=""
entry, and you should be able to choose from the list.
Add an attribute to the <h:form>
tag: <!----><!---->
prependId="false" |
This new attribute in JavaServer Faces 1.2 technology advises the JavaServer Faces runtime to not prepend any naming container IDs to your component IDs within the form. The result is that the IDs you type in the markup are the actual ones that appear in the page. This is important in reducing page size and in making it easier to address markup from JavaScript code. This is line 16 in Listing 1.
After the <h:form prependId="false" />
add the following line:
<!----><!---->
<jsfExt:scripts /> |
This is one of two tags in the Dynamic Faces tag library, and it simply instructs the runtime to render the <script>
tags necessary for the Dynamic Faces Ajax functionality.
Now you can add the button. Find the line that contains the string Register_Backing.userid
. This bit of markup renders the user ID: label, text field, and a validation message. You will now add a button and another label. After the <h:inputText>
, add the following code:
<!----><!---->
<h:commandButton id="userIDAvailable" value="#{res[\'register.userIDAvailableButton\']}" actionListener="#{Register_Backing.checkUserIDAvailable}" onclick="DynaFaces.fireAjaxTransaction(this, { execute: 'userid,userIDAvailable', render: 'userIDAvailableMessage', immediate: true}); return false;" /> <h:outputText style="{color: red}" id="userIDAvailableMessage" value="#{requestScope.userIDAvailableMessage}" /> |
This is line 88 in Listing 1. Here you have a regular JavaServer Faces h:commandButton
. You have given it the ID of userIDAvailable
, added an actionListener
, and most importantly, added an onclick
handler. This handler causes the JavaScript function DynaFaces.fireAjaxTransaction()
to be invoked when the user clicks the button. This function makes an Ajax transaction back to the JSF server, preserving the full JSF view state and allowing the full JSF life cycle to run by way of Ajax. To prevent the browser from submitting the form, because the form submittal will happen over Ajax in this case, you must add return false;
as the last JavaScript line in the onclick
handler.
The arguments to the DynaFaces.fireAjaxTransaction()
function are fully explained in the Project Dynamic Faces reference materials, but let's look at what you need for this example.
The first argument is the JavaScript reference to the Document Object Model (DOM) element that originates this Ajax transaction. The second argument is a JavaScript associative array of name: value
pairs that describe the options given to this transaction. In this case, you have three options: execute
, render
, and immediate
.
The execute
option is a comma-separated list of JSF client IDs that will be traversed during the execute
portion of the JSF request processing life cycle. Normally in JavaServer Faces technology, the entire view is traversed during this part of the life cycle, but with Dynamic Faces, you can control what subtrees in the view are traversed. Note that each client ID named in this list, along with any children of those nodes, will be traversed. For a review of the JavaServer Faces life cycle, see the Sun Web Developer Pack Tutorial.
In this example, you want the execute
portion of the life cycle to hit the userid
and the userIDAvailable
nodes. These IDs correspond to the <h:inputText>
for the user ID and the <h:commandButton>
for the button to check for ID availability, respectively. You want to make sure that only these nodes are traversed because you need the user ID that the user entered to be submitted, and you want the actionListener
for the button to be executed.
The next option is render
. Like execute
, this option is a comma-separated list of client IDs. But unlike execute
, this list names the subtrees to be traversed during the render
portion of the JavaServer Faces life cycle. In this case, the value is userIDAvailableMessage
, which is an <h:outputText>
, as shown on line 93 of Listing 1. The userIdAvailableMessage
component simply outputs a request-scoped attribute that is named userIDAvailableMessage
.
The last option is immediate: true
. This option is like the immediate
option on JavaServer Faces components, except that it has an effect only on this particular run through the life cycle. Its operation and intent is exactly the same as in core JavaServer Faces technology. For more on the immediate
attribute, see the book JavaServer Faces: The Complete Reference. That's it for the rendering side of the activity.
Listing 1: The register.jsp
File
1. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
2. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
3. <%@ taglib uri="http://java.sun.com/jsf/extensions/dynafaces" prefix="jsfExt"%>
4. <f:view>
5. <f:loadBundle basename="com.jsfcompref.trainer.resources.UIResources"
6. var="res"/>
7. <html>
8. <head>
9. <meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></meta>
10. <title>
11. <h:outputText value="#{res.title} - #{res['register.pageTitle']}"/>
12. </title>
13. <link href="css/vt.css" rel="stylesheet" media="screen"></link>
14. <link rel="shortcut icon" href="images/favicon.ico">
15. </head>
16. <body><h:form prependId="false">
17. <jsfExt:scripts />
18. <table width="100%" border="0">
19. <tr>
20. <td>
21. <h1 align="center">
22. <h:graphicImage url="/images/vtlogo.jpg"
23. alt="#{res.title}"/>
24. <h:outputText value="#{res.title}"/>
25. </h1>
26. </td>
27. </tr>
28. <tr>
29. <td>
30. <f:subview id="loginbar">
31. <jsp:include page="loginbar.jsp"/>
32. </f:subview>
33. </td>
34. </tr>
35. <tr>
36. <td>
37. <p>
38. <h:outputText value="#{res[\'register.header\']}" styleClass="PageTitle"/></p>
39.
40. <h:messages globalOnly="true" infoClass="RegError"/>
41.
42. <h:panelGrid width="70%" columns="3" border="0">
43.
44. <h:outputLabel value="#{res[\'register.firstName\']}" for="fname" />
45. <h:inputText required="true" id="fname"
46. binding="#{Register_Backing.firstName}"/>
47. <h:message for="fname" errorClass="ValidateError"/>
48.
49. <h:outputLabel value="#{res[\'register.lastName\']}" for="lname" />
50. <h:inputText required="true" id="lname"
51. binding="#{Register_Backing.lastName}"/>
52. <h:message for="lname" errorClass="ValidateError"/>
53.
54. <h:outputLabel value="#{res[\'register.gender\']}" for="gender" />
55. <h:selectOneRadio required="true" id="gender"
56. binding="#{Register_Backing.gender}" >
57. <f:selectItem itemLabel="#{res[\'register.male\']}" itemValue="male"/>
58. <f:selectItem itemLabel="#{res[\'register.female\']}" itemValue="female"/>
59. </h:selectOneRadio>
60. <h:message for="gender" errorClass="ValidateError"/>
61.
62. <h:outputLabel value="#{res[\'register.dob\']}" for="dob"/>
63. <h:inputText id="dob" required="true"
64.
65. binding="#{Register_Backing.dob}" >
66. <f:convertDateTime pattern="mm-dd-yyyy"/>
67. <f:validator validatorId="pastDateValidate"/>
68. </h:inputText>
69. <h:message for="dob" errorClass="ValidateError"/>
70.
71. <h:outputLabel value="#{res[\'register.email\']}" for="email"/>
72. <h:inputText required="true" id="email"
73. binding="#{Register_Backing.email}"
74. validator="#{Register_Backing.validateEmail}"/>
75. <h:message for="email" errorClass="ValidateError"/>
76.
77. <h:outputLabel value="#{res[\'register.serviceLevel\']}" for="level"/>
78. <h:selectOneMenu binding="#{Register_Backing.serviceLevel}" id="level">
79. <f:selectItem itemLabel="#{res[\'register.basic\']}" itemValue="Basic"/>
80. <f:selectItem itemLabel="#{res[\'register.medium\']}" itemValue="Medium"/>
81. <f:selectItem itemLabel="#{res[\'register.premium\']}" itemValue="Premium"/>
82. </h:selectOneMenu>
83. <f:verbatim> </f:verbatim>
84.
85. <h:outputLabel value="#{res[\'register.userid\']}" for="userid"/>
86. <h:inputText required="true" id="userid" autocomplete="off"
87. binding="#{Register_Backing.userid}"/>
88. <h:commandButton id="userIDAvailable"
89. value="#{res[\'register.userIDAvailableButton\']}"
90. actionListener="#{Register_Backing.checkUserIDAvailable}"
91. onclick="DynaFaces.fireAjaxTransaction(this, { execute: 'userid,userIDAvailable',
92. render: 'userIDAvailableMessage', immediate: true}); return false;" />
93. <h:outputText style="{color: red}" id="userIDAvailableMessage"
94. value="#{requestScope.userIDAvailableMessage}" />
95. <h:message for="userid" errorClass="ValidateError"/>
96. <br />
97. <h:outputLabel value="#{res[\'register.password\']}" for="password"/>
98. <h:inputSecret required="true" id="password"
99. binding="#{Register_Backing.password}" />
100. <h:message for="password" errorClass="ValidateError"/>
101.
102. <h:outputLabel value="#{res[\'register.passwordReType\']}" for="password2"/>
103. <h:inputSecret required="true" id="password2"
104. binding="#{Register_Backing.passwordCheck}"
105. validator="#{Register_Backing.validatePassword}"/>
106. <h:message for="password2" errorClass="ValidateError"/>
107.
108.
109. <f:verbatim> </f:verbatim> 110. <h:panelGroup> 111. <h:commandButton value="#{res[\'register.registerButton\']}" 112. action="#{Register_Backing.registerUser}"/> 113. <f:verbatim> </f:verbatim> 114. <h:commandButton value="#{res[\'register.cancelButton\']}" action="cancel" 115. immediate="true"/> 116. </h:panelGroup> 117. 118. </h:panelGrid> 119. </td> 120. </tr> 121. </table> 122. </h:form></body> 123. </html> 124. </f:view> |
<!---->
Listing 2 shows a diff
, a file comparison showing the difference between two versions of the same file, of the register.jsp
file before and after the changes that this article discusses. As you can see, the author of this article added or changed only 11 lines.
Listing 2: diff
of Old and New register.jsp
Files
1. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> 2. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> 3. +<%@ taglib uri="http://java.sun.com/jsf/extensions/dynafaces" prefix="jsfExt"%> 4. <f:view> 5. <f:loadBundle basename="com.jsfcompref.trainer.resources.UIResources" 6. var="res"/> 7. -12,7 +13,8 @@ 8. <link href="css/vt.css" rel="stylesheet" media="screen"></link> 9. <link rel="shortcut icon" href="images/favicon.ico"> 10. </head> 11. - <body><h:form> 12. + <body><h:form prependId="false"> 13. + <jsfExt:scripts /> 14. <table width="100%" border="0"> 15. <tr> 16. <td> 17. -81,10 +83,17 @@ 18. <f:verbatim> </f:verbatim> 19. 20. <h:outputLabel value="#{res[\'register.userid\']}" for="userid"/> 21. - <h:inputText required="true" id="userid" 22. + <h:inputText required="true" id="userid" autocomplete="off" 23. binding="#{Register_Backing.userid}"/> 24. + <h:commandButton id="userIDAvailable" 25. + value="#{res[\'register.userIDAvailableButton\']}" 26. + actionListener="#{Register_Backing.checkUserIDAvailable}" 27. + onclick="DynaFaces.fireAjaxTransaction(this, { execute: 'userid,userIDAvailable', 28. + render: 'userIDAvailableMessage', immediate: true}); return false;" /> 29. + <h:outputText style="{color: red}" id="userIDAvailableMessage" 30. + value="#{requestScope.userIDAvailableMessage}" /> 31. <h:message for="userid" errorClass="ValidateError"/> 32. - 33. +<br /> 34. <h:outputLabel value="#{res[\'register.password\']}" for="password"/> 35. <h:inputSecret required="true" id="password" 36. binding="#{Register_Backing.password}" /> |
ActionListener
Method
The next step is to define the method called by the actionListener
attribute on line 90 of Listing 1: <!----><!---->
actionListener="#{Register_Backing.checkUserIDAvailable}" |
Listing 3 shows this new method. This is a plain-old JavaServer Faces ActionListener
method. It does not do anything specifically Ajax-related, but because you are using Dynamic Faces, it will be invoked by Ajax. On line 2 of Listing 3, you get the value of the Userid field. Note that this component is bound with a JavaServer Faces component binding, as shown on line 87 of Listing 1. For more detail on component bindings, see the book JavaServer Faces: The Complete Reference.
Because you are running the full JavaServer Faces life cycle, you know that the value has been validated and converted according to any validators or converters attached to the component.
Line 3 of Listing 3 uses a class specific to the Virtual Trainer application, UserRegistry
, which provides a handy method, userIdAlreadyExists
. You simply pass the user ID from the text field to this method and store a message in request scope, as shown on lines 4 through 14 of Listing 3.
Because this application is already internationalized, you will use the ResourceBundle
for the application to get the message. Note that you are passing the current locale from the ViewRoot
into the ResourceBundle.getLocale()
method. This ensures that the language settings sent by the browser are correctly handled with respect to the supported localizations for this application.
Listing 3: The checkUserIDAvailable
Method
1. public void checkUserIDAvailable(ActionEvent e) { 2. String userId = (String) this.getUserid().getValue(); 3. UserRegistry managedUserRegistry = (UserRegistry) JSFUtil.getManagedObject("UserRegistry"); 4. boolean exists = managedUserRegistry.userIdAlreadyExists(userId); 5. Map<String,Object> requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap(); 6. ResourceBundle rb = ResourceBundle.getBundle("com.jsfcompref.trainer.resources.UIResources", FacesContext.getCurrentInstance().getViewRoot().getLocale()); 7. String message = null; 8. try { 9. message = exists ? rb.getString("register.userIDExists") : rb.getString("register.userIDDoesNotExist"); 10. } 11. catch (MissingResourceException mre) { 12. message = exists ? "UserID already exists" : "UserID is available"; 13. } 14. requestMap.put("userIDAvailableMessage", message); 15. } |
As mentioned earlier, Virtual Trainer is a localized application, so you will have to add the values for the labels to the ResourceBundle
. In this case, the bundle is in the Source Packages node in the NetBeans tree, within the com.jsfcompref.trainer.resources
package. Edit the file UIResources.properties
and add the following entries:
<!----><!---->
register.userIDAvailableButton=Check for availability of userid register.userIDExists=That userid already exists. Please choose another one. register.userIDDoesNotExist=Userid is available. |
After making these changes, clean, build, and deploy the application. When you visit the revised registration page, type jake
as the user ID, click on the Check for Availability of Userid button, and enjoy the benefits of Ajax. The results appear in Figure 3.
|
The DynaFaces.fireAjaxTransaction()
method is best used when you want to add Ajax behavior to a specific element in your user interface, and you can do so by manually adding a JavaScript event handler to the markup for your page.
But there will be times when you may want to Ajaxify an individual element even though you do not have access to the markup for the element. One example is when you are using a JavaServer Faces component that renders complex nested markup, such as a result set scroller. In that case, if you want to Ajaxify the individual subelements of that component, you should use the DynaFaces.installDeferredAjaxTransaction()
function, as described in the Sun Web Developer Pack Tutorial.
edit_te.jsp
Page
In addition to the JavaScript functions exposed on the DynaFaces
JavaScript object, the JavaServer Pages (JSP) technology and Facelets tag called ajaxZone
allows you to Ajaxify your page with little or no JavaScript coding. The following example shows how to do this.
Open the edit_te.jsp
page from within the Web Pages/app
node in the NetBeans tree view. As before, you must add the taglib
for Dynamic Faces:
<!----><!---->
<%@ taglib uri="http://java.sun.com/jsf/extensions/dynafaces" prefix="jsfExt"%> |
<!---->
The only other addition to make is to add the ajaxZone
around the portion of the page you want to Ajaxify. In this case, you want to Ajaxify the table. Find the sessionsTable
element, and add this line before it:
<!----><!---->
<jsfExt:ajaxZone id="autoUpdate"> |
Find the corresponding ending element of the table and add the closing element for ajaxZone
:
<!----><!---->
</jsfExt:ajaxZone> |
Many options are available for an Ajax zone, and these are fully described in the Project Dynamic Faces reference materials. Briefly, putting components within an Ajax zone causes them to be imbued with Ajax behavior such that clicking on the element will cause an Ajax transaction to the server, allowing just those components within the zone to update themselves by way of Ajax. The Sun Web Developer Pack Tutorial describes how to make action in one zone cause a reaction in another zone, all by way of Ajax.
This article shows how to use Dynamic Faces to Ajaxify an existing application using both the DynaFaces.fireAjaxTransaction()
JavaScript function and the <jsfExt:ajaxZone>
tag.