Skip to main content

Auto-save JSF forms with Ajax: Part 3

Restore the user input of JSF forms

Andrei Cioroianu, Senior Java Developer and Consultant, Devsphere
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.

Summary:  In the first article of this series, author and Java™ developer Andrei Cioroianu showed how to submit the user input of a Web form with Asynchronous JavaScript + XML (Ajax) and how to handle the Ajax requests with JavaServer Faces (JSF). In the second article of the series, Andrei discussed data management on the server side and presented a data repository for keeping the auto-saved form data. In this final installment of the three-part series, you'll find out how to restore the data of a JSF form, which is trickier than you might think. You will learn interesting JSF techniques, such as using the immediate and onclick attributes of JSF components, skipping some of the phases of the JSF request processing life cycle, and using hidden form elements to trigger JSF listeners. You will also learn how to include JSP/JSF expressions within the JavaScript code, how to use JavaScript with the HTML form elements generated by the renderers of the JSF components, and how to implement a servlet context listener for serializing and deserializing application beans.

View more content in this series

Date:  09 Oct 2007
Level:  Intermediate
Activity:  2235 views

Introduction to the series

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.

The developerWorks Ajax resource center

Check out the Ajax Resource Center, your one-stop shop for information about the Ajax programming model, including articles and tutorials, discussion forums, blogs, wikis, events, and news. If it's happening, it's covered here.

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();
    }
    ...
}

Implementing JSF listeners

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>

Sending restore requests

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 renderResponse() call discussed in the previous section has nothing to do with the renderResponse() call analyzed here. The JSF framework makes the former call during the Restore View phase. The latter call is made in the sample application's code during the Apply Request Values phase of the JSF request processing life cycle.

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.

Adding a Save button

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.

Adding an Auto-Save check box

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.

Client-side initialization

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);
}

Conclusion

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 XMLHttpRequest objects 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 immediate and onclick attributes of the JSF components.
  • Calling the renderResponse() and responseComplete() 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.



Download

DescriptionNameSizeDownload method
Sample application for this articlewa-aj-jsf3.zip9KB HTTP

Information about download methods


Resources

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

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.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Java technology
ArticleID=260235
ArticleTitle=Auto-save JSF forms with Ajax: Part 3
publish-date=10092007
author1-email=andcio@gmail.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers