Using PhaseListener Approach for Java Server Faces Technology with AJAX

Mark Basler
Status: In Early Access

Problem Description

The Java Enterprise Edition 5 platform includes JavaServer Faces (JSF) 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 JSF and to reuse these components across applications. There are many strategies for using AJAX with JSF.  This entry focuses primarily on the how to use the PhaseListener approach  to incorporate AJAX functionality into a JSF application.  For more information about the different ways to access static & dynamic resources, see "Accessing Static and Dynamic Resources".  For alternative and related solutions see "Using JSF with AJAX".

Solution

Developers who want to include AJAX support as part of their JSF components have more than one strategy to choose from.  One way to approach this problem is to use a PhaseListener to intercept and fulfill the custom component's AJAX requests and optionally serve the component's resources.

We will discuss the strategy of using a PhaseListener for incorporating AJAX support into a JSF application.  This involves creating a custom component to generate the necessary Javascript  to execute the AJAX interactions on the client-side, with another server-side component to serve the AJAX request in the form of a PhaseListener.

This approach can be used in the following cases:

This approach also has its problems, which are as follows::

For these reasons our current recommend approach is to use a third party library which is described in detail in the "Access Static and Dynamic Resource" entry.  We are still researching evolving technologies and will update these entries when our recommendations change.

Flow of AJAX Request


Figure 1.  The steps taken when submitting an AJAX request


1)  The mouseover event handler triggers the Javascript function that populates and sends the XMLHttpRequest.  The URL contains the context root (bp-jsf-example), the FacesServlet Mapping (faces), the name space sensitive flag to tell the CompBPhaseListener to serve the request (bpui_compb_action), the managed bean name that is registered in faces-config.xml (CompBBean), the managed bean's method to call (processMethod) and the populated query string associated with the request.  For example:

    /bp-jsf-example/faces/bpui_compb_action/CompBBean/processMethod?itemId=test1A

2) The CompBPhaseListener is invoked on the request, since it is registered in the faces-config.xml file and searchs the root ID (the portion of the URL after the "/faces" servlet mapping) for the flag "bpui_compb_action".  If the flag is found, then the root ID is parsed into its manage bean name and method name and altered to be in the form of a javax.el.MethodExpression and then the method of the managed bean is invoked.  For example:

    #{CompBBean.processMethod}

3) Since the managed bean has a managed property set in faces-config.xml (itemId), its value is automatically set in the manage beans instance from the request.  The manage bean then retrieves the HttpServletResponse from the FacesContext and constructs an appropriate response for the itemId that was sent with the request.

4) The XML response is sent back to the browser and the callback function is invoked.

5) The response is parsed, the popup values are populated and the popup is displayed.


Custom AJAX Component Creation

To start designing custom components for use in your application, you should be familiar with the "Creating Custom UI Components" section of the JavaEE 5 tutorial. To take the next step and design custom components for reuse by others, there are a few main points that need to be considered.  A brief summary is as follows:

When creating your custom JSF 1.2 component, the set of objects required are a tag library definition file, a JSF configuration file (usually called faces-config.xml), a tag handler class and a renderer class.  A custom component class is commonly also developed, but they aren't necessary in all cases.  The following sections use the bp-jsf-example sample application's "CompB" custom component as a reference for the coding examples.

Server Side Code:

JSF Custom Renderer (com.sun.javaee.blueprints.components.ui.example.CompBRenderer)

When rendering common artifacts, one way to keep track of whether they have been rendered or not is by putting a flag in the Request Map.  The renderer code will check to see if the flag already exists, if it doesn't it sets the flag and renders the common artifacts.  If it is already set, then the artifacts aren't re-rendered.  Notice that this component expects that the FacesServlet is mapped to the "/faces/*"  URI.  If this simple component was being distributed for others to use, this dependency should be documented in the component's user documentation..

Map requestMap=context.getExternalContext().getRequestMap();
Boolean scriptRendered=(Boolean)requestMap.get(RENDERED_SCRIPT_KEY);

// Check to see if resource already rendered
if (scriptRendered != null && scriptRendered.equals(Boolean.TRUE)) {
return;
}

