Including Ajax Functionality in a Custom JavaServer Faces Component

By Gregory Murray and Jennifer Ball    



 
Contents
 
What is Ajax?
JavaServer Faces Technology and Ajax
The DOJO Toolkit and Ajax
Using an Ajax-aware Custom Component
Steps to Include Ajax Support in a Custom Component
The Editable Label Example
Creating Necessary JavaScript Functions
Rendering JavaScript Tags With a Custom Renderer
Creating the PhaseListener Class to Handle the Ajax Requests



What is Ajax?



  • JavaScript that allows for interaction with the browser and responding to events
  • The DOM for accessing and manipulating the structure of the HTML of the page
  • XML, which represents the data passed between the server and client.
  • An XMLHttpRequest object for asynchronously exchanging the XML data between the client and the server.

                Figure 1: General Sequence of Ajax Request



  1.  The user generates an event, such as by clicking a button.  This results in a JavaScript call.
  2. An XMLHttpRequest object is created and configured with a request parameter that includes the ID of the component that generated the event and any value that the user might have entered.
  3. The XMLHttpRequest object makes an asynchronous request to the web server.  An object (such as a servlet or listener) receives the request, processes it, and stores any data in the request to the data store.  In the case of Ajax-aware JavaServer Faces components, the object that processes the request is a PhaseListener object.  We'll cover that more later in the document.
  4. The object that processed the request returns an XML document containing any updates that need to go to the client.
  5. The XMLHttpRequest object receives the XML data, processes it, and updates the HTML DOM representing the page with the new data.


JavaServer Faces Technology and Ajax


JavaServer Faces technology is designed to make web application development easier for page authors and developers alike.  The most important feature that logy oSun Java Studio CreatorNetBeans IDE



The DOJO Toolkit and Ajax






DOJOXMLHttpRequest



Using an Ajax-aware Custom Component






<%@ taglib uri="#" prefix="h" %>
                    

<%@ taglib uri="#" prefix="f" %>
                    

<%@ taglib prefix="dl" uri="#" %>
                  
 


<dl:dlabel valueBinding="#{SessionBean.name} " />
                    

                  



list of Ajax-aware componentsJava Blueprints Solutions CatalogHow to Use the Dynamic Text Component

Steps to Include Ajax Support in A Custom Component


Creating Custom UI Components

  1. Create necessary JavaScript functions, leveraging DOJO as much as possible.
  2. Add code to the component's renderer that will render the JavaScript tags to the page.
  3. Create the PhaseListener class, which handles the Ajax request, interacts with the custom component, and returns an XML document that contains the updated data.




The Editable Label Example




Figure 2: Screen shot of the Editable Label Example

Running the Example


Java Blueprints site


  1. Go to the Java Blueprints site on java.net.
  2. Click Documents & files in the right-hand navigation bar.
  3. Select the bpcatalog-ee5(2) directory.
  4. Download the bpcatalog-ee5-ea-0.6-installer.jar.
  5. Run the installer according to the directions on the page from where you downloaded it.

  1. Obtain the Java EE 5 SDK, install it, and start the Sun Java System Application Server.
  2. Add the Ant build tool included in the Application Server to your path.  It's located in the Applications Server installation's lib/ant/bin directory.
  3. Go to <bpcatalog_install>/bp-project/.
  4. Open build.properties in a text editor.
  5. Set the javaee.home property to the path of your Application Server installation.
  6. Set the javaee.server.passwordfile property to the fully-qualified path to a file that contains your password.  Follow the instructions in the build.properties file.
  7. Save build.properties.
  8. Go to <bpcatalog_install>/apps/webtier/bp-dynamic-text.
  9. Run ant .
  10. Run ant deploy.
  11. Launch a browser and enter this URL:

    http://localhost:8080/bp-dynamic-text/

 

The Pieces of the Example


bpcatalog.dev.java.net
  • index.jsp , a JSP page that contains the dlabel custom JavaServer Faces component tags.
  • confirmation.jsp , a JSP page that displays the updated data of the components.
  • DLabel , which defines the custom component.
  • DLabelRenderer , which renders the markup for the components and the script tags that reference the JavaScript files onto the HTML page.
  • DLabelPhaseListener , which processes the Ajax request, exchanges the data with the server-side object, and returns the XML data to the client.
  • SessionBean , which holds the component data on the server.
  • script.js , a JavaScript file that contains the functions used in this application.
  • styles.css , a stylesheet.
  • dojo.js , the JavaScript file that contains all the JavaScript functions supported by DOJO.
