Using a Servlet with JavaServer Faces Technology and 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.  This entry discusses strategies that add AJAX support to JSF components by introducing a servlet to process the AJAX requests.  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 servlet to intercept and fulfill the custom components AJAX requests.  The resources required by the custom JSF component will be packaged with the application bundle and accessed directly.

We will discuss the strategy of using a Servlet 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 Java Servlet.

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.

For a simple example of this component's functionality see the standalone.jsp that contains all the scripting and functionality in a single JSP page.


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 Servlet mapping that is configured in the web.xml deployment descriptor (CompAServlet) and the populated query string associated with the request.  For example:
    /bp-jsf-example/CompAServlet?itemId=test1A
2) The Servlet receives the request, retrieves the itemId parameter of the request and create/sends the appropriate response.

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

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


Custom AJAX Component Creation

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 "CompA" custom component as a reference for the coding examples.

Server Side Code:

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

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 commons artifacts.  If it is already set, then the artifacts aren't re-rendered.

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 + "/compA.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 hard code the markup in the renderer, setting the values that are necessary to maintain the name space conventions.  Here, the component's ID is pre-pended to the 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.
	
writer.write("<div class=\"bpui_compA_popTop\">");
writer.write(" <div class=\"bpui_compA_cornerTL\"><div class=\"bpui_compA_cornerTR\">");
writer.write(" <center><font color=\"white\"><b><span id=\"" + clientId + "_title\">title</span></b></font></center>");
writer.write(" </div></div>");
writer.write("</div>");
writer.write("<div class=\"bpui_compA_popMid\">");
writer.write(" <table border=\"0\" style=\"width: 100%\">");
writer.write(" <tr>");
writer.write(" <td>");
writer.write(" <table border=\"0\" bgcolor=\"#ffffff\" cellpadding=\"5\" cellspacing=\"5\">");
writer.write(" <tr>");
writer.write(" <td><span id=\"" + clientId + "_message\">Value</span></td>");
writer.write(" </tr>");
writer.write(" </table>");
writer.write(" </td>");
writer.write(" </tr>");
writer.write(" </table>");
writer.write("</div>");
writer.write("<div class=\"bpui_compA_popBot\">");
writer.write(" <div class=\"bpui_compA_cornerBL\"><div class=\"bpui_compA_cornerBR\">");
writer.write(" </div></div>");
writer.write("</div>");

Java Servlet (com.sun.javaee.blueprints.components.ui.example.CompAServlet)
In the HttpServlet the HttpServletResponse is passed in as an argument, 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.

	protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/xml;charset=UTF-8");
PrintWriter out = response.getWriter();
String itemId=request.getParameter("itemId");

response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);
if(itemId != null) {
out.println("<response>");
// custom response code goes here
...

out.println("</response>");
} else {
out.println("<response>");
out.println("<title><![CDATA[REQUEST ERROR]]></title>");
out.println("<message><![CDATA[The query parameter 'itemId' required]]></message>");
out.println("</response>");
}
out.flush();
out.close();
}


Component's faces-config.xml File
The only artifact that is registered in the faces-config.xml file that pertains to the "CompA" component is the custom renderer that uses an standard component family.
	<!-- register the custom renderer for the standard component family -->
<render-kit>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>CompA</renderer-type>
<renderer-class>com.sun.javaee.blueprints.components.ui.example.CompARenderer</renderer-class>
</renderer>
</render-kit>



Client Side Code:

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

	<ui:compA id="pop1" url="CompAServlet?itemId="/>

        <a href="#" onmouseover="bpui.compA.showPopup('pop1', event, 'test1A')" 
onmouseout="bpui.compA.hidePopup('pop1')" style="cursor: pointer"><b>Mouse over link to see popup (test1A)</b></a><br/>
<small><i>(With a Servlet fulfilling 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_compA_popup">
<div class="bpui_compA_popTop">
<div class="bpui_compA_cornerTL"><div class="bpui_compA_cornerTR">
<center>
<font color="white">
<b><span id="pop1_title">title</span></b></font>
</center>
</div></div>
</div>
<div class="bpui_compA_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_compA_popBot">
<div class="bpui_compA_cornerBL"><div class="bpui_compA_cornerBR">
</div></div>
</div>
</div>

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


Javascript functions:
The following Javascript function is called when the CompA 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.compA.createPopup=function(componentId, urlx) {
this.componentId=componentId;
this.urlx=urlx;

this.ajaxReturnFunction=function() {
// make sure response is ready
if (bpui.compA.req.readyState == 4) {
// make sure it is a valid response
if (bpui.compA.req.status == 200) {
// populate the popup with the info from the response
var resultx=bpui.compA.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.compA.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.compA.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.compA.timeout=setTimeout("bpui.compA.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.compA.showPopupInternal=function(popupx, itemId) {
// initialize the AJAX request
bpui.compA.req=bpui.compA.initRequest();
// retrieve the correct popup object that is being shown
popObject=bpui.compA[popupx];

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


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.