 | Level: Intermediate Andrei Cioroianu, Senior Java Developer and Consultant, Devsphere
07 Aug 2007 In this three-part series, author and Java™ developer Andrei Cioroianu shows you how to automatically save form data in a Java Web application using Asynchronous JavaScript + XML (Ajax) and JavaServer Faces (JSF) technologies. You'll learn how to submit Web forms with Ajax, how to use the JSF framework to handle Ajax requests, how to control the JSF request processing life cycle, how to manage form data on the server side, and how to identify anonymous users across browser sessions. Discover several frequently occurring development mistakes, including incorrect form-data encoding and improper Ajax request management, which can lead to failed requests and memory leaks.
Introduction
Many desktop applications let users save documents at any time, and many products
automatically save documents being edited to minimize data loss if the application
crashes. When users interact with Web applications, typically their data is saved only
when the form is submitted to the server. Most Web applications don't allow users to
save partially filled forms, close the browser, and resume the work later. In addition,
if the user is suddenly disconnected because of a network problem, he/she cannot save data and some of their work might be lost.
 |
The developerWorks Ajax resource center
Check out the Ajax Resource Center, your one-stop shop for information on the Ajax programming model, including articles and tutorials, discussion forums, blogs, wikis, events, and news. If it's happening, it's covered here. |
|
Ajax is the ideal solution for solving these types of issues. When form data is sent
with Ajax, the page doesn't need to be refreshed and the scroll position can remain
unchanged, just as if you were working with a desktop application. Your users will
appreciate the auto-saving capability, especially when they fill out complex forms or
when there is a risk of data loss. For example, a Web support form should be saved
automatically and periodically if users are testing a product while they fill out the
support form for reporting problems. The users' systems might become unstable during the
product testing and they might need to restart the computer several times before submitting the completed form. Form auto-saving can save time and potential data loss in such a case.
This first installment of the three-part series focuses on sending form data with Ajax
and handling Ajax requests with JSF. It shows you the complete data flow that is
implemented for auto-saving and covers how to get, encode, and submit form data in a
Web browser using JavaScript. This article also shows how a JSF listener handles the submitted data on the server side, customizing the JSF request processing life cycle so that it can efficiently deal with Ajax requests. You can apply the techniques from this article in any Java Web application that is based on Ajax and JSF, whether you need form auto-saving or another similar feature.
 | | When users interact with Web applications, typically their data is saved only when the form is submitted to the server. Most Web applications don't allow users to save partially filled forms, close the browser, and resume the work later. In addition, if the user is suddenly disconnected because of a network problem, he/she cannot save data and some of their work may be lost. Ajax is the ideal solution for solving these types of issues. |