index.jsp

<%@ taglib uri="#" prefix="h" %>
                    

<%@ taglib uri="#" prefix="f" %>
                    

<%@ taglib prefix="dl" uri="#" %>
                    

...
                    

<f:view>
                    

   <h:panelGrid cellpadding="5" cellspacing="0"
                    

      columns="2" style="margin-bottom: 20px">
                    

 <h:outputText value="Name:" />
                    

        <dl:dlabel size="40" valueBinding="#{SessionBean.name} " />
                    

 <h:outputText value="Street:" />
                    

      <dl:dlabel size="40" valueBinding="#{SessionBean.street} " />
                    

       <h:outputText value="City:" />
                    

        <dl:dlabel size="40" valueBinding="#{SessionBean.city} " />
                    

 <h:outputText value="State:" />
                    

       <dl:dlabel size="40" valueBinding="#{SessionBean.state} " />
                    

        <h:outputText value="Zip:" />
                    

 <dl:dlabel size="40" valueBinding="#{SessionBean.zip} " />   
                    

   </h:panelGrid>
                    

   ...
                    

</f:view>
                    

                  

dl:dlabelSessionBean

How the Example Works




Figure 3: Sequence of Ajax Request in Editable Label Example

The following steps explain the architecture illustrated in Figure 3:

  1. The renderer outputs the script tag to the page.
    The index.jsp page contains an HTML script tag rendered by the renderer, DLabelRenderer.  While performing the rendering of the components, DLabelRenderer also creates a hash map with all the component IDs and values.

  2. The page obtains the JavaScript from the phase listener.
    A call is made to the URL, faces/ajax-dlabel-script.js, which is mapped to a FacesServlet instance.  This instance processes the DLabelPhaseListener instance, which recognizes the URL and returns the script. js page containing the client-side JavaScript code necessary for the Ajax interactions. When the page loads, it is initialized, and the initialize function is called.  This function registers all the JavaScript events that are needed for rendering the DLabel component tags on the page.
  3. The user enters a value in the field and clicks the Update Me button to update the data.
    After the user clicks a label and enters a value, he or she clicks the Update Me button, which generates an onclick event.  The updateOnServer JavaScript function, which is mapped to this event, creates an XMLHttpRequest object and configures it with the URL to the FacesServlet instance. If the user changed the Name field to Bob, the URL is faces/ajax-dlabel-update&component-id=Name&component-value=Bob.
  4. The XMLHttpRequest object makes a call to FacesServlet, which updates SessionBean.
    After the URL is configured, the XMLHttpRequest object makes a call to the FacesServlet instance, passing the URL faces/ajax-dlabel-update&component-id=Name&component-value=Bob.  The FacesServlet instance processes the DLabelPhaseListener instance that recognizes the URL the XMLHttpRequest object passed to it.   At this point, the FacesServlet instance also updates the value of the name property in SessionBean.
  5. The phase listener loo ks up the component ID included in the URL and stores the new data
      DLabelPhaseListener takes the component ID and value that is included in the URL, looks up the component ID and its value from a hash map, and updates it with the user's input.
  6. The phase listener sends the new data back to the page
    The DLabelPhaseListener instance generates an XML document containing the new component ID and value and returns it to the XMLHttpRequest object.
  7. The XMLHttpRequest object updates the HTML DOM with the new data
    The XMLHttpRequest object calls the XMLHttpRequest callback function.  This function updates the HTML DOM based on the contents of the XML document that was returned, and the response is sent back to the client.

 

Creating Necessary JavaScript Functions


The JavaScript Resource Center


  • Register JavaScript events on the components
  • Update the data to the server
  • Render extra components (such as the input field and the Update button in our example) and other changes when onmouseover events occur.
dojo.eventdojo.io

onmouseoveronclick

script.jsdojo.event.connect

bpui.dlabel = new DLabel();
                    

dojo.event.connect("before", window, "onload", bpui.dlabel, "initialize");
                    

                  
 
initialize

