The AjaxComponent strategy for JSF: The best of both worlds

Sky-Tiger發表於2008-01-06

Adding Ajax capability to JSF applications is easy to talk about but not so simple to do. Developing a custom lifecycle phase is too complex for most needs; putting the handler code in the component's decode() method doesn't give you enough control; and using a phase listener by itself breaks encapsulation. In this article, Matt Tyson demonstrates the AjaxComponent strategy -- an effective and practical way of integrating Ajax into your JSF applications, and easily extensible to boot.

[@more@]

If you want to handle Ajax requests in JavaServer Faces (JSF) applications, you'll probably start with a handful of popular approaches and quickly realize that each one has its advantages and disadvantages. For instance, if you use a component to set up and handle an Ajax request, you remain within the JSF component model and keep all the code related to the request encapsulated. If you use a phase listener as a kind of proxy to delegate to the component, you can invoke a specific method on the component, avoid invoking other components unnecessarily, and avoid issues with the immediate attribute.

Normally, if you just use a component to handle an Ajax request, you would put the Ajax code in one of the component's standard JSF methods (like decode()) and allow JSF to execute it in the normal course of the request processing lifecycle, but this doesn't give you enough control. On the other hand, if you were to use a phase listener alone, you would break encapsulation by putting unrelated logic in the phase listener. You can think of the component as achieving the where that you want, and of the phase listener as getting you the when that you want.

If you combine the component and phase listener approaches, you get both the when and the where, and you can also add new Ajax-enabled components to your application with ease. Moreover, you keep the components' standard request processing and Ajax processing nicely separated. The JSF Ajax component implements an interface with a single method, handleAjaxRequest(), which the phase listener invokes. This component interface, and the combined strategy, is called the AjaxComponent. Using it, you gain the simplicity of a component and the power of a phase listener. The combined approach you'll learn about in this article is very effective and practical for most applications, and is easily extensible, too.

When you need to Ajax-enable a JSF component, AjaxComponent is a solid strategy for doing it. AjaxComponent also works seamlessly with the JSF AjaxCommand strategy described in my previous JavaWorld article.

The pieces of the puzzle

Building a JSF component involves a fair amount of overhead. It might even bring to mind the process of creating EJBs. It looks as if this problem will be addressed in JSF 2.0, but for now, with JSF 1.1, we're still stuck with a multi-step process. On the positive side, these steps bring you a reusable component that can be accessed with a simple tag in your JSPs. Moreover, components can be, and are, shared among developers -- just take a look at the Apache Tomahawk Trinidad and Tobago projects.

The following elements are required to build a JSF component, and the AjaxComponent is no exception. The file in parentheses is the element as it exists for the example AjaxComponent that you'll see built in the course of this article. (See Resources to download the complete code for this application.)

  • A Component class (AjaxComponent.java)
  • A Renderer (AjaxComponentRenderer.java)
  • A tag handler class (AjaxComponentTag.java)
  • A tag descriptor file entry (Tutorial.tld)
  • An entry in faces-config.xml

The sample application includes an AjaxComponent with all of the above elements. Note that the application uses MyFaces 1.1.5 and the Dojo JavaScript library.

The best way to get a good understanding of how the AjaxComponent strategy works is to examine each of the elements listed above in turn. After that, I'll wrap up by describing how to deploy the sample application and some changes that a production component might require.

The Component class and the AjaxInterface

Let's begin with the extremely simple and essential interface: the AjaxInterface, shown in Listing 1. This is the interface that will be implemented by the sample JSF component you'll build over the course of this article.

Listing 1. The AjaxInterface

public interface AjaxInterface {

    public void handleAjaxRequest(FacesContext context);
}

Nice and simple, just how we like it.

If you are familiar with how the JSF component model works, you know that the Component class is essentially a state-holding mechanism, and the real work is done by the Renderer. The Component class defines what the Component is, while the Renderer defines what it does. (Just to avoid confusion, I should point out that when people talk about a JSF component in general, they usually mean the whole bundled thing: the Component class, the Renderer, tag handler, and so on, as opposed to the Component class alone.)