|
Obtaining the form data on the client side
This section presents a JSF form whose data is extracted in the Web browser using
JavaScript and DOM. You can reuse the JavaScript code in your own applications with any
Web forms. This sections also explains how to encode the form data properly to submit it to the server.
Building the JSF form
Let's take a quick look at a typical JSF example. The SupportForm.jsp page contains basic HTML components, such as input
boxes, lists, radio buttons, a check box, and a submit button. All input components have
their values bound to the properties of a JavaBean named SupportBean. The page's header contains a <script> tag importing the AutoSaveScript.js file (see Download). This JavaScript file contains a function named setAutoSaving() that is called within the onload attribute of the <body> tag to activate the form auto-saving after the Web browser loads the page. Listing 1 shows the partial source code of the SupportForm.jsp page:
Listing 1. The SupportForm.jsp page containing an example JSF form
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<head>
<title>Support Form</title>
<script src="AutoSaveScript.js">
</script>
</head>
<body onload="setAutoSaving(10000)">
<f:view>
<h1>Support Form</h1>
<h:form id="supportForm">
<p><h:outputText value="Name: "/>
<h:message for="name"/><br>
<h:inputText id="name" value="#{supportBean.name}"
size="40" required="true">
</h:inputText>
...
<p><h:outputText value="Platform: "/>
<h:message for="platform"/><br>
<h:selectOneRadio id="platform" value="#{supportBean.platform}"
layout="lineDirection" required="true">
<f:selectItem itemValue="Windows" itemLabel="Windows"/>
<f:selectItem itemValue="Linux" itemLabel="Linux"/>
<f:selectItem itemValue="Mac" itemLabel="Mac"/>
</h:selectOneRadio>
...
<p><h:outputText value="Problem: "/>
<h:message for="problem"/><br>
<h:inputTextarea id="problem" value="#{supportBean.problem}"
rows="10" cols="40" required="true"/>
<p><h:commandButton id="submit" value="Submit"
action="#{supportBean.submit}"/>
</h:form>
</f:view>
</body>
</html>
|
When a user clicks a JSF link or enters a URL to open a JSF page, the Web browser
builds an HTTP request and sends it to the Web server, which identifies the application
containing the page and invokes the FacesServlet (configured
in web.xml) to handle the request. After some context
initialization, the page is executed and the JSF framework creates a component tree that
mirrors the JSF tags used in the Web page. The renderers of those components generate
the HTML code (shown in Listing 2), which contains the form elements:
Listing 2. The HTML code generated by the SupportForm.jsp page
<html>
<head>
<title>Support Form</title>
<script src="AutoSaveScript.js">
</script>
</head>
<body onload="setAutoSaving(10000)">
<h1>Support Form</h1>
<form id="supportForm" method="post"
action="/autosave/faces/SupportForm.jsp"
enctype="application/x-www-form-urlencoded">
<p>Name: <br>
<input id="supportForm:name" type="text"
name="supportForm:name" size="40" />
...
<p>Platform: <br>
<table id="supportForm:platform">
<tr>
<td><label><input type="radio" name="supportForm:platform"
value="Windows"> Windows</input></label></td>
<td><label><input type="radio" name="supportForm:platform"
value="Linux"> Linux</input></label></td>
<td><label><input type="radio" name="supportForm:platform"
value="Mac"> Mac</input></label></td>
</tr>
</table>
...
<p>Problem: <br>
<textarea id="supportForm:problem" name="supportForm:problem"
cols="40" rows="10">
</textarea>
<p><input id="supportForm:submit" type="submit"
name="supportForm:submit" value="Submit" />
<input type="hidden" name="com.sun.faces.VIEW"
value="H4sIAA..." />
<input type="hidden" name="supportForm" value="supportForm" />
</form>
</body>
</html>
|
At the end of the HTML form, you will notice some hidden elements. These are internally used by the JSF implementation to identify the submitted form and to store the state of the component tree between requests if the javax.faces.STATE_SAVING_METHOD parameter is set to client in the web.xml file. From the browser's perspective, a JSF form is just like any other HTML form and you can use JavaScript and DOM to access the form's elements in the Web browser.
Getting and encoding the form data
The AutoSaveScript.js file contains a JavaScript function named getFormData(), which takes a form object and iterates over its elements to build a string containing the name-value pairs. This string follows the standard application/x-www-form-urlencoded format, separating the parameters with & and using = between the name and value of each parameter. The addParam() inner function encodes a single parameter, using the escape() function, which is provided by the JavaScript API. The escape() function replaces almost any non-alphanumeric ASCII character with % followed by the two-digit hexadecimal code of the encoded character. I say "almost any non-alphanumeric ASCII character" because there are a few other characters (such as +) that are included as unencoded along with the alphanumeric characters.
For example, if you pass the string a + b to escape(), you get a%20+%20b (20 is the hexadecimal code of the space character). This is a valid
URL encoding as specified by RFC 1738, but if you submit the encoded string to a
server-side script such as a JSP, you will get a space instead of the + character. This is also correct because RFC 1866, which describes the application/x-www-form-urlencoded format, states that space characters are encoded as + and any non-alphanumeric characters are replaced with % followed by the hexadecimal code. When the server decodes the string, any + character is reverted to a space.
In conclusion, the URL encoding performed by escape() is not
exactly the same as application/x-www-form-urlencoded because
escape() leaves the + characters
unencoded while in the case of application/x-www-form-urlencoded, spaces can be encoded as + characters. The easiest way to solve this issue is to encode any
+ character as %2B (2B is the hexadecimal code of +). You can
perform this operation on the result returned by escape(),
using replace(). In this example, you would encode the string a + b as a%20%2B%20b. Listing 3 is the addParam() inner function, which adds an encoded name-value pair to the local dataString variable of getFormData():
Listing 3. Encoding a single request parameter
function getFormData(form) {
var dataString = "";
function addParam(name, value) {
dataString += (dataString.length > 0 ? "&" : "")
+ escape(name).replace(/\+/g, "%2B") + "="
+ escape(value ? value : "").replace(/\+/g, "%2B");
}
...
}
|
The getFormData() function gets the elements array of the form object and calls addParam(), depending on each element's type. A single name-value pair is added for every text box, password, or hidden field. The value of a check box or radio button is encoded only if the form element is checked. In the case of a list, a name-value pair is added for each selected option. Then getFormData() returns the string containing the form's encoded data (see Listing 4):
Listing 4. Getting and encoding the form data
function getFormData(form) {
...
var elemArray = form.elements;
for (var i = 0; i < elemArray.length; i++) {
var element = elemArray[i];
var elemType = element.type.toUpperCase();
var elemName = element.name;
if (elemName) {
if (elemType == "TEXT"
|| elemType == "TEXTAREA"
|| elemType == "PASSWORD"
|| elemType == "HIDDEN")
addParam(elemName, element.value);
else if (elemType == "CHECKBOX" && element.checked)
addParam(elemName,
element.value ? element.value : "On");
else if (elemType == "RADIO" && element.checked)
addParam(elemName, element.value);
else if (elemType.indexOf("SELECT") != -1)
for (var j = 0; j < element.options.length; j++) {
var option = element.options[j];
if (option.selected)
addParam(elemName,
option.value ? option.value : option.text);
}
}
}
return dataString;
}
|
Submitting the form data with Ajax
This section shows how to send the form data to the JSF page using Ajax and also covers
related topics, such as error handling and the correct management of the XMLHttpRequest objects, which you need to know to prevent memory leaks in the Web browser.
Creating and sending Ajax requests
The submitFormData() function, whose code you can find in
the AutoSaveScript.js file (see Download), uses an Ajax request object to submit the encoded data to
the Web server. First of all, it needs to create the request object using ActiveXObject() for Microsoft® Internet Explorer or the XMLHttpRequest() constructor for all other Ajax-capable browsers,
such as Firefox, Netscape, Mozilla, Opera, and Safari. Listing 5 shows the code that creates the XMLHttpRequest object:
Listing 5. Creating an Ajax request object
function submitFormData(form) {
var xhr;
if (window.ActiveXObject)
xhr = new ActiveXObject("Microsoft.XMLHTTP");
else if (window.XMLHttpRequest)
xhr = new XMLHttpRequest();
else
return null;
...
}
|
The form's encoded data is submitted to the page identified by the action URL of the form, using the specified HTTP method, which is POST in the case of a JSF form. The submitFormData() function can also be used with non-JSF forms that may use the default GET method or POST. The code would work even if the form doesn't specify an action URL. In this case, submitFormData() would use the current page's URL, which is obtained with document.URL. The encoded form data is retrieved from the form object, using the getFormData() function, which was presented in the previous section. If the HTTP method is GET, the encoded data is appended to the URL after a ? character. Then, submitFormData() initializes the xhr object, using its open() method (see Listing 6):
Listing 6. Initializing the Ajax request object
function submitFormData(form) {
...
var method = form.method ? form.method.toUpperCase() : "GET";
var action = form.action ? form.action : document.URL;
var data = getFormData(form);
var url = action;
if (data && method == "GET")
url += "?" + data;
xhr.open(method, url, true);
...
}
|
An inner function named submitCallback() is invoked when the
response to the Ajax request is received from the server. This Ajax callback signals an
error if autoSaveDebug is true,
the request is completed (readyState is 4), but its status isn't OK (status is not
200). In case of an error, the status and statusText properties of the
xhr object are reported with alert() and the autoSaveDebug flag is set to false so that you don't get the error message again and again (because the form saving is performed periodically). If you reload the page, however, the JavaScript code will be reinitialized and you'll see the error message again if the problem that generated the HTTP error still hasn't been fixed. This behavior is suitable for debugging during the development phase. In a production environment, it would be better to redirect the user to another page instead of showing the alert message. In any case, the onreadystatechange property of the xhr object must contain a reference to submitCallback() so that the callback function can be invoked during the life cycle of the Ajax request (see Listing 7):
Listing 7. The callback function
var autoSaveDebug = true;
function submitFormData(form) {
...
function submitCallback() {
if (autoSaveDebug && xhr.readyState == 4
&& xhr.status != 200) {
autoSaveDebug = false;
alert("Auto-Save Error: "
+ xhr.status + " " + xhr.statusText);
}
}
xhr.onreadystatechange = submitCallback;
...
}
|
Next, submitFormData() sets the Ajax-Request header, which is specific to the sample application and is used to identify the Ajax requests on the server side as you'll see later in the article. If the HTTP method is POST, the submitFormData() function sets the standard Content-Type header and submits the form data to the JSF page, using the send() method of the xhr object. If the HTTP method is GET (currently not used by JSF forms), the form data has already been appended to the URL and send() is called with a null parameter. After submitting the form data, the function returns a reference to the xhr object (see Listing 8):
Listing 8. Sending the Ajax request
function submitFormData(form) {
...
xhr.setRequestHeader("Ajax-Request", "Auto-Save");
if (method == "POST") {
xhr.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
xhr.send(data);
} else
xhr.send(null);
return xhr;
}
|
Managing the XHR instances
When sending the form data repeatedly, you might be tempted to reuse the XMLHttpRequest (XHR) objects, but in most cases this is a bad idea
for several reasons. First, it would complicate your code because you would have to
manage a pool and track the life cycle of the XHR instances. Keep in mind that the state
of these objects is typically accessed after the HTTP request is completed and the
application code would have to signal to the code managing the pool when each XHR isn't
needed anymore. Also, some browsers seem to have problems reusing XHR objects for multiple requests.
Creating a new XHR object per HTTP request poses another problem because the browser
won't delete these objects from the memory as long as the application might need them. A Web page that sends Ajax requests from time to time would cause a memory leak in the Web browser if the application doesn't free the memory occupied by the XHR objects using the JavaScript delete operator. A good strategy that works in many cases is to abort a request and resend it if you don't get a response in a reasonable time frame. Both the server and the client must be prepared to get Ajax requests and responses out of order, and some of them might even be missed. If this isn't acceptable, you should probably use XMLHttpRequest to send synchronous requests.
Applications can use Ajax for auto-saving, but your forms should either provide good
old submit buttons or use synchronous requests for sending the data for processing. The
unreliable nature of the asynchronous requests is acceptable for auto-saving because this is implemented only to partially recover from a browser crash or network failure.
The AutoSaveScript.js file contains a function named submitAllForms(), which sends the data of all forms of the Web page. The XHR objects are kept in an array that allocates one element for each form. Before sending the data of a form with submitFormData(), the submitAllForms() function aborts and deletes the request that was used for the previous auto-saving of the form. Calling abort() on a successfully completed request won't do anything and onreadystatechange is set to an empty function just in case the browser gets a delayed response for an old request. Listing 9 shows the code that iterates over the forms of the page, submitting their data to the server:
Listing 9. Submitting all forms of the current page
var autoSaveXHR = new Array(document.forms.length);
function submitAllForms() {
var formArray = document.forms;
for (var i = 0; i < formArray.length; i++) {
if (autoSaveXHR[i]) {
var oldXHR = autoSaveXHR[i];
oldXHR.onreadystatechange = function() { };
oldXHR.abort();
delete oldXHR;
}
autoSaveXHR[i] = submitFormData(formArray[i]);
}
}
|
The XHR objects returned by submitFormData() will be deleted the next time submitAllForms() is called. Another function of the AutoSaveScript.js file is setAutoSaving(), which enables the form auto-saving by using the setInterval() function of the JavaScript API. The browser will call submitAllForms() every time the specified number of milliseconds elapses until setAutoSaving() is used again to clear the effects of the previous call with clearInterval() (see Listing 10):
Listing 10. Enabling form auto-saving for the current page
var autoSaveIntervalId = null;
function setAutoSaving(millisec) {
if (autoSaveIntervalId) {
clearInterval(autoSaveIntervalId);
autoSaveIntervalId = null;
}
if (millisec != 0)
autoSaveIntervalId = setInterval(
"submitAllForms()", millisec);
}
|
Using a JSF listener for handling Ajax requests
So far, you've learned how to submit form data to a JSF page with Ajax. Now let's see
how to handle Ajax requests on the server side. This starts with a brief
description of the JSF request processing life cycle, containing all you need to know to understand the sample code included with this article. The JSF specification contains a complete presentation of the request processing life cycle, which you'll probably find very useful when developing your own JSF-based applications.
Understanding the JSF request processing life cycle
When processed by the JSF framework, a typical request that posts some form data goes
through six phases:
- Restore View
- Apply Request Values
- Process Validations
- Update Model Values
- Invoke Application
- Render Response
First, the framework needs to restore the component tree of the form page. Depending on the value of the javax.faces.STATE_SAVING_METHOD configuration parameter, the component tree may be deserialized from a request parameter, or it may be obtained from the HttpSession object.
Then, the JSF framework traverses the tree recursively, updating the state of the components. For example, an input component that implements EditableValueHolder will have its submittedValue property set to the corresponding request-parameter. If the immediate property of the component is true, the JSF framework also converts and validates the submitted value, setting the value property of the component. If the immediate property is false, the conversion and validation are performed during the following phase of the JSF request processing life cycle.
After the first three phases (Restore View, Apply Request Values, and Process
Validations), the component tree contains submitted form data, which has been decoded,
converted, and validated by the JSF framework. At this point, the application should save the values of the JSF components to be able to restore the data of the Web form later.
The processing life cycle of an Ajax request that auto-saves form data must be stopped after the Validation phase. Otherwise, the JSF framework would go to the next phase, updating the model values of the JavaBean properties that are bound to the JSF components. In the case of a normal form submit that occurs when the user clicks a button, the JSF framework would also invoke the action method associated with the command button. The final phase renders the HTML response. The last three phases of the request processing life cycle (Update Model Values, Invoke Application, and Render Response) are not needed when the form is auto-saved.
Implementing the PhaseListener interface
Form auto-saving should not interfere with the functionality of the application. This means no JavaBean properties should be set and no action method should be invoked when the form is submitted with Ajax for auto-saving purposes. In addition, no HTML response should be generated after an auto-save because the browser doesn't need any refresh. Therefore, the application needs to control the JSF request processing life cycle, which can be easily done by implementing a PhaseListener. In the sample application included with this article, the JSF listener class is named AutoSaveListener and is configured in the faces-config.xml file (see Listing 11):
Listing 11. Configuring the JSF phase listener
<faces-config>
<lifecycle>
<phase-listener>autosave.AutoSaveListener</phase-listener>
</lifecycle>
...
</faces-config>
|
As explained earlier, processing the auto-saving requests must be stopped after the validation phase so that the JSF framework doesn't update the JavaBean data model, which should remain unaffected by the auto-saving. Therefore, the listener is interested in receiving notifications only after the validation phase, whose ID is returned by the getPhaseId() method. If the request is marked with the Ajax-Request header, the listener's afterPhase() method invokes the responseComplete() method of the FacesContext object, signaling the JSF framework that it should stop processing the request (see Listing 12):
Listing 12. Implementing the JSF phase listener
package autosave;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import java.util.Iterator;
import java.util.Map;
public class AutoSaveListener implements PhaseListener {
public PhaseId getPhaseId() {
return PhaseId.PROCESS_VALIDATIONS;
}
public void beforePhase(PhaseEvent e) {
}
public void afterPhase(PhaseEvent e) {
System.out.println();
System.out.print(e.getPhaseId());
System.out.print(" - ");
FacesContext ctx = e.getFacesContext();
Map headers = ctx.getExternalContext().getRequestHeaderMap();
if ("Auto-Save".equals(headers.get("Ajax-Request"))) {
System.out.println("Auto-Save");
ctx.responseComplete();
} else
System.out.println("Submit");
printTree(ctx.getViewRoot(), 0);
}
...
}
|
Printing the component tree
In addition to stopping the request processing at the right time, the listener prints
the component tree using a recursive method named printTree(). This method outputs the component family, the renderer type, the unique id, and the validated value of each component (see Listing 13):
Listing 13. Printing the JSF component tree
public class AutoSaveListener implements PhaseListener {
...
public void printTree(UIComponent comp, int level) {
if (comp == null)
return;
Object value = null;
if (comp instanceof EditableValueHolder)
value = ((EditableValueHolder) comp).getValue();
for (int i = 0; i < level; i++)
System.out.print(" ");
System.out.print(comp.getFamily());
System.out.print(" - ");
System.out.print(comp.getRendererType());
System.out.print(" - [");
System.out.print(comp.getId());
System.out.print("]");
if (value != null) {
System.out.print(" - ");
if (value instanceof Object[]) {
Object array[] = (Object[]) value;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
System.out.print(" ");
}
} else
System.out.print(value);
}
System.out.println();
Iterator children = comp.getChildren().iterator();
while (children.hasNext()) {
UIComponent child = (UIComponent) children.next();
printTree(child, level + 1);
}
}
}
|
Listing 14 shows how the printed component tree looks:
Listing 14. The printed component tree
PROCESS_VALIDATIONS 3 - Auto-Save
javax.faces.ViewRoot - null - [null]
javax.faces.Form - javax.faces.Form - [supportForm]
javax.faces.Output - javax.faces.Text - [_id0]
javax.faces.Message - javax.faces.Message - [_id1]
javax.faces.Input - javax.faces.Text - [name] - John Smith
...
javax.faces.Output - javax.faces.Text - [_id11]
javax.faces.Message - javax.faces.Message - [_id12]
javax.faces.SelectOne - javax.faces.Radio - [platform] -
Windows
javax.faces.SelectItem - null - [_id13]
javax.faces.SelectItem - null - [_id14]
javax.faces.SelectItem - null - [_id15]
...
javax.faces.Output - javax.faces.Text - [_id26]
javax.faces.Message - javax.faces.Message - [_id27]
javax.faces.Input - javax.faces.Textarea - [problem] -
Unable to ...
javax.faces.Command - javax.faces.Button - [submit]
|
By getting the values of the input components that implement the EditableValueHolder interface, the printTree() method actually obtains the submitted form data from the
component tree. In Part 2 of this series, you'll find more JSF techniques for working with the component tree and controlling the request processing life cycle.
Conclusion
This article has taught you how to encode and submit form data with Ajax and how to
implement a JSF listener that retrieves submitted data from the JSF component tree.
Stay tuned for the second part of this series, where you will see how to manage form
data on the server side and how to restore the JSF form after the user closes and
reopens the browser. The solution that Part 2 presents works for a multiuser and a
multiform application and provides a method for identifying anonymous users across browser sessions.
Download | Description | Name | Size | Download method |
|---|
| Sample application for this article | wa-aj-jsf1.zip | 9KB | HTTP |
|---|
Resources Learn
Get products and technologies
Discuss
About the author  | |  | Andrei Cioroianu is a Senior Java Developer and Consultant at Devsphere, a provider of custom Java EE development and Ajax/JSF consulting services. You can reach Andrei through the contact form at www.devsphere.com. |
Rate this page
|  |