|
Articles Index
Contents
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.
Figure 1:
Screen Capture of Non-Ajax Virtual Trainer Registration Page
|
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.
Figure 2:
Screen Capture of Non-Ajax Virtual Trainer Event Information Page
|
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.
Application and Environment Setup
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.
Rendering Changes: Modifying the 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:
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:
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}" />
|
Code Changes: Adding the 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. }
|
Modifying the Resource Bundle
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.
Figure 3:
Screen Capture of Ajax Virtual Trainer Registration Page
|
Final Notes About This Example
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.
Adding Ajax to the 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:
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.
Conclusion
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.
For More Information
Source code for this bundle
Original source for the virtual trainer application
Download the Java EE SDK
Sun Web Developer Pack Tutorial
JavaServer Faces technology
Ajax Developer Resource Center
Ed Burns's blog
About the Author
Ed Burns is a senior staff engineer at Sun. He has
worked on a wide variety of client- and server-side web technologies
since 1994, including NCSA Mosaic, Mozilla, the Sun Java Plug-in,
Jakarta Tomcat, and JavaServer Faces technology.
|