All three articles of this series present a single Web application, which was progressively enhanced with each subsequent part. This section provides a brief overview of the sample application.
Part 1
starts with a typical JSF form named SupportForm.jsp and
presents a set of reusable JavaScript functions for getting, encoding, and submitting
form data with Ajax, which lets you save user input automatically, periodically, and transparently. You can find the source code of the JavaScript functions in the AutoSaveScript.js file of the sample application. Part 1 also explains how to build a JSF phase listener named AutoSaveListener, which handles the Ajax requests. The listener class was modified in Part 2.
In Part 2, you
see how to store the data of the current JSF view into a data repository. The DataMapRepository class is a Map containing data maps. Each data map maintains the user input of a single form instance. Any access to the data repository is made through a thread-safe wrapper class named RepositoryWrapper. A servlet context listener named DataMapPersistence is used to serialize the repository into a file before shutdown and restore the repository's state after startup. Part 2 also presents a servlet filter named BrowserIdFilter, which is used to identify anonymous users across browser sessions.
This article (the third and final part of the series) modifies the SupportForm.jsp page so that its data can be restored after the user
closes and reopens the browser. The ViewRestorer class
provides JSF listener methods that handle the restore requests. You might experience
difficulties in understanding the sample code of Part 3 if you aren't familiar with the JSF request processing life cycle. Part 1 contains a brief overview of this JSF mechanism and all articles of the series provide as many details as possible about the used JSF features. You can also find a full description of the request processing life cycle in the JSF specification.
Building a handler for the restore requests
This section presents the Java methods that are used to restore the data of the JSF form. These methods are used in combination with several JavaScript functions and JSF components, which are discussed later in this article.
Restoring the current view's data
Part 2 shows
how to save the form data of the current view by traversing the JSF component tree and getting the values of the input components. This operation is performed in the saveValues() method of the DataMapRepository class, which also contains a method for restoring the values of the input components. For each component that implements the EditableValueHolder interface, the restoreValues() method (shown in Listing 1) gets the previously saved value from the given data map, sets the value property of the JSF component, and clears the submittedValue property:
Listing 1. Restoring the values of the input components
package autosave;
...
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
...
public class DataMapRepository ... {
...
public static void restoreValues(UIComponent comp,
Map<String, Object> dataMap) {
if (comp == null || dataMap == null)
return;
if (comp instanceof EditableValueHolder) {
// Input component. Get its value from the data map
// and clear any submitted value
EditableValueHolder evh = (EditableValueHolder) comp;
evh.setValue(dataMap.get(comp.getId()));
evh.setSubmittedValue(null);
}
// Iterate over the children of the current component
Iterator children = comp.getChildren().iterator();
while (children.hasNext()) {
UIComponent child = (UIComponent) children.next();
// Recursive call
restoreValues(child, dataMap);
}
}
}
|
Part 2 also
presents the saveCurrentView() method of the AutoSaveListener class, which handles form auto-saving requests. This section discusses the ViewRestorer class of the sample application, which contains a method named restoreCurrentView() and two JSF event listeners that handle the requests for restoring the data of the current JSF view, simply called restore requests.
Listing 2 shows the restoreCurrentView() method, which accesses the repository through the wrapper bean to get the data map for the current user/view combination. Then, it calls the restoreValues() method to restore the values of the input components. After that, restoreCurrentView() invokes the renderResponse() method of the faces context to signal the JSF framework to go to the Render Response phase. The renderResponse() call is used in conjunction with the immediate attribute as you'll see later in this article.
Listing 2. Restoring the data of the current JSF view
package autosave;
import java.util.Map;
...
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
public class ViewRestorer implements java.io.Serializable {
public void restoreCurrentView() {
// Get the faces context of the current request.
FacesContext ctx = FacesContext.getCurrentInstance();
// Get the root component of the current view.
UIViewRoot root = ctx.getViewRoot();
// Get the managed bean instance wrapping the data repository.
RepositoryWrapper wrapper = RepositoryWrapper.getManagedBean(ctx);
// Get the data map for the current context.
Map<String, Object> dataMap = wrapper.getDataMap(ctx);
// Use the data map to restore the values of the JSF components.
DataMapRepository.restoreValues(root, dataMap);
// Signal the JSF framework to go to the Render Response phase.
ctx.renderResponse();
}
...
}
|
The actionListener() and valueChangeListener() methods (shown in Listing 3) of ViewRestorer can be used within the actionListener and valueChangeListener attributes of the JSF components, which should trigger the restore of the current view. The next section demonstrates how to use these listener methods.
Listing 3. JSF listeners for restore events
package autosave;
...
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
public class ViewRestorer implements java.io.Serializable {
...
public void actionListener(ActionEvent e) {
restoreCurrentView();
}
public void valueChangeListener(ValueChangeEvent e) {
restoreCurrentView();
}
...
}
|
The isCurrentViewRestorable() method returns true if the repository contains a data map for the current view and the current user (see Listing 4):
Listing 4. Verifying if the current view's data can be restored
package autosave;
...
public class ViewRestorer implements java.io.Serializable {
...
public boolean isCurrentViewRestorable() {
FacesContext ctx = FacesContext.getCurrentInstance();
RepositoryWrapper wrapper = RepositoryWrapper.getManagedBean(ctx);
return wrapper.hasDataMap(ctx);
}
}
|
Listing 5 shows how the ViewRestorer
class is configured as a managed bean in faces-config.xml so that the JSF components of the SupportForm.jsp page can use the listener methods:
Listing 5. Configuring the view restorer
<faces-config>
...
<managed-bean>
<managed-bean-name>viewRestorer</managed-bean-name>
<managed-bean-class>autosave.ViewRestorer</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
...
</faces-config>
|
The SupportForm.jsp page activates the form auto-saving
feature with the setAutoSaving() function of the AutoSaveScript.js file, which Part 1 presents. This section shows how to modify the JSF page so that the current view's data can be restored.
Using a hidden trigger for the restore requests
Adding a hidden element to the SupportForm.jsp page is the
simplest way to fire a ValueChangeEvent for the valueChangeListener() method of the ViewRestorer bean. Listing 6 shows the code that must be added to the JSF page:
Listing 6. Hidden trigger for restore events
<h:form id="supportForm">
<h:inputHidden id="restoreTrigger" value="default"
valueChangeListener="#{viewRestorer.valueChangeListener}"
immediate="true"/>
...
</h:form>
|
You might wonder why we're using a hidden component to trigger the form restore. Why can't you just restore the form when it is rendered? To understand the answer to this question, you have to know how the JSF framework builds the tree of components whose values must be restored. Keep in mind that the user has left the application (or the browser crashed) and now the user returns to the form page, using a GET request.
When handling a GET request in the scenario described above, the JSF framework will most likely not be able to restore the JSF component tree in the first phase (Restore View) of the request processing life cycle. Even if the JSF implementation could do that, the request will have no parameters because the user is just returning to the application.
According to the JSF specification, the JSF implementation must call the renderResponse() method of the faces context during the Restore View
phase if the view cannot be restored or if the request contains neither query parameters
nor POST data. Therefore, the request processing will jump to the last phase (Render
Response), which generates the HTML output and builds the component tree at the same
time because the tree was not restored earlier. This means the application has no chance to update the component tree during the processing of the GET request before it is too late and the output has already been sent to the user's browser.
Sending a POST request to restore the JSF form
The solution to the problem described above is to let the JSF framework handle the GET
request and then send a new POST request, which allows the application to process the JSF component tree, restoring the values of the components. The POST request can be sent with the JavaScript submit() method of the form object. You wouldn't use XMLHttpRequest this time because you want the page to be refreshed after the POST request.
If you look into the HTML output of the form page, you'll notice that JSF generates the
supportForm:restoreTrigger ID for the hidden element. Therefore, you need a JavaScript function like getFormElement() (shown in Listing 7) to locate a form element object in the Web page:
Listing 7. Finding a form element rendered by a JSF component
function getFormElement(formId, elemId) {
return document.getElementById(formId + ":" + elemId);
}
|
Listing 8 shows another JavaScript function, which sends the
request for restoring the form data. Before invoking the submit() method of the supportForm object, the submitRestoreRequest() function changes the value of the hidden element to
trigger the valueChangeListener() method of the ViewRestorer bean when the
request is processed on the server-side. You can find the submitRestoreRequest() function in the SupportForm.jsp file.
Listing 8. Submitting a request for restoring the form data
function submitRestoreRequest() {
var restoreTrigger
= getFormElement("supportForm", "restoreTrigger");
restoreTrigger.value = "restore";
var supportForm = document.getElementById("supportForm");
supportForm.submit();
}
|
Verifying that the form is restorable
The restore request, which is sent to the server with submitRestoreRequest(), uses POST because this is the HTTP method specified by any JSF form. To avoid an infinite loop, the restore request should be sent only when the current page was generated in response to a GET request. In addition, the repository must contain the saved data for the current user/form combination. Both conditions are verified within the isRestorable() function, which evaluates a client-side JavaScript expression containing the values of two JSP/JSF EL expressions that are evaluated on the server-side (see
Listing 9):
Listing 9. Verifying if the restore request can be sent
function isRestorable() {
return "${pageContext.request.method}".toUpperCase() == "GET"
&& <h:outputText value="#{viewRestorer.currentViewRestorable}"/>;
}
|
Assuming that the page was generated after a GET request and the isCurrentViewRestorable() method of the ViewRestorer bean returned true on the server side, the generated expression of the JavaScript function evaluates to true (see Listing 10):
Listing 10. JavaScript code generated after a GET request
function isRestorable() {
return "GET".toUpperCase() == "GET"
&& true;
}
|
If the restore request is sent with submitRestoreRequest()
or if the user clicks the Submit button, the server will receive a POST request and isRestorable() will return false, as you can see in Listing 11:
Listing 11. JavaScript code generated after a POST request
function isRestorable() {
return "POST".toUpperCase() == "GET"
&& true;
}
|
Understanding how restore requests are processed
This section explains the role of the immediate attribute and the use of the renderResponse() call from the restoreCurrentView() method of the sample application's ViewRestorer class.
Using the immediate attribute of the JSF components
The <h:inputHidden> component used in the previous
section has its immediate attribute set to true so that the listener method can be called very early in the JSF request processing life cycle. To be more precise, this will happen in the Apply Request Values phase. The immediate attribute can also be set to true for command buttons whose action methods should be called during the Apply Request Values phase rather than waiting until the Invoke Application phase. You can use a <h:commandButton> tag whose immediate attribute is true if you want to add a Restore button to the JSF form (see Listing 12):
Listing 12. Command button for restoring the form data
<h:form id="supportForm">
...
<h:commandButton id="restoreButton" value="Restore"
actionListener="#{viewRestorer.actionListener}"
immediate="true"/>
...
</h:form>
|
In conclusion, the valueChangeListener() and actionListener() methods of the ViewRestorer bean will be invoked during the Apply Request Values phase because the restoreTrigger and restoreButton components of the SupportForm.jsp page have the immediate attribute set to true.
Skipping some of the JSF request processing phases
The two listener methods of the ViewRestorer class call the restoreCurrentView() method of the same class, which invokes the renderResponse() method of the faces context. Therefore, the request processing jumps from the Apply Request Values phase straight to the Render Response phase, skipping the Process Validations, Update Model Values, and Invoke Application phases.
Let's analyze the implications of the renderResponse() call
from the restoreCurrentView() method of the ViewRestorer class. First of all, the application must ignore any
submitted data when the form is posted with the submitRestoreRequest() function to trigger the valueChangeListener() of the ViewRestorer.
That's why the restoreValues() method of the DataMapRepository class clears the submittedValue property of each input component. The missing values would normally cause lots of validation errors, but this doesn't happen in the sample application because the Process Validations phase is not executed for a restore request because of the renderResponse() call.
Skipping the Process Validations, Update Model Values, and Invoke Application phases with renderResponse() also means that you don't have to worry about any side effects of the restore requests. Therefore, the AutoSaveListener class, which listens for phase events after the validation phase, won't be affected by those requests, so there is no need to change that class. In addition, the application's data model won't be updated and no action method will be called after a restore request. As you can see, the same principle is applied for both restore and auto-save requests, which are handled transparently, without interfering with the application logic.
Allowing the user to control form saving and restoring
The restoreTrigger hidden component and the submitRestoreRequest() JavaScript function provide the means for
triggering the form restoring. The server-side code does the rest, generating a restored form that contains the auto-saved data. It is possible, however, to add a few more buttons so that your users can control when a form is saved or restored. In a real application, you might not add such buttons if you want to keep the user interface simple and do both saving and restoring transparently and automatically.
Nevertheless, you might want to extend the auto-saving functionality for a real application, so that your users can save/load the Web forms just like desktop applications save/load their documents. In this case, users would be able to restore a Web form, change some data, and resubmit the form to the Web server. Let's say you have a form that is used to submit monthly invoices. Some of the input — such as the invoice date, the invoice number, and maybe the invoice lines — will change from one month to another, but the entered supplier and buyer info could be reused, reducing the time needed to enter the form data.
You've already seen in the previous section how to add a Restore button. The form can also contain a Save button whose code is shown in Listing 13. Instead of specifying a server-side method through the action or actionListener attributes, the Save button uses the onclick attribute to call the submitSaveRequest() JavaScript function when the user clicks the button.
Listing 13. Command button for saving the form data.
<h:form id="supportForm">
...
<h:commandButton id="saveButton" value="Save"
onclick="return submitSaveRequest()"/>
...
</h:form>
|
Listing 14 shows the submitSaveRequest() function of the SupportForm.jsp page. This JavaScript function sends the form data to the server with the submitAllForms() function of the AutoSaveScript.js file, whose code was presented in Part 1 of the series. Then, submitSaveRequest() returns false so that the onclick expression of the Save button can return false, which means the Web browser won't submit the form to the server.
Listing 14. Submitting the current user input to be saved on the server
function submitSaveRequest() {
submitAllForms();
return false;
}
|
If the return keyword is deleted from the onclick attribute or if true is returned instead of false, the form data would be submitted twice, the first time by the submitAllForms() function, which uses Ajax, and the second time by the Web browser, which would treat the Save button as a regular Submit button. The browser's submit would lead to a page refresh and possibly to validation errors. By returning false within the onclick attribute, the Web browser doesn't submit the form data anymore and the user input is saved only by the submitAllForms() function, without refreshing the page.
The form page can also contain a check box that allows the user to enable and disable the auto-saving feature for the current page (see Listing 15). The setAutoSaving() function is called within the onclick attribute of the Auto-Save Form check box, which is created with <h:selectBooleanCheckbox> in the SupportForm.jsp file. The setAutoSaving() function of the AutoSaveScript.js file uses the setInterval() function of the JavaScript API to instruct the Web browser to call submitAllForms() every time the specified number of milliseconds elapses. If 0 is passed to the setAutoSaving() function, the auto-saving feature is disabled.
Listing 15. Check box for enabling and disabling the auto-saving feature
<h:form id="supportForm">
...
<h:selectBooleanCheckbox id="autoSaveCheckbox"
onclick="setAutoSaving(this.checked ? autoSaveInterval : 0)"/>
<h:outputLabel value="Auto-Save Form" for="autoSaveCheckbox"/>
...
</h:form>
|
The autoSaveInterval variable is set to 10000 in SupportForm.jsp.
The <body> tag of SupportForm.jsp uses the onload attribute to call the init() function (shown in Listing 16) after the Web browser loads the page. This function verifies if the form data is restorable and then asks the user to confirm if he or she wants to restore the auto-saved form. If the user clicks the OK button of the confirmation dialog, init() calls submitRestoreRequest(). Otherwise, init() calls the setAutoSaving() function if the autoSaveCheckbox is checked.
Listing 16. Restoring the form data if the user agrees
var autoSaveInterval = 10000;
function init() {
if (isRestorable())
if (confirm("Do you want to restore the auto-saved form?")) {
submitRestoreRequest();
return;
}
var autoSaveCheckbox
= getFormElement("supportForm", "autoSaveCheckbox");
if (autoSaveCheckbox.checked)
setAutoSaving(autoSaveInterval);
}
|
In this three-part series, you have done the following:
- Implemented form auto-saving in JSF-based Web applications.
- Used Ajax to save forms without refreshing the Web page.
- Managed the form data on the server side.
- Restored the data of the JSF forms.
You have learned plenty of JavaScript and JSF techniques, such as:
- Encoding and submitting the form data with JavaScript.
- Deleting
XMLHttpRequestobjects to avoid a memory leak in the Web browser. - Handling Ajax requests with the JSF framework.
- Setting browser IDs to identify anonymous users across browser sessions.
- Traversing the JSF component tree recursively.
- Using the
immediateandonclickattributes of the JSF components. - Calling the
renderResponse()andresponseComplete()methods of the faces context.
Feel free to reuse the sample code included with this series in your own Ajax/JSF applications if you need to implement form auto-saving or a similar feature.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample application for this article | wa-aj-jsf3.zip | 9KB | HTTP |
Information about download methods
Learn
- Take a look at "Auto-save JSF forms with Ajax, Part 1" for an overview of 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.
- In
"Auto-save JSF
forms with Ajax, Part 2", you see how to identify anonymous users across browser sessions, how to manage the automatically saved form data for multiple users and pages, how to choose a data repository, and how to deal with thread-safety issues.
-
Find more Ajax resources on Wikipedia.
-
Get just about everything you need know about forms in HTML 4.01 documents.
- The developerWorks Web development
zone is packed with tools and information for Web 2.0 development.
- The developerWorks Ajax resource
center contains a growing library of Ajax content as well as useful resources to get you started developing Ajax applications today.
Get products and technologies
-
JavaServer Faces Technology - Download contains the JSF reference implementation and the JSF specification.
- The
Apache MyFaces Project is another popular JSF implementation.
Discuss
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.