Now take a look at the Component class itself, shown in Listing 2.

Listing 2. The Component class

public class AjaxComponent extends UIComponentBase implements AjaxInterface {
  private static final transient Log log = LogFactory.getLog(tutorial.jsf.ajax.component.AjaxComponent.class);

  public static final String DEFAULT_RENDERER_TYPE = "tutorial.jsf.ajax.component.AjaxComponentRenderer";
  public static final String COMPONENT_FAMILY = "tutorial.jsf.ajax.component.AjaxComponent";
  public static final String COMPONENT_TYPE = "tutorial.jsf.ajax.component.AjaxComponent"; // Used by Tag Handler

  public AjaxComponent() {
    this.setRendererType(AjaxComponent.DEFAULT_RENDERER_TYPE);
  }

  public String getFamily() {
    return COMPONENT_FAMILY;

  /**
   * This method is executed by the Ajax listener when an Ajax call for this component is detected.
   */
  public void handleAjaxRequest(FacesContext context){
    // Delegate to the Renderer
    AjaxRendererInterface renderer = (AjaxRendererInterface)this.getRenderer(context);
    renderer.handleAjaxRequest(context, this);
  }
}

Again, this is a very simple class. Of course, much of the complexity exists in the UIComponentBase base class; but the work you'll be concerned with is just delegated to the Renderer, in order to keep the code that handles the request in the same place as the code that sets up the request.

The Renderer and AjaxRendererInterface

The Renderer in a JSF component is responsible for outputting whatever is necessary to display the component, and for handling the input coming from the user for that component. In this case (and for most cases, really), the output is HTML and JavaScript. For the standard (non-Ajax) request processing, the decode() method processes the input. In this case, as you saw above, the component delegated the handling of the Ajax response to the renderer.

Again, the AjaxRendererInterface defines one method, handleAjaxRequest(). Listing 3 demonstrates how that method works.

Listing 3. The Renderer: handleAjaxRequest()

public void handleAjaxRequest(FacesContext context, UIComponent component){
  if (log.isInfoEnabled()) { log.info("BEGIN handleAjaxRequest()"); }
  HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();

  String textField = request.getParameter(INPUT_NAME);
  String serverContribution = "SERVER RESPONSE: ";
  StringBuffer xml = null;

  if (textField == null){
      if (log.isInfoEnabled()) { log.info("No parameter found for text field."); }
  } else {
    if (log.isTraceEnabled()) { log.trace("textField: " + textField); }
    // We now have the new value entered by the user
    // Now we create our XML response
    xml = new StringBuffer("");

    xml.append("" + serverContribution + textField + "");

    xml.append("OK");
  }

  if (xml == null){
    if (log.isInfoEnabled()) { log.info("Response is null."); }
    xml = new StringBuffer(this.getErrorString());
  }
  // Now we are ready to send the response
  HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse();

  response.setContentType("text/xml");
      response.setHeader("Cache-Control", "no-cache");

      try {
        response.getWriter().write(xml.toString());
        if (log.isInfoEnabled()) { log.info("Response sent: " + xml); }
      } catch (IOException e){
        if (log.isErrorEnabled()) { log.error("Error writing ajax response.", e); }
      }
}

protected String getErrorString(){
    return new String("There was a problemERROR");
}


Remember, the handleAjaxRequest() method is invoked when the user has submitted an Ajax request to the component. The method is responsible for doing everything necessary, both receiving data and formulating the response. This sample method is very simple, but it communicates the idea that you get some data from the user, do some processing on the server, and return a response. All this method does is take whatever the user sent and return it back as SERVER RESPONSE: to demonstrate that the server had its say.

The technique for packaging the response is interesting. You wrap it in XML, which the front-end JavaScript is expecting. The whole response is framed in tags; in addition to the , it includes a element. You can see that the response can be as simple or involved as the situation merits.

If anything goes wrong, you need to prepare an appropriate response, with its status set to ERROR.

Finally, you take the XML string and send it off to the JavaScript that initiated the call. The request processing is then complete.

Notice that you did not call FacesContext.responseComplete() anywhere; that's because you know that it was called by the PhaseListener that invoked this method in the first place. You'll see that happen when you take a closer look at the PhaseListener.

Setting up the Ajax Request: The encodeBegin() method

Next, you'll see how the renderer outputs itself with the ability to make the Ajax call. This simple example only requires the encodeBegin() method, illustrated in Listing 4.

Listing 4. The Renderer: encodeBegin()

private static final String INPUT_ID = "tutorial.jsf.ajax.component.INPUT";
  private static final String INPUT_NAME = "tutorial.jsf.ajax.component.INPUT"; // This will be the text field key in the request
  private static final String BUTTON_ID = "tutorial.jsf.ajax.component.BUTTON";
  private static final String MESSAGE_DIV_ID = "tutorial.jsf.ajax.component.MESSAGE_DIV";
    private static final String CLIENT_ID = "tutorial.jsf.ajax.component.CLIENT_ID";

