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