// put flag in requestMap to indicate already rendered
requestMap.put(RENDERED_SCRIPT_KEY, Boolean.TRUE);

String contextRoot=context.getExternalContext().getRequestContextPath();

// Render markup tag for the javascript file
writer.startElement("script", component);
writer.writeAttribute("type", "text/javascript", null);
writer.writeAttribute("src", contextRoot + "/faces/jsf-example/compB/compB.js", null);
writer.endElement("script");
writer.write("\n");

Since our component is a Popup that shows detailed information, we need to render some markup that actually constructs the division that is displayed. There are multiple ways of doing this, one is to use a template file, read it in and token replace the values that are necessary to maintain the name space conventions.  Here, the template text's "%%%ID%%%" token is replaced with the component's ID to keep multiple instances of the component from clashing.  The component developer can also code the template directly in the renderer by using the ResponseWriter's startElement and endElement methods.  If your component was going to be tooled (for visual use in an IDE) this may be the best way to go, because each startElement call is associated with a component.  This enables the tool to keep better track of the markup that belongs to the individual components.

	<div class="bpui_compB_popTop">
<div class="bpui_compB_cornerTL"><div class="bpui_compB_cornerTR">
<center><font color="white"><b><span id="%%%ID%%%_title">title</span></b></font></center>
</div></div>
</div>
<div class="bpui_compB_popMid">
<table border="0" style="width: 100%">
<tr>
<td>
<table border="0" bgcolor="#ffffff" cellpadding="5" cellspacing="5">
<tr>
<td><span id="%%%ID%%%_message">Value</span></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<div class="bpui_compB_popBot">
<div class="bpui_compB_cornerBL"><div class="bpui_compB_cornerBR">
</div></div>
</div>


JSF Custom PhaseListener com.sun.javaee.blueprints.components.ui.example.CompBPhaseListener)
The PhaseListener in our example fulfills different types of requests.  It serves static resources if the "APP_KEY" is present in the Root ID and it has a suffix that matches the types that this PhaseListener was designed to serve.  The PhaseListener also forwards an AJAX request that contains the appropriate "ACTION_KEY" to the Managed Bean's method designated in the Root ID.

public void afterPhase(PhaseEvent event) {
FacesContext context=event.getFacesContext();
String rootId=context.getViewRoot().getViewId();

int iPos=rootId.indexOf(APP_KEY);
int iPosx=rootId.indexOf(ACTION_KEY);

// see what suffix is used for mapping to content type
if (rootId.endsWith(SCRIPT_SUFFIX) && iPos > -1) {
handleResourceRequest(event, PATH_PREFIX + rootId, "text/javascript");
} else if (rootId.endsWith(CSS_SUFFIX) && iPos > -1) {
handleResourceRequest(event, PATH_PREFIX + rootId, "text/css");
} else if (rootId.endsWith(GIF_SUFFIX) && iPos > -1) {
handleResourceRequest(event, PATH_PREFIX + rootId, "image/gif");
} else if (rootId.endsWith(JPG_SUFFIX) && iPos > -1) {
handleResourceRequest(event, PATH_PREFIX + rootId, "image/jpeg");
} else if (rootId.endsWith(PNG_SUFFIX) && iPos > -1) {
handleResourceRequest(event, PATH_PREFIX + rootId, "image/x-png");
} else if (iPosx > -1) {
// action to invoke through a deferred method expression
String methodx="#{" + rootId.substring(iPos + ACTION_KEY.length() + 1) + "}";
methodx=methodx.replace('/','.');

try {
Class[] argTypes = { PhaseEvent.class};
// create method expression
MethodExpression mex=context.getApplication().getExpressionFactory().createMethodExpression(context.getELContext(),
methodx, null, argTypes);
Object[] args = { event };
// invoke method of managed bean
mex.invoke(context.getELContext(), args);
} catch (Exception e) {
// Just log exception
e.printStackTrace();
}
}
}