  //...

public void encodeBegin(FacesContext context, UIComponent component) throws IOException{
  if (log.isTraceEnabled()) { log.trace("begin encodeBegin()"); }

  HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();

  AjaxComponent ajaxComp = (AjaxComponent)component;

  ResponseWriter out = context.getResponseWriter();

  String clientId = ajaxComp.getClientId(context);

  out.startElement("div", ajaxComp);
  out.writeAttribute("id", clientId, null);
  out.writeAttribute("style", "border:solid; width:200; height:200;", null);

  out.startElement("div", ajaxComp); // Message div
  out.writeAttribute("id", MESSAGE_DIV_ID, null);
  out.endElement("div"); // Message div

  out.startElement("input", ajaxComp);
  out.writeAttribute("type", "text", null);
  out.writeAttribute("id", INPUT_ID, null);
  out.writeAttribute("name", INPUT_NAME, null);
  out.endElement("input");

  out.startElement("button", component);
  out.writeAttribute("type", "button", null);
  out.writeAttribute("name", BUTTON_ID, null);
  out.writeAttribute("id", BUTTON_ID, null);
  out.writeAttribute("value", BUTTON_ID, null);
  out.writeText("Ajax It", "null");
  out.endElement("button");

  // A hidden field to hold the URL of the server for the ajax request
  out.startElement("input", ajaxComp);
  out.writeAttribute("id", "tutorial.jsf.ajax.component.SERVER", null);
  out.writeAttribute("type", "hidden", null);
  out.writeAttribute("value", request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getRequestURI(), null);
  out.endElement("input");

  // A hidden field to hold the component Client ID
  out.startElement("input", ajaxComp);
  out.writeAttribute("id", CLIENT_ID, null);
  out.writeAttribute("type", "hidden", null);
  out.writeAttribute("value", clientId, null);
  out.endElement("input");

  out.write("AjaxComponentn");
  out.startElement("script", ajaxComp);
  out.write("dojo.addOnLoad(AjaxComponent.loadComponent());n"); // Sets up the client side JS
  out.endElement("script");
}


Most of this code is standard JSF Renderer fare. It outputs HTML that displays what you want. Notice, however, that it's careful to give every element you're interested in a distinct name. That's what all those String constants are for. This is important, because you're going to need to find these elements again in the client-side JavaScript.

Notice also that one bit of information sent is the URL of the server; this tells your JavaScript where to send its eventual Ajax call. Another piece of information is the JSF clientId. You will see that this is used later by the PhaseListener. Both of these are written to hidden fields, where the user can't see them, but they are available to the JavaScript.

This is the critical line:

out.write("dojo.addOnLoad(AjaxComponent.loadComponent());n");

This code uses the Dojo JavaScript library to add an onLoad listener that calls a method once the page has loaded. Note that the component requires that the JavaScript function AjaxComponent.loadComponent() be available! In other words, the page using the AjaxComponent tag must include the JavaScript file with the AjaxComponent JavaScript.

There are more sophisticated means of incorporating JavaScript (and CSS) files into the component that avoid the JSP include. If you are using MyFaces, you can use the AddResource mechanism, but be aware that this will couple your component to the MyFaces implementation. Another strategy you might be interested in is the use of Weblets. For simplicity's sake, though, this example will just include the JavaScript file in the JSP.

The client side: AjaxComponent.loadComponent()

Take a look at the loadComponent() function in Listing 5. Keep in mind how this function is invoked. After the HTML page has been rendered, along with all the elements of the component, the function will be called.

Listing 5. JavaScript: AjaxComponent.loadComponent()

AjaxComponent.loadComponent = function(){
  // Get the Server
  var serverField = document.getElementById("tutorial.jsf.ajax.component.SERVER");
  AjaxComponent.server = serverField.value; // Save the server for use in ajax call

  // Get the output message div
  var msgDiv = document.getElementById("tutorial.jsf.ajax.component.MESSAGE_DIV");
  AjaxComponent.messageDiv = msgDiv; // This is the actual domNode

  // Get the output message div
  var clientId = document.getElementById("tutorial.jsf.ajax.component.CLIENT_ID");
  AjaxComponent.clientId = clientId.value;

  // Get the button
  var button = document.getElementById("tutorial.jsf.ajax.component.BUTTON");
  // Use dojo to add an event handler -- like onClick="..." but handles cross browser issues
  dojo.event.connect(button, "onclick", AjaxComponent, "onButtonClick");
}

You can see that this function's job is to find every important element of the component by the IDs you gave it in the encode() method. First you get and save the server URL. Next, you grab the

element to use for outputting messages. In that case, you actually save the domNode of the
. Then you get and save the clientId. Finally, you get the button element, and attach an event listener to it.

Note that as written, it is not safe to use multiple instances of the component on the same page. That's because there's nothing to distinguish the elements of different component instances from each other. You can overcome this by incorporating the cleintId (which is always unique for every component on the page) into the IDs of the elements you need to get.

Using dojo.event.connect() to add your JavaScript event listener means that you don't have to worry about cross-browser concerns. This method says that for the domNode in the variable button, you'll add an onclick listener that invokes the AjaxComponent.onButtonClick() function. (Note that the connect method has a three-argument version that you can use if your handler function isn't a method on an object like AjaxComponent.)

So now you've gotten to the point where the user has clicked on the button. When that happens, the JavaScript executes the AjaxComponent.onButtonClick() function, shown in Listing 6.

Listing 6. JavaScript: AjaxComponent.onButtonClick() event handler

AjaxComponent.onButtonClick = function(evt){

  // Get the input text field
  var input = document.getElementById("tutorial.jsf.ajax.component.INPUT");
  AjaxComponent.inputField = input.value;

  // Build the request params
  var params = {
        "tutorial.jsf.ajax.AJAX_REQUEST" : "true",
        "tutorial.jsf.ajax.component.INPUT" : AjaxComponent.inputField,
        "tutorial.jsf.ajax.AJAX_CLIENT_ID" : AjaxComponent.clientId
    };
    // Send ajax request
    dojo.io.bind({
                url: AjaxComponent.server,
                method: "GET",
                load: function(type, data, evt){
                    AjaxComponent.onAjaxResponse(data);
                },
                mimetype: "text/xml",
      content: params
    });
}

Here again you leverage dojo to make the Ajax call painless. First, you get the element that contains the text entered by the user, and hold onto its value in the inputField variable.

Next, you compose an associative array. This array holds the request parameters that you want to send, along with the request. The first parameter tells the PhaseListener (which you'll see in more detail shortly) that the request is an Ajax request. The second parameter contains the user input. The third and final parameter communicates to the PhaseListener the component that made the request.

It's worth mentioning here that you can send any number of parameters along to help direct the processing that takes place on the server, including different kinds of requests issued by your component if need be. For example, different button clicks might require different responses, in which case you would send along a parameter declaring which button was clicked.

Now you're ready to send the request. You use a dojo.io.bind() call, which has an associative array for its argument.

  • The url element tells the request where to go. Use the server URL you saved earlier.
  • method defines that kind of request that you're sending (use POST if your request might get large).
  • load defines the function that's responsible for handling the response from the server. You use the function to delegate to your own function, passing in the data argument, which contains the actual data from the server (in this case, the XML that you prepared in the handleAjaxRequest() method of the AjaxRenderer).
  • mimetype is important in identifying the kind of data that is being sent.
  • context holds the parameters you prepared earlier.

The tag handler class and taglib descriptor

Before you can use your new AjaxComponent tag in a JSP, you need to create a tag handler for it, and also declare it in a taglib file.

The AjaxComponentTag class, shown in Listing 7, is very simple, because there are no attributes to manage on the tag. If there were, this is where you'd pass them on to the component itself.

Listing 7. The tag handler: AjaxComponentTag

public class AjaxComponentTag extends UIComponentTagBase {
  private static final transient Log log = LogFactory.getLog(tutorial.jsf.ajax.component.AjaxComponentTag.class);

  public AjaxComponentTag() {
  }

  public String getComponentType() {
    return AjaxComponent.COMPONENT_TYPE;
  }

  public String getRendererType() {
    return AjaxComponent.DEFAULT_RENDERER_TYPE;
  }
}

You do have to declare here the component type that the handler is for, and what the renderer type is. This will become important later when you declare the component in faces-config.xml.

Now you declare the tag in a taglib entry, as in Listing 8.

Listing 8. The tag library descriptor



tlib-version>1.3
  1.2
  tut
  

  JSF Tutorial - Ajax
  
    ajaxComponent
    tutorial.jsf.ajax.component.AjaxComponentTag

    JSP
    
      The AjaxComponent example.
    
    <!-- UIComponent attributes --&gt
    

      id
      false
      false
      java.lang.String

      
        The developer-assigned ID of this component. The ID must
        be unique within the scope of the tag's enclosing naming
        container (e.g. h:form or f:subview). This value must be
        a static value.
      
    
    
      binding

      false
      false
      java.lang.String
      

        Identifies a backing bean property (of type UIComponent
        or appropriate subclass) to bind to this component
        instance. This value must be an EL expression.
      
    
    
      rendered
      false

      false
      java.lang.String
      
        A boolean value that indicates whether this component
        should be rendered. Default value: true.
      

    
  

Listing 8 includes the entire taglib file from the example app. You just need to place this somewhere on the classpath, usually in the WEB-INF directory.

You might wonder what all these attributes are doing in the listing, given I just said that we don't have any, and that's why the tag handler class is so simple. Those attributes belong to the UIComponent interface and are handled by the base classes you've extended (UIComponentBase and UIComponentTagBase). Because tag descriptors do not support inheritance, you must declare them here for your tag.

faces-config.xml

Now you are finally ready to declare the component for use in the application.

You can see in the example application that the faces-config.xml file, part of which is shown in Listing 9, is also deployed inside the WEB-INF folder. JSF automatically searches for that file to configure itself. (It also looks for the file inside the META-INF folder of JAR files, which allows you to package your component descriptors along with the components).

Listing 9. faces-config.xml component and phase listener entries



odewrap">
public static final String DEFAULT_RENDERER_TYPE = "tutorial.jsf.ajax.component.AjaxComponentRenderer";
public static final String COMPONENT_FAMILY = "tutorial.jsf.ajax.component.AjaxComponent";
public static final String COMPONENT_TYPE = "tutorial.jsf.ajax.component.AjaxComponent"; // Used by Tag Handler

public AjaxComponent() {
  this.setRendererType(AjaxComponent.DEFAULT_RENDERER_TYPE);
}

public String getFamily() {
  return COMPONENT_FAMILY;
}

Remember also that you declared the type and renderer-type in the tag handler. Getting this configuration step just right, though simple, can sometimes be a little touchy. Make sure the strings you use on the component and tag handler match what you declare in faces-config.xml, and also that the classnames are exactly right.

The PhaseListener

Remember that you've also declared a phase listener in the lifecycle element. That phase listener is the key to the whole process. Listing 11 includes the whole class.

Listing 11. The PhaseListener class (AjaxListener)

package tutorial.jsf.ajax;

// Imports exluded ...

public class AjaxListener implements PhaseListener {
  private static final transient Log log = LogFactory.getLog(tutorial.jsf.ajax.AjaxListener.class);
  // These constants are used to get values from the request params
  // Determining if the request is an AjaxComponent and what the client id of the component
  // making the request is, respectively.
  private static final String AJAX_PARAM_KEY = "tutorial.jsf.ajax.AJAX_REQUEST";
  private static final String AJAX_CLIENT_ID_KEY = "tutorial.jsf.ajax.AJAX_CLIENT_ID";

  public AjaxListener() {
  }

  /**
   * Handling the any potential Ajax component requests after the Restore View phase makes the restored view
   * available to us.  Therefore, we can get the component (which made the request) from the view,
   * and let it respond to the request.
   */
  public void afterPhase(PhaseEvent event) {
    if (log.isInfoEnabled()) { log.info("BEGIN afterPhase()"); }
    FacesContext context = event.getFacesContext().getCurrentInstance();

    HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();
    String ajaxParam = request.getParameter(AJAX_PARAM_KEY);

    // Check for the existence of the Ajax param
    if (ajaxParam != null && ajaxParam.equals("true")){  // 1
      if (log.isInfoEnabled()) { log.info("This is an ajax request."); }
      context.responseComplete(); // Let JSF know to skip the rest of the lifecycle

      String componentId = request.getParameter(AJAX_CLIENT_ID_KEY);  // Get the Ajax component ID
      if (componentId == null){ // 2
        if (log.isWarnEnabled()) { log.warn("No Client ID found under key: " + componentId); }
      } else {
        handleAjaxRequest(context, componentId);
      }

      // Save the state of the page
      context.getApplication().getStateManager().saveSerializedView(context);
    }
  }

  protected void handleAjaxRequest(FacesContext context, String ajaxClientId) {
    UIViewRoot viewRoot = context.getViewRoot();

    AjaxInterface ajaxComponent = null;
    try {
      ajaxComponent = (AjaxInterface)viewRoot.findComponent(ajaxClientId); // 3
    } catch (ClassCastException cce){
      throw new IllegalArgumentException("Component found under Ajax key was not of expected type.");
    }
    if (ajaxComponent == null){
      throw new NullPointerException("No component found under specified client id: " + ajaxClientId);
    }

    ajaxComponent.handleAjaxRequest(context); // 4
  }

  public void beforePhase(PhaseEvent arg0) {
    // We do nothing in the before phase.
  }

  public PhaseId getPhaseId() {
    return PhaseId.RESTORE_VIEW;
  }
}


Implementing PhaseListener requires only three methods. The first thing to decide is what phase the listener will affect. For the AjaxComponent, you want the RESTORE_VIEW phase. After that, you need to decide whether to execute before or after the phase (or both before and after, if need be).

The reason you choose RESTORE_VIEW is that it's the first phase; because you're handling an Ajax request, you want to avoid the rest of the JSF processing lifecycle. You use the afterPhase() method because you need the view to be restored, and thus make the component available to use, to handle the request.

Take a look at Listing 11 in more detail. I've annotated the code with some commented numbers that I'll use to direct your attention to particularly interesting sections.

In line 1, you check for the request parameter denoting this as an Ajax request. Remember back in the JavaScript, when you added that to the parameters of the Ajax call? If that parameter is not found, you immediately leave the phase listener and allow the regular lifecycle to proceed.

If, on the other hand, you do find the Ajax request parameter, you first call FacesContext.requestComplete(). That means that as soon as this phase is done, Ajax will skip any more processing.

Next, you get the clientId from the request. That was also sent with the Ajax call. In line 2, you check to make sure that it was found. If not, that means the request was set up wrong, and was flagged as an Ajax call, but no component clientId was sent. On the other hand, if the clientId is found, you call handleAjaxRequest().

Line 3 shows how you can recover the AjaxComponent from the view. JSF maintains each view as a hierarchical model, and every component can find its children via the findComponent() method. So you get the root of the view, and use find component to get the component that made the request.

Next, you check to make sure the component was found. If not, something has gone awry. Perhaps the clientId was not correct, or maybe you somehow haven't gotten the right view back. That can happen if you sent the request to a different URL than the one that set it up. In that case, JSF will create a new view tree -- one that doesn't have your component in it.

At line 4, once the component is found, you execute the handleAjaxRequest(). That brings you back to where you began. You've already seen how the component delegates to the renderer, and the renderer then returns an XML response.

You've also seen how the JavaScript onButtonClick() method used dojo.io.bind() to set the AjaxComponent.onAjaxResponse() function to handle the response. You can see how the onAjaxResponse() actually does this in Listing 12.

Listing 12. Handling the XML response back from the server: AjaxComponent.onAjaxResponse()

AjaxComponent.onAjaxResponse = function(responseXml){
    var status = responseXml.getElementsByTagName("status")[0].childNodes[0].nodeValue;
    var msg = responseXml.getElementsByTagName("message")[0].childNodes[0].nodeValue;

    var style = "";
    if (status != "OK"){
      style = "color: red;";
    }

    var msgNode = document.createTextNode(msg);

    var para = document.createElement("p");
    para.setAttribute("style", style);
    para.appendChild(msgNode);

    AjaxComponent.messageDiv.appendChild(para);
}

Parsing XML in JavaScript can be a little finicky. Still, you can see what's going on here. You get what was sent in the element, and create a style based on that. Then you create a paragraph element, give it the style, and append the text from the tag. Finally, using the domNode you saved when you rendered the component, you append the new paragraph element to the message

.

Building and deploying the sample application

The example application uses Maven 2 for its build tool. Once you've got Maven installed, browsing to the root of the application and typing mvn clean install will compile the class files and package them into a WAR file, along with the descriptors and config files from the webapp directory.

If you are familiar with Ant, Maven can take a little getting used to, but it saves time by defining a typical structure for things (like a Web app) and automating tasks around that structure. If you develop with Eclipse, note that you can tell Maven to build all your .project and .classpath files by using mvn eclipse:eclipse. You should also be aware that when Maven generates these files, it uses an environment variable called M2_REPO that you'll want to add to Eclipse. M2_REPO points to the root of the Maven repository.

The Maven repository holds all the files that your builds have requested. The first time you request a file, it has to go out to the Internet to find it, using the artifact ID and version provided in pom.xml. The latter file is what defines the build process. (This quick description isn't intended to be thorough; if you need more information on Maven, see the Resources section.)

Once you've run mvn clean install, the WAR file is ready for deployment. You can find it in the target directory, as jsf-ajax-component-1.0.0.war. Just copy the WAR file into Tomcat's webapp directory, and browse to .

The best of both worlds

The AjaxComponent strategy described here is very effective for use in production components. Using this technique, you can add Ajax capability to JSF components relatively easily, and keep all of your input/output logic nicely packaged in the renderer. Once the phase listener is set up, it's very easy to add more components that use the strategy -- in fact, you don't even have to touch the listener.

If you find that putting the request handling logic into the component itself doesn't really make sense, then you should probably use the JSF AjaxCommand strategy instead. You can even use the same phase listener. With the two strategies in place, you are ready to handle any kind of Ajax request from your JSF application.

A good guideline for choosing between these two strategies is to ask yourself whether the functionality you are providing will be required by just one component, or many. If the answer is many, then your application is a good candidate for an AjaxCommand, since you will then be able to share the functionality across all the components without duplicating the code. Otherwise, go with the AjaxComponent, and keep the code tight and encapsulated.

As always, let me know your experience and thoughts. Join the discussion below to let me know what you think!

Author Bio

Matt Tyson has been building Java Web applications for 10 years. He runs a software consultancy called Odyssey Design and Development, Inc.

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/71047/viewspace-996757/,如需轉載,請註明出處,否則將追究法律責任。

The AjaxComponent strategy for JSF: The best of both worlds
請登入後發表評論 登入
全部評論

相關文章