Using JavaServer Faces Technology with AJAX
https://bpcatalog.dev.java.net/nonav/ajax/jsf-ajax/
Problem Description
The Java 2 Enterprise Edition includes JavaServer Faces technology, which provides a mature and extensible user interface component model. The design of this model makes it easy for application developers to create custom components by extending the standard components included with JavaServer Faces technology and to reuse these components across applications.
The example featured in this entry includes a JavaServer Faces text field component that takes the name of a city. When the user begins entering characters in the field, the application uses AJAX to perform auto completion on the data by matching the user's input to a list of cities stored on the server. In two of the versions of this example, the text field component is a custom JavaServer Faces component that provides the AJAX support. This component shields the page author from the complexities of AJAX by rendering all the HTML and JavaScript code that is required to incorporate AJAX capabilities into an application.
A third version of this example adds AJAX support to a standard JavaServer Faces component through a servlet that interacts with the JavaServer Faces component and other JavaServer Faces server-side objects to generate the appropriate auto completion data.
This entry focuses primarily on the question, "How can I incorporate AJAX functionality into a JavaServer Faces application?"
Among the other questions that this entry considers are the following:
- Should AJAX request handling be done in a separate servlet controller or should it be handled by the JavaServer Faces technology life cycle?
- How can existing components be AJAX-enabled without rewriting them?
- Should JavaScript code be embedded in Java code or externalized in a separate script file?
- How does the programming model differ between component writer and page developer?
- How should I organize the project structure in my workspace?
- How should I package the JavaServer Faces objects and JavaScript artifacts into a WAR file or EAR file?
Solution
Developers who want to include AJAX support in JavaServer Faces applications have more than one strategy to choose from. Which strategy they choose depends on the needs of their situation. Developers might ask themselves the following questions to help them decide which strategy to select:
- Is this a new application or is the development team adding AJAX support to an existing JavaServer Faces application?
- Is the page author capable of adding the JavaScript and AJAX code to the page, or should it be handled by a server-side component so that the page author can simply add the component tag to the page?
- Must the AJAX code be reusable?
- Must the AJAX code be customizable?
- Must the AJAX code be usable outside of the JavaServer Faces technology runtime?
- Is the application developer capable of doing the extra programming required to synchronize data with the JavaServer Faces objects if the development team decides to de-couple the AJAX code from the JavaServer Faces runtime?
This entry discusses three strategies for incorporating AJAX support into a JavaServer Faces application. The first two strategies involve creating a custom component to generate the necessary JavaScript and to make AJAX interactions with another server-side component. In Strategy 1, the JavaServer Faces technology life cycle and the custom component handle the AJAX requests. In Strategy 2, the custom component communicates with a special servlet, which handles all the AJAX requests. Strategy 3 does not require any custom components; all the additional AJAX support is performed by a servlet provided by the developer.
Strategy 1 and Strategy 2 require knowledge of the JavaServer Faces technolgy life cycle to implement them. Strategy 3 requires knowledge of the Java servlet API.
The following sections explain the three strategies and provide more detail on their advantages and disadvantages. After reading about these strategies and answering the list of questions above, developers should be able to select the strategy that is appropriate for their particular situation. The Comparison of Strategies section includes a chart that summarizes the characteristics of each strategy, which makes it even easier to make the decision.
Strategy 1: A Custom JavaServer Faces Component Renders Client-side AJAX JavaScript and Processes AJAX Requests
Figure 1 illustrates how this strategy works. The numbered steps following the figure explain what is happening in the figure.
Figure 1: Architecture of a JavaServer Faces Component that Renders Client-Side AJAX JavaScript and Processes AJAX Requests
The following steps explain the architecture illustrated in Figure 1:
- The page called Enter Address Page contains an HTML
script
element rendered by the renderer, AutoCompleteTextRenderer
.
- A call is made to the URL,
faces/autocomplete-script
, which is mapped to a FacesServlet
instance. This instance processes the RenderPhaseListener
instance, which recognizes the URI and returns the component.js
page containing the client-side JavaScript code necessary for the AJAX interactions. After the component.js
page is returned, the RenderPhaseListener
instance stops taking part in the JavaServer Faces technology life cycle processing for now.
- When the user starts typing in the City text field, an
onkeypress
event occurs. The JavaScript function mapped to this event creates an XMLHttpRequest
object and configures it with the URL to the FacesServlet
instance. This URL is faces/autocomplete&id=San
. The id=San
part of the URL is a URL parameter in which San
is the user input that is matched against the list of cities. The XMLHttpRequest
object makes a call to the FacesServlet
instance. HTML page events continue to be processed by the JSP page.
- The
FacesServlet
instance processes the RenderPhaseListener
instance that recognizes the URL faces/autocomplete
and looks up the AutoCompleteTextField
component, which provides the completion logic.
- The
RenderPhaseListener
instance generates an XML document containing the potential completion items and returns it to the XMLHttpRequest
object, which then calls the XMLHttpRequest
callback function.
- The
XMLHttpRequest
callback function updates the HTML DOM based on the contents of the XML document that was returned.
- After entering the form data, the user clicks the Update button and the form is posted using an HTTP POST to the
FacesServlet
instance, which updates SessionBean
with the address information entered by the user.
The remainder of this section provides further details on how this strategy works.
In Figure 1, the page called Enter Address Page is a JavaServer Faces page. This page contains a tag that represents the AutocompleteTextField
component, which is a JavaServer Faces component. This component renders two things: an HTML text field, into which the user enters the name of a city, and the necessary client-side JavaScript AJAX code that fetches possible city names from a server-side managed bean. This managed bean contains a method that implements the algorithm for completing city names.
To add the AJAX-enabled component to the application, the page author need only include the following tag in the page:
<ajaxTags:completionField size="40" id="cityField"
completionMethod="#{AutoCompleteTextfield.completeCity}"
value="#{SessionBean.city}" required="true"
/>
Code Example 1: Declaring an AJAX-enabled JavaServer Faces Component in a JavaServer Faces page
The
AutoCompleteTextFieldTag
tag handler implements the
ajaxTags:completionField
tag, which is rendered to HTML by the renderer,
AutoCompleteTextFieldRenderer
. The first time the component is encountered in the page,
AutoCompleteTextFieldRenderer
renders a reference to external JavaScript code, as shown in this HTML fragment:
<script type="text/javascript" src="faces/autocomplete-script">
Code Example 2: Rendered HTML that references JavaScript to be returned by RenderPhaseListener
When the reference to the external script file is reached, the browser makes a request to the URL, faces/autocomplete-script
. The request first goes to the FacesServlet
instance and is then received by the RenderPhaseListener
instance, which returns the client-side JavaScript relevant to the AJAX interactions of the component. In the case of this example, the component.js
file is included with the component and returned using the class loader. Bundling the relevant JavaScript code with the component is a good practice because it keeps the script and component close to each other but keeps the JavaScript out of the component code. The AJAX client-side JavaScript contains the
XMLHttpRequest
callback method, which includes the event mappings to the form elements rendered by AutoCompleteTextFieldRenderer
.
When the target onkeypress
event is encountered in the City form field, the client-side XMLHttpRequest
object makes a HTTP GET call with the partial text input from the field to the FacesServlet
instance, which in turn calls the RenderPhaseListener
instance. The RenderPhaseListener
instance looks up the AutoCompleteTextField
component and gets a set of city names that match partial text input. For example, if the user entered "San" into the field, the component would fetch the set of city names that start with the string "San".
At this point, the RenderPhaseListener
instance can access the view state associated with the page if it is needed to process the interaction. When processing AJAX requests with JavaServer Faces components, a developer can treat each AJAX request as a new request by using simple HTTP GET calls. In this case, every request is a new Faces request. Or the developer can use POST calls, which give access to the view state associated with the JavaServer Faces page. Strategy 1A provides further details on using POST calls to access view state. When using AJAX GET requests with JavaServer Faces technology, a developer has access to managed bean components and has the ability to evaluate expressions but does not have access to the view state.
In the case of this example, each auto complete AJAX request is a GET request and is therefore a new Faces request. The RenderPhaseListener
instance renders an XML response based on the results obtained from the AutoCompleteTextField
component.
The server-side AJAX request processing is provided by a RenderPhaseListener
instance. From the perspective of a server-side developer, the requests are a little different from other AJAX requests in that they usually do not go through all the phases of the JavaServer Faces technology life cycle. The following code shows how the RenderPhaseListener
instance handles an AJAX request:
private void handleAjaxRequest(PhaseEvent event) {
FacesContext context = event.getFacesContext();
HttpServletResponse response =
(HttpServletResponse)context.getExternalContext().getResponse();
..
if ("complete".equals(action)) {
String method = request.getParameter("method");
try {
//get completion items
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
//Code to write XML response goes here...
event.getFacesContext().responseComplete();
return;
} catch (EvaluationException ee) {
ee.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Code Example 3: PhaseListener code to handle AJAX requests
One thing the developer needs to remember is that the content type must be set to "text/xml"
and the Cache-Control header also needs to be set to "no-cache
" as shown in the following code snippet, which is taken from Code Example 3.
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
The call to event.getFacesContext().responseComplete()
following the creation of the XML response causes the rest of the JavaServer Faces life cycle to be skipped.
The response is returned to the XMLHttpRequest
callback method, which updates the HTML DOM with the list of potential city names.
The end user might then select a relevant city name from the list returned from the AutoCompleteTextField
component and click on the Update button, which causes the form to be submitted to the FacesServlet
instance, after which the SessionBean
object is updated.
This strategy makes it easy for page authors to add AJAX-enabled components to a JavaServer Faces page. As long as the component is added to the application, all they need to do is include the corresponding component tag in the page.
The component developer can make the component more customizable by allowing external JavaScript files to override the embedded file as well as allowing the use of external style sheets. One way to do this is to include JavaScript and CSS properties on the component that the user can override. For example, the AutocompletionTextField
component can have an ondisplay
property that lets users of the component specify JavaScript code that is executed when an item is selected from the popup. Similarly, a selectedItemStyle
property could let the user specify a CSS or a CSS style class to be applied to selected items in the popup.
This approach is used by the AJAX Progress Bar for JavaServer Faces Technology. As with Strategy 1, FacesServlet
is used to orchestrate all the processing of AJAX requests and all the serving of script files. The main difference between Strategy 1 and Strategy 1A is the use of POST to submit the actual view state back to the server. Doing this requires the AJAX-enabled components to abort normal page processing to prevent rendering of the entire JSP page for AJAX requests. This is done the same way as in Strategy 1, using the responseComplete
method on the FacesContext
instance. Another difference in the architecture of the progress bar example is that the PhaseListener
instance is responsible only for rendering the script, whereas ProgressBarRenderer
writes out the response XML that is handled by the AJAX JavaScript code.
Another complexity resulting from the polling nature of the progress bar is the need for a closure to the function passed to the setTimeout
JavaScript method. The progress bar uses the technique described by Richard Cornford at http://jibbering.com/faq/faq_notes/closures.html. This is a very handy technique when coding AJAX-enabled components.
Performance should be considered when determining how much view state is associated with each request relative to the volume of AJAX requests being processed when using this approach. While this approach is more tightly coupled with the JavaServer Faces life cycle and therefore provides ready access to Faces objects, this tight coupling could result in a performance degradation compared to the strategy of using a separate AJAX controller, which would not have a tight coupling to the view state.
Strategy 2: A Custom JavaServer Faces Component with Separate AJAX Controller
In this strategy the JavaScript rendered by the JavaServer Faces component communicates with a separate servlet. All asynchronous requests will therefore be handled outside of the JavaServer Faces technology life cycle processing. However, the separate servlet can look up the FacesContext
instance and evaluate value binding and method binding expressions so that it can do such things as find managed beans and make calls to them.
Figure 2 illustrates this strategy. The numbered steps following the figure explain what is happening in the figure.
Figure 2: Architecture of a JavaServer Faces Component with separate AJAX Controller
The following list describes the interactions shown in Figure 2:
- The page, called Enter Address Page, contains an HTML script element that is rendered by the renderer,
AutoCompleteTextRenderer
.
- A call is made to the URL,
faces/autocomplete-script
, which is mapped to the FacesServlet
instance. This instance processes the RenderPhaseListener
instance, which recognizes the URL and returns the component.js
page containing the client-side JavaScript code necessary for the AJAX interactions. After the component.js
page is returned, the RenderPhaseListener
instance stops contributing to the JavaServer Faces technology life cycle processing for now.
- When the user enters text into the City text field, an
onkeypress
event is generated. The JavaScript function mapped to this event creates an XMLHttpRequest
object and configures it with the URL to the AjaxControllerServlet
instance. This URL is autocomplete&id=San
. The id=San
part of the URL is a URL parameter in which San
is the user input that is matched against the list of cities.
- The
XMLHttpRequest
object makes the call to the AjaxControllerServlet
instance, and HTML page events continue to be processed by the page. The AjaxControllerServlet
instance looks up the AutoCompleteTextField
component and gets a list of potential completion items.
- The
AjaxControllerServlet
instance generates an XML document containing the potential completion items and returns it to the XMLHttpRequest
object. The XMLHttpRequest
object then calls the XMLHttpRequest
callback function.
- The
XMLHttpRequest
callback function updates the HTML DOM with the list of city names, which are contained in the returned XML document. At this point, users can select a city name, and when they do, the form element's value is set with the selected value.
- After entering the form data, the user clicks the Update button and the form is POSTed to the
FacesServlet
instance, which updates the SessionBean
object with the address information entered by the user.
This strategy provides a clean separation of the AJAX interaction logic and the traffic that is usually handled by the FacesServlet
instance. Each AJAX request is a new request that the JavaServer Faces technology life cycle does not see, meaning that AjaxControllerServlet
does not have access to the JavaServer Faces page view state. The AJAX servlet can communicate with the managed beans and evaluate expressions by looking up the FacesContext
instance and calling the corresponding code.
The strategy is similar to Strategy 1 in that all "plumbing" is generated by the JavaServer Faces component itself. In this strategy, the JavaServer Faces component is depending on a separate dedicated servlet to handle the JavaScript events it is adding to the rendered HTML. Updates to the JavaServer Faces model data occur outside the scope of the JavaServer Faces technology life cycle request/response processing, and so care should be taken that model data is consistent.
Strategy 3: Retrofitting an Existing JavaServer Faces Application
In this strategy, no custom JavaServer Faces components are used. As in Strategy 2, a separate dedicated AJAX servlet is used, and custom JavaScript code is added to the JSP page for the purpose of communicating with the servlet. Developers are responsible for all the "plumbing". This means that they must provide the code that handles JavaScript events associated with the JavaServer Faces components, makes asynchronous calls, and updates the HTML document when responses arrive.
Figure 3 illustrates this strategy. The numbered steps following the figure explain what is happening in the figure.
Figure 3: Retrofitting an Existing JavaServer Faces Application
The following steps explain the architecture of the JavaServer Faces application shown in Figure 3:
- The page, called Enter Address Page, contains the client-side JavaScript code necessary for the AJAX interactions.
- When the user types in the City text field, an
onkeypress
event is generated. The JavaScript function mapped to this event creates an XMLHttpRequest
object and configures it as a URL to the AjaxControllerServlet
instance. This URL is autocomplete&id=San
. The id=San
part of the URL is a URL parameter in which San
is the user input that is matched against the list of cities. After the XMLHttpRequest
object makes the call to AjaxControllerServlet
, the page continues to process HTML page events.
AjaxControllerServlet
looks up the CitiesBean
managed bean and gets a list of potential completion items.
- The
AjaxControllerServlet
instance generates an XML document containing the potential completion items and returns it to the XMLHttpRequest
object. The XMLHttpRequest
object calls the XMLHttpRequest
callback function.
- The
XMLHttpRequest
callback function updates the HTML DOM based on the contents of the XML document that was returned.
- After entering the form data, the user clicks the Update button and the form is POSTed to the
FacesServlet
instance, which updates the SessionBean
object with the respective address information.
Figure 3 is very similar to Figure 2, which illustrates Strategy 2, with one exception: In Strategy 2, the servlet is provided with the JavaServer Faces component and handles "generic" traffic. In Strategy 3, the JavaScript and the servlet are provided by the application developer. Notice that the servlet talks about the application-specific list of cities, whereas in Strategy 2 the servlet handles generic items. This is because strategy 2 is designed to be reusable in that the solution can be utilitzed to handle various auto completion use cases, such as completing city names, state names, or people names, whereas Strategy 3 is bound to the specific use case of completing city names.
This strategy has the advantage of not relying on any special AJAX-enabled JavaServer Faces components, and therefore, any existing JavaServer Faces application can be retrofitted with custom JavaScript and a special servlet to handle AJAX behavior. However, like Strategy 2, the use of a separate servlet makes calling JavaServer Faces methods more difficult. Furthermore, it is impossible to get access to the view state of the JavaServer Faces page.
This strategy allows for more customization of the JavaScript code that provides the AJAX interaction, but it is also more difficult to maintain and requires that the page author be familiar with AJAX interactions and JavaScript. The page author is responsible for matching the events generated by the HTML elements to the JavaScript functions. Doing this might be difficult for the average page author because the HTML elements are generated by a JavaServer Faces component renderer, and therefore the page author must be somewhat familiar with the server-side renderer code. In addition, the page author is also responsible for making sure that the AJAX updates to the HTML DOM match the HTML elements rendered by a JavaServer Faces component.
The application developer is responsible for the server-side processing of AJAX using a web component, such as a servlet. Updates to the JavaServer Faces model data occur outside the scope of the JavaServer Faces technology life cycle request/response processing, so care should be taken to ensure that model data updated by a server-side component is consistent with that managed by the JavaServer Faces technology runtime.
Comparison of Strategies
Below is a table that contains a list of trade-offs for each strategy.
|
Strategy 1: Custom JavaServer Faces Component that Renders Client-side AJAX Script and Processes AJAX Requests |
Strategy 2: Custom JavaServer Faces Component for separate AJAX Controller |
Strategy 3: No Custom JavaServer Faces Component: Developer-Managed JavaScript and AJAX Servlet |
Where do I put the control logic? |
In a PhaseListener instance. |
In a servlet or servlet filter. |
In a servlet or servlet filter. |
Does the page developer need to provide client-side AJAX JavaScript and map to form elements? |
No. The JavaServer Faces component renderer will render the necessary JavaScript. |
Yes. Additionally, care must be taken to ensure the form elements mapped to by the script match those rendered by the target JavaServer Faces component. |
Is the solution re-usable? |
Yes. However, the JavaScript included with the JavaServer Faces component must be written such that it can process requests from multiple components. There is a limited number of XMLHttpRequest objects that can be used in a page, so care should be taken with the JavaScript in this case also. |
No. Each JavaServer Faces page would require its own JavaScript. The JavaScript can be shared in a separate file, but portions such as the XMLHttpRequest callback function are generally bound to a specific use case. |
Is the solution customizable? |
Yes, provided the component provides customization hooks |
Yes, but that is the problem. Each JavaServer Faces page contains a customized solution. |
Does the component have access to the view state of a JavaServer Faces page? |
Yes, but there are subtle differences in how the view state can be made accessible depending on the version of the JavaServer Faces technology runtime you are using. |
No |
No |
Can the AJAX control logic for a JavaServer Faces component be overridden? |
Yes. The control logic is in a PhaseListener instance, which would need to be replaced. This would require repacking the component. |
Yes. The controller is a servlet that can be modified or replaced. |
Can the AJAX control logic access managed beans and evaluate expressions? |
Yes. Each request is a JavaServer Faces request that has ready access to managed beans and expressions. |
Yes. The AJAX control logic code can access managed beans and evaluate expressions by looking up the FacesContext instance. This requires a few lines of code. |
Can I leverage existing JavaScript libraries? |
Yes. Either the JavaScript must be embedded with the JavaServer Faces component or the component must be capable of using external JavaScript. |
Yes. |
Can I leverage the AJAX control logic outside a JavaServer Faces technology runtime? |
No. |
No. |
Yes. Servlets and servlet filters do not require a JavaServer Faces technology runtime; although, if portions of the AJAX control logic are bound to managed beans or are evaluating expressions in the JavaServer Faces technology runtime, this would not be possible. |
Rendering JavaScript Exactly Once
The JavaScript should be placed in a separate file as mentioned above, to ensure that the JavaScript contents can be cached by the browser.
While the JavaServer Faces component can be used multiple times in a page, the JavaScript inclusion reference should be emitted exactly once -- before the first usage. A good way to do this is to modify the renderer that emits the script
tag so that it stores a flag for itself in the request map. This flag records whether the renderer has written the script out for the current page yet. The following code snippet demonstrates how to add code to the renderer that will store the flag and check its value to determine if the script
tag has been rendered:
...
private void renderScriptOnce(ResponseWriter writer, UIComponent component, FacesContext context)
throws IOException {
// Store attribute in request map when we've rendered the script such
// that we only do this once per page
Map requestMap = context.getExternalContext().getRequestMap();
Boolean scriptRendered = (Boolean)requestMap.get(RENDERED_SCRIPT_KEY);
if (scriptRendered == Boolean.TRUE) {
return;
}
// Render script and CSS here
...
}
private static final String RENDERED_SCRIPT_KEY = "bpcatalog-ajax-script-rendered";
Project Structure and Packaging
One issue that developers must deal with is how to organize their workspace. What should the project conventions be? Generally, this is covered by existing project conventions for JavaServer Faces technology modules and applications. For example, the faces-config.xml
file and the TLD files for JavaServer Faces technology are usually kept in the src/conf
directory.
But where should you keep the JavaScript files? Should they be kept with the source code files? In most cases, we recommend that the JavaScript files be kept in the src/resources
directory. In some cases, you might be using a pre-existing library of JavaScript files, which might be reusable even without the JavaServer Faces components, such as in the case of making a JavaServer Faces component that uses some of those existing JavaScript files. In this instance, the JavaScript library should be viewed as a separate project and should be used only by the JavaServer Faces application.
Another issue to consider is how to package these JavaServer Faces components and JavaScript artifacts into a WAR file or EAR file. The packaging is mostly the same as for other JavaServer Faces modules and applications, but you need to consider where to put the AJAX artifacts, which include the javascript files. Generally, it is recommended that you place the AJAX artifacts with the compiled classes so that the JavaServer Faces components can load them by calling getClass().getResourceAsStream()
.
In order to use the components within an IDE, there are additional packaging considerations, such as how to make properties visible in IDE property sheets. These considerations differ from IDE to IDE (until this hopefully gets standardized with JSR 273). For a description of how to package your JavaServer Faces component for Sun Java Studio Creator, read http://developers.sun.com/prodtech/javatools/jscreator/reference/techart/jsfcomplibrary.html .
Helpful Hints for Developing AJAX components with JavaServer Faces Technology
- Understand the JavaServer Faces component model. The J2EE 1.4 Tutorial on Creating JavaServer Faces Components describes the necessary artifacts needed to create custom JavaServer Faces components.
- Get a good JavaScript book. JavaScript: The Definitive Guide, 4th Edition By David Flanagan is quite adequate.
- Get familiar with Venkman, the JavaScript debugger in the Mozilla suite.
- Get familiar with the Internet Explorer Script Debugger.
- Visit QuirksMode.org, a great JavaScript/CSS reference site.
- It's generally a good idea to put all your JavaScript code into one file that is referred to using <script> tags. During iterative development, it's a good idea to place this file directly in your server root so you can edit it in place. Then modify your code to refer to it at this location, just during development. When you get everything working, you can switch to using the
FacesServlet
and PhaseListener
style to keep things self contained.
References
For more details on this solution see the JavaServer Faces Technology Auto-Completion Component with AJAX: Design Details and AJAX Progress Bar for JavaServer Faces Technology: Design Details.