script.jsdojo.io.bindioXMLHttpRequestdojo.io.bindXMLHttpRequestXMLHttpRequest ajax-commons.js (as plain text)

Registering JavaScript Events on the Components


onloadinitializedlabelPlainTextgetElementsByClassRendering JavaScript Tags with a Custom RendererclassinitializeupdateItem
   
                      // key in on the dlabel components by the                       
className
function getElementsByClass(className){ var found = []; var elements= document.all ? document.all : document.getElementsByTagName("*"); for (var i=0; i < elements.length; i++){ // TODO: Handle multiple classnames if (elements[i].className==className) { found.push(elements[i]); } } return found; } this.initialize = function() { var pageElements = getElementsByClass('dlabelPlainText'); for (var loop = 0; loop < pageElements.length; loop++) { var pageElement = pageElements[loop]; var itemId = pageElement.getAttribute("id"); var itemValue = pageElement.firstChild.nodeValue; updateItem(itemId, itemValue); } }

Updating Data on the Server


updateOnServerupdateOnServer

function updateOnServer(itemId, itemValue) {
                    

   var bindArgs = {
                    

  url: "faces/ajax-dlabel-update",
                    

    method: "post",
                    

     content: {"component-id": itemId,
                    

             "component-value":itemValue} ,
                    

 mimetype: "text/xml",
                    

       load: function(type, data) {
                    

           processUpdateResponse(data);
                    

        } ,
                    

    ...
                    

   } ;
                    

   dojo.io.bind(bindArgs);
                    

}
                  

updateOnServerdojo.io.bind
  • url: This is the URL that the XMLHttpRequest object passes to the phase listener, which will use it to create an XML document with the component IDs and values.
  • method: This indicates the HTTP method to use when the function is called.  In this case, we want to use POST to post data to the server.
  • content: This attribute contains the ID of the component the user chose to edit as well as the value the user entered into the text field.
  • mimetype: Indicates the MIME type of the content to be passed from the phase listener
  • load: Specifies the function that will be invoked after the XML data is received.
updateOnServerprocessUpdateResponse

updateOnServerprocessUpdateResponse
updateItem

function processUpdateResponse(responseXML) {
                    

                     

    // sync the changes with what is on the server
                    

    var compId =
                    

        responseXML.getElementsByTagName("component-id")
                    

            [0].childNodes[0].nodeValue;
                    

    var compValue = "";
                    

    if(responseXML.getElementsByTagName("component-value")
                    

       [0].childNodes[0])
                    

    compValue = responseXML.getElementsByTagName("component-value")
                    

   [0].childNodes[0].nodeValue;
                    

    var status = responseXML.
                    

       getElementsByTagName("status")[0].childNodes[0].nodeValue;
                    

    updateItem(compId, compValue);
                    

}
                  
 
scriptscript.js

Rendering JavaScript Tags With a Custom Renderer






index.jspconfirmation.jspdLabelencodeEndDLabelRendererscriptDLabelRendererRENDERED_SCRIPT_KEY

encodeEndDLabelRendererrenderScriptOnceDLabelRendererResponseWriterscriptRENDERED_SCRIPT_KEYscript

private void renderScriptOnce(ResponseWriter writer,
                    

  UIComponent component, FacesContext context)
                    

    throws IOException {
                    

   Map requestMap =
                    

       context.getExternalContext().getRequestMap();
                    

   Boolean scriptRendered =
                    

       (Boolean)requestMap.get(RENDERED_SCRIPT_KEY);
                    

                     

   if (scriptRendered == Boolean.TRUE) {
                    

  return;
                    

   }
                    

                     

   requestMap.put(RENDERED_SCRIPT_KEY, Boolean.TRUE);
                    

   ...
                    

}
                    

                  

renderScriptOnceFacesServletfaces/script

writer.startElement("script", component);
                    

writer.writeAttribute("type", "text/javascript", null);
                    

String ssrc = "faces/ajax-dlabel-script.js";
                    

writer.writeAttribute("src", ssrc, null);
                    

writer.endElement("script");
                    

                  
 
src

DLabelRendererResponseWriter

           
                    

HashMap bindings =
                    

   (HashMap)session.getAttribute("dBindings");
                    

if(bindings == null) {
                    

     bindings = new HashMap();
                    

   session.setAttribute("dBindings", bindings);
                    

}
                    

...
                    

synchronized(bindings) {
                    

     bindings.put(baseId, valueBinding);
                    

}
                    

                  
 
DLabelPhaseListener

DLabelRendererdivdivdivdlabelPlainTextRegistering JavaScript Events on the Componentsinitialize

writer.startElement("div", component);
                    

writer.writeAttribute("id", baseId, "id");
                    

writer.writeAttribute("class", "dlabelPlainText", null);
                    

writer.write(value);
                    

writer.endElement("div");
                    

writer.write("\n")
                  

Creating the PhaseListener Class to Handle the Ajax Requests


PhaseListenerafterPhase(PhaseEvent)PhaseListener

PhaseListenerPhaseListenerscript

DLabelPhaseListener
  • Renders the JavaScript to the page based on the URL passed to it
  • Gets the component ID and new value from the request, edits the XML file with the new value, and puts the file into the response.


DPhaseListener

private static final String SCRIPT_VIEW_ID = "ajax-dlabel-script.js";
                    

private static final String DOJO_VIEW_ID = "dojo.js";
                    

private static final String CSS_VIEW_ID = "ajax-dlabel.css";
                    

private static final String UPDATE_STATE_ID = "ajax-dlabel-update";
                    

                  
 
afterPhaseDLabelPhaseListenerajax-dlabel-script.jshandleResourceReqestscript.js

public void afterPhase(PhaseEvent event) {
                    

        String rootId =
                    

             event.getFacesContext().getViewRoot().getViewId();
                    

  if(rootId.endsWith(SCRIPT_VIEW_ID)) {
                    

          handleResourceRequest(event,
                    

                       "script.js", "text/javascript");
                    

            ...
                    

 }
                    

     ...
                    

}
                  

Rendering the JavaScript to the Page


handleResourceRequestscript.jsdojo.jsresponseComplete

DLabelPhaseListenerXMLHttpRequest

Getting the User Input and Creating the XML File with the New Data


updateOnServerfaces/ajax-dlabel-updateDLabelPhaseListenerXMLHttpRequestafterPhaseDLabelPhaseListenerhandleAjaxRequest

private static final String UPDATE_STATE_ID = "ajax-dlabel-update";
                    

...
                    

public void afterPhase(PhaseEvent event) {
                    

  ...
                    

 }  else if (rootId.indexOf(UPDATE_STATE_ID) != -1) {
                    

              handleAjaxRequest(event);
                    

   }
                    

     ...
                    

}
                    

                  
 
handleAjaxRequest

FacesContext context = event.getFacesContext();
                    

HttpServletResponse response =
                    

      (HttpServletResponse)context.getExternalContext().
                    

          getResponse();
                    

Object object = context.getExternalContext().getRequest();
                    

if (!(object instanceof HttpServletRequest)) {
                    

 return;
                    

}
                    

HttpServletRequest request = (HttpServletRequest)object;
                    

HttpSession session = request.getSession();
                    

String compId = request.getParameter("component-id");
                    

String compValue = request.getParameter("component-value");
                    

                     

                  
 


ValueExpression valueBinding = (ValueExpression)
                    

       ((HashMap)sContext.getAttribute("dBindings")).get(compId);
                    

valueBinding.setValue(context.getELContext(), compValue);
                    

                  
 
handleAjaxRequestcompIdcompValue

String message = "success";
                    

StringBuffer sb = new
                    

StringBuffer();
                    

sb.append("<message>");
                    

sb.append("<component-id>" + compId + "</component-id>");
                    

sb.append("<component-value>" + compValue + "</component-value>");
                    

sb.append("<status>" + message + "</status>");
                    

sb.append("</message>");
                    

try {
                    

      response.setContentType("text/xml");
                    

        response.setHeader("Cache-Control", "no-cache");
                    

    response.getWriter().write(sb.toString());
                    

}  catch (IOException iox) {
                    

        System.err.println(
                    

    "DLabelPhaseListener error writing Ajax response : "
                    

            + iox);
                    

}
                    

                  
 
updateOnServerXMLHttpUpdateResponseupdateOnServerprocessUpdateResponse

Conclusion




For More Information


JavaScript Resource Center
JavaServer Faces Technology
Java Blueprints Solutions Catalog
NetBeans
DOJO JavaScript Toolkit

Java 8 banner (182)