JSF Managed Bean com.sun.javaee.blueprints.components.ui.example.CompBBean)
In the managed bean the HttpServletReponse is extracted from the PhaseEvent and the XML response is constructed and returned.  Since our example expects XML to be returned the appropriated context type is set to "text/xml".  We also set the status code because our AJAX callback function is also looking for a response status of "200" before it shows the detailed data.  Notice that we are setting response headers to indicate that this request/response set shouldn't be cached.  If these header values aren't set then some browsers will cache the result and on subsequent requests that has the same URL the browser will just automatically provide the same response, many times without even making a new query.  This can be beneficial in some cases, but in the case of a polling AJAX component, caching would be undesirable behavior.  Also note that the responseComplete method of the FacesContext is called to signify that the rest of the JSF life cycle should be skipped.

	public void processMethod(PhaseEvent event) {
HttpServletResponse response=null;
try {
FacesContext context=event.getFacesContext();
response=(HttpServletResponse)context.getExternalContext().getResponse();
StringBuffer sb=new StringBuffer();
response.setContentType("text/xml;charset=UTF-8");
response.setStatus(200);
// need to set no cache or IE will not make future requests when same URL used.
response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);
sb.append("<response>");
// custom response code goes here
...
sb.append("</response>");
response.getWriter().write(sb.toString());
} catch (IOException iox) {
iox.printStackTrace();
} finally {
try {
response.getWriter().flush();
} catch (Exception ee) {}
// set response complete so JSF lifecycle is not continued
event.getFacesContext().responseComplete();
}
}


Component's faces-config.xml File
The artifacts that are registered in the faces-config.xml file that pertain to the "CompB" component are listed below.  There is the custom renderer that uses an standard component family, a PhaseListener that fulfills static requests & forwards the dynamic requests and the managed bean that creates the response to the AJAX request.  Notice that the manged bean has a managed property that is preset after each request through the properties mutator method.  If the request doesn't contain the itemId parameter then the itemId is set to null.
	<!-- register the custom renderer for the standard component family -->
<render-kit>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>CompB</renderer-type>
<renderer-class>com.sun.javaee.blueprints.components.ui.example.CompBRenderer</renderer-class>
</renderer>
</render-kit>

<!-- register components custom phaselistener -->
<lifecycle>
<phase-listener>com.sun.javaee.blueprints.components.ui.example.CompBPhaseListener</phase-listener>
</lifecycle>

<!-- register the custom managed bean, with a managed property (the requests itemId parameter -->
<managed-bean>
<managed-bean-name>CompBBean</managed-bean-name>
<managed-bean-class>com.sun.javaee.blueprints.components.ui.example.CompBBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>itemId</property-name>
<value>#{param.itemId}</value>
</managed-property>
</managed-bean>




Client Side Code:


The CompB custom component's tag would reside in your JSP page and is detailed below:

	<ui:compB id="pop1" url="faces/bpui_compb_action/CompBBean/processMethod?itemId="/>

        <a href="#" onmouseover="bpui.compB.showPopup('pop1', event, 'test1A')" 
onmouseout="bpui.compB.hidePopup('pop1')" style="cursor: pointer"><b>Mouse over link to see popup (test1A)</b></a><br/>
<small><i>(With a JSF Managed Bean fulfilling the AJAX Request)</i></small><br/><br/>

Once our populated custom tag goes through the FacesServlet and is rendered, the resultant HTML markup and Javascript code should look as follows:
	<div id="pop1" class="bpui_compB_popup">            
<div class="bpui_compB_popTop">
<div class="bpui_compB_cornerTL"><div class="bpui_compB_cornerTR">
<center><font color="white"><b><span id="pop1_title">title</span></b></font></center>
</div></div>
</div>
<div class="bpui_compB_popMid">
<table border="0" style="width: 100%">
<tr>
<td>
<table border="0" bgcolor="#ffffff" cellpadding="5" cellspacing="5">
<tr>
<td><span id="pop1_message">Value</span></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<div class="bpui_compB_popBot">
<div class="bpui_compB_cornerBL"><div class="bpui_compB_cornerBR">
</div></div>
</div>
</div>

<script type="text/javascript">
bpui.compB['pop1']=new bpui.compB.createPopup('pop1', '/bp-jsf-example/faces/bpui_compb_action/CompBBean/processMethod?itemId=');
</script>
Note the Javascript code that creates a new bpui.compB.createPopup object and stores the result in the bpui.compB name space under the "pop1" property.  This object is a Javascript closure and is retrieved when the popup in trigger through the bpui.compB.showPopup function described below.


Javascript functions:
The following Javascript function is called when the CompB component is rendered and it initiates the popup object closure that maintains the AJAX callback function with the component's identifier and URL.  The callback function checks to ensure a valid response and then parses the return XML data, populates and displays the popup.
	// create a closure to maintain the component's id and AJAX url with callback function
bpui.compB.createPopup=function(componentId, urlx) {
this.componentId=componentId;
this.urlx=urlx;

this.ajaxReturnFunction=function() {
// make sure response is ready
if (bpui.compB.req.readyState == 4) {
// make sure it is a valid response
if (bpui.compB.req.status == 200) {
// populate the popup with the info from the response
var resultx=bpui.compB.req.responseXML.getElementsByTagName("response")[0];
document.getElementById(componentId + "_title").innerHTML=resultx.getElementsByTagName("title")[0].childNodes[0].nodeValue;
document.getElementById(componentId + "_message").innerHTML=resultx.getElementsByTagName("message")[0].childNodes[0].nodeValue;;
// show popup with the newly populated information
document.getElementById(componentId).style.visibility='visible';
} else if (bpui.compB.req.status == 204){
// error, just show alert
alert("204 returned from AJAX call");
}
}
}
}



The following Javascript function is used by the popup triggering object to position the popup and set a timeout value to trigger the function that sends the AJAX request.

	bpui.compB.showPopup=function(popupx, eventx, itemId) {
// Position popup base on event and set timeout so popup isn't flashing all the time
var xx=0;
var yy=0;
if (!eventx) var eventx=window.event;
if (eventx.pageX || eventx.pageY){
xx=eventx.pageX;
yy=eventx.pageY;
} else if (eventx.clientX || eventx.clientY) {
xx=eventx.clientX + document.body.scrollLeft;
yy=eventx.clientY + document.body.scrollTop;
}
document.getElementById(popupx).style.left= (xx + 3) + "px";
document.getElementById(popupx).style.top=yy + "px";
// make sure the popup doesn't show all the time, need to mouseover for at least 1 second.
bpui.compB.timeout=setTimeout("bpui.compB.showPopupInternal('" + popupx + "', '" + itemId + "')", 1000);
}


The following Javascript function initiates the XMLHttpRequest, retrieves the popup Javascript object by identifier, concatenates the AJAX URL with the itemId, sets the specific popup's AJAX callback function and sends the request.
	bpui.compB.showPopupInternal=function(popupx, itemId) {
// initialize the AJAX request
bpui.compB.req=bpui.compB.initRequest();
// retrieve the correct popup object that is being shown
popObject=bpui.compB[popupx];

// concatenate the itemId value to the URI
url=popObject.urlx + escape(itemId);
// set the correct popup's callback function
bpui.compB.req.onreadystatechange = popObject.ajaxReturnFunction;
bpui.compB.req.open("GET", url, true);
// send the request
bpui.compB.req.send(null);
}



Processing AJAX requests against the full View:

The FacesServlet is used above to facilitate the serving of the static resources (the Javascript files ans CSS sheet) and the delegation of the dynamic resources (the AJAX request).  An alternate approach is to use a HTTP POST to submit the actual view state back to the server with your AJAX request.  Doing this requires the AJAX-enabled component to abort normal page processing to prevent rendering of the entire JSP page for AJAX requests.  This is done by using the ResponseComplete method on the FacesContext to skip the remaining phases. Another difference in the architecture is that the PhaseListener is responsible only for rendering the static resources and the Renderer handles the AJAX request, creating XML response.

Performance should be considered when determining how much view state is associated with each request relative to the volume of AJAX requests. While this approach is more tightly coupled with the JSF life cycle and therefore provides ready access to the Faces' objects, this tight coupling could result in a performance degradation in contrast to an approach that wouldn't have a tight coupling to the view state.


References


© Sun Microsystems 2006. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.