Chapter 12. Open Source

Ajax4jsf

Now that we have discussed the particulars of both implementing and encapsulating Ajax with JSF, we turn our attention to a framework that takes care of many of those details for you: Ajax4jsf.

Ajax4jsf is a java.net project, whose homepage (https://ajax4jsf.dev.java.net/ajax/ajax-jsf) is shown in Figure 11-10. Ajax4jsf provides 18 handy JSP tags that you can use to seamlessly integrate Ajax into your JSF applications. You can find a list of all the tags and their corresponding descriptions at the Ajax4jsf home-page. In our brief exploration of Ajax4jsf, we will discuss two of those tags: a4j:support, which lets you attach Ajax functionality to a component, typically an input component; and a4j:status, which renders JSF components at the start and end of each Ajax4jsf Ajax call.

Figure 11-10. The Ajax4jsf homepage


To illustrate both the power and pitfalls of using Ajax4jsf, we revisit the form completion and realtime validation examples from earlier in this chapter.

Implementing Form Completion with Ajax4jsf

Figure 11-11 and Figure 11-12 show the form completion example implemented with Ajax4jsf. The top picture in Figure 11-11 shows the result of entering an unrecognized zip code (the only zip code the application recognizes is 97402), and subsequently tabbing out of the zip code field. In that case, we use Ajax4jsf to make an Ajax call that determines that 97401 is unrecognized and updates the city and state text fields accordingly.

The bottom picture in Figure 11-11 shows the result of shift-tabbing back into the zip code field after entering an unrecognized zip code. In that case, we clear out the city and state fields in preparation for a new zip code entry, again by using Ajax4jsf to make an Ajax call.

Figure 11-11. Entering an unrecognized zip code


Figure 11-12. Entering a valid zip code


Figure 11-12 shows the result when the user enters the single zip code recognized by the application. In that case, we populate the city and state text fields with the values corresponding to that zip code. Once again, those fields are populated with an Ajax call made by Ajax4jsf.

Here is the JSP page shown in Figure 11-11 and Figure 11-12:

  <html>      ...      <%@ taglib uri="https://ajax4jsf.dev.java.net/ajax" prefix="a4j" %>      <f:view>         <f:loadBundle basename="com.corejsf.messages" var="msgs"/>         ...            <h:form >               ...                  <h:panelGrid columns="2">                  ...                  <h:inputText                         size="5"                        value="#{bb.zip}">                     <a4j:support event="onfocus"                        actionListener="#{bb.clearCityAndState}"                        reRender="city, state"/>                     <a4j:support event="onblur"                        actionListener="#{bb.setCityAndStateForZip}"                        reRender="city, state"                        oncomplete="Fat.fade_element('form:city');                        Fat.fade_element('form:state');"/>                  </h:inputText>                  <h:outputLabel for="city"                        value="#{msgs.cityPrompt}"/>                  <h:inputText                         size="25"                        value="#{bb.city}"/>                  <h:outputLabel for="state"                        value="#{msgs.statePrompt}"/>                  <h:inputText                         size="15"                        value="#{bb.state}"/>                    ...                </h:panelGrid>             </h:form>          </body>      </f:view>   </html>     

The preceding JSP page uses two a4j:support tags to implement the Ajax calls. Both tags are enclosed in the zip code input component; one of those tags is triggered by an onblur event, whereas the other is triggered by an onFocus event.

When the zip code field receives focus, Ajax4jsf makes an Ajax call to the server, which invokes the JSF life cycle. Ajax4jsf intervenes in the life cycle and adds an action listener to the call. That action listener is our backing bean's setCityAndStateForZip method.

When the zip code field loses focus, Ajax4jsf makes another Ajax call to the server, which again invokes the JSF life cycle, and once again Ajax4jsf adds an action listener to the call. That action listener is our backing bean's clearCityAnd-State method. The backing bean's implementation is shown below:

   package com.corejsf;    import javax.faces.event.ActionEvent;    public class BackingBean {        private String zip;        private String city;        private String state;        public String getState() { return state; }        public void setState(String state) { this.state = state; }        public String getCity() { return city; }        public void setCity(String city) { this.city = city; }        public String getZip() { return zip; }        public void setZip(String zip) { this.zip = zip; }        public void clearCityAndState(ActionEvent e) {            setCity("");            setState("");        }        public void setCityAndStateForZip (ActionEvent e) {            if (zip.equals("97402")) {                setCity("Eugene");                setState("Oregon");            }            else if(zip.equals("80132")) {                setCity("Monument");                setState("Colorado");            }            else {                setCity("NO DATA");                setState("NO DATA");            }        }    }     

Notice the reRender attributes for the a4j:support tags. When the Ajax calls initiated by Ajax4jsf return, Ajax4jsf rerenders only the city and state fields, which causes those fields to fetch their appropriate values from our backing bean. That is the fundamental magic of Ajax4jsf it can rerender part of a JSF tree of components instead of the entire tree.

Because Ajax4jsf rerenders the city and state fields, all our backing bean has to do is make sure that the city and state properties, to which the fields are wired, have been appropriately updated.

Implementing Realtime Validation with Ajax4jsf

In the preceding example, we saw that Ajax4jsf can be easy and quite natural to use for JSF developers. In fact, the last example was so simple that you might be wondering why we took the time to show you how to do the same thing by hand, and why we have gone to such great lengths in this chapter to illustrate the low-level details of implementing Ajax from scratch. Why not just start with Ajax4jsf and leave out the rest of that stuff, if you will never need to know those low-level details?

The truth is that although Ajax4jsf can be drop-dead simple to use for simple applications, you must understand the low-level details of using Ajax with JSF for more complex applications to use Ajax4jsf effectively. In fact, our realtime validation example that we discussed earlier in this chapter, which is just a conceptual step above the form completion example, is complex enough to throw a monkey wrench into using Ajax4jsf. In that case, you must have a solid understanding of the JSF life cycle and how Ajax works with JSF. So now we take a look at the realtime validation example implemented with Ajax4jsf and, more imporantly, divulge the monkey wrench.

Figure 11-13, Figure 11-14, and Figure 11-15 all show the Ajax4jsf version of our realtime validation example in action.

Figure 11-13. Leaving the zip code field blank


Figure 11-14. Entering the wrong number of characters for a zip code


Figure 11-15. Using five digits for the zip code


Figure 11-13 shows what happens when you leave the zip code field blank and tab out of the field. Notice that we display a busy indicator during the Ajax call, and subsequently hide it when the call is complete. The zip code field is a required field. So the Ajax call generates an appropriate error message, which we display using an h:outputText tag that shows an errorMessage property contained in our backing bean. As you will see momentarily, we set that errorMessage property when validation fails as a result of the Ajax call.

We have also attached a length validator to the zip code field, which requires the user to enter exactly five characters in the field. Once again, we use Ajax4jsf to make an Ajax call to validate the field and display an appropriate error message when validation fails, as shown in Figure 11-14.

We have also attached a custom validator to the zip code field that checks to see whether the zip code the user entered is the only zip code field that the application recognizes: 97402. Figure 11-15 shows the consequence of violating that constraint, which is yet another error message that lets the poor user know that the zip code was invalid, but provides no clue as to what the only valid zip code is. We trust that if you ever implement such a zip code field that you will be more conscientious than we have been in that regard.

Finally, if the user enters the correct zip code, he is rewarded with city and state fields that are populated with the appropriate values for the 97402 zip code, as was the case in the form completion example.

Now that we have seen how the example works, we look at how it is implemented. Here is the JSP page:

  <html>      ...     <%@ taglib uri="https://ajax4jsf.dev.java.net/ajax" prefix="a4j" %>     <f:view locale="en">        <f:loadBundle basename="com.corejsf.messages" var="msgs"/>        ...        <body>            ...            <h:panelGrid columns="2" style>               <a4j:status >                  <f:facet name="start">                     <h:graphicImage value="/indicator_radar.gif"/>                  </f:facet>               </a4j:status>                ...            </h:panelGrid>            <h:form >                <h:panelGrid columns="2">                    ...                    <h:panelGroup>                        <h:inputText                                    size="5"                                  value="#{bb.zip}"                               required="true">                           <!-- Add two validators: one for length and                                 another for our custom zip code                                 validator -->                           <f:validateLength minimum="5"                                             maximum="5"/>                           <f:validator                               validator/>                           <!-- The a4j:support tag must be immediate;                                otherwise, if validation fails, JSF will                                skip action listeners and go directly to                                the Render Response phase to rerender                                the current view. -->                           <a4j:support event="onblur"                                    immediate="true"                               actionListener="#{bb.validateZip}"                                     reRender="city, state, errorMessage"/>                        </h:inputText>                        <h:outputText                                    value="#{bb.errorMessage}"                                   style="color: red; font-style: italic;"/>                    </h:panelGroup>                    <h:outputLabel for="city"                               value="#{msgs.cityPrompt}"/>                    <h:inputText  binding="#{bb.cityInput}"                             size="25"                            value="#{bb.city}"/>                    <h:outputLabel for="state"                               value="#{msgs.statePrompt}"/>                    <h:inputText  binding="#{bb.stateInput}"                             size="15"                            value="#{bb.state}"/>                    ...                    </h:panelGroup>               </h:panelGrid>           </h:form>          </body>      </f:view>   </html>     

The first thing to notice is that we have used the a4j:status tag to display the busy indicator. You can specify start and stop facets for that tag, and when any Ajax call initiated by Ajax4jsf occurs, Ajax4jsf will display the start facet's component. When the call completes, Ajax4jsf will display the stop facet's component.

In our case, we did not specify a stop facet, and so Ajax4jsf will simply remove the start facet's component from the page when the call is complete.

Second, notice that the zip code field is required, and that we have added a length validator and our custom validator to that input component.

Third and here is part one of the monkey wrench is the fact the we have made the a4j:support tag inside the zip code component immediate. If we neglect to do that, our example will no longer work. To find out why, other than reading the comment in the preceding JSP page, we take a look at our backing bean's implementation:

  package com.corejsf;   import javax.faces.event.ActionEvent;   import javax.faces.context.FacesContext;   import javax.faces.component.UIInput;   import javax.faces.application.FacesMessage;   import java.util.Iterator;   public class BackingBean {       private String zip;       private String city;       private String state;       private String errorMessage = null;       public String getState() { return state; }       public void setState(String state) { this.state = state; }       public String getCity() { return city; }       public void setCity(String city) { this.city = city; }       public String getZip() { return zip; }       public void setZip(String zip) { this.zip = zip; }       // Error message setter & getter, plus a clear error msg method       public String getErrorMessage() {           return errorMessage;       }       public void setErrorMessage(String errorMessage) {           this.errorMessage = errorMessage;       }       public void clearErrorMessage(ActionEvent e) {           errorMessage = null;       }       // AJAX       /* cityInput and stateInput are set by component        * bindings in the view:        *        * <h:inputText  binding="#{bb.cityInput}"        *            size="25"        *           value="#{bb.city}"/>        *        * <h:inputText  binding="#{bb.stateInput}"        *            size="15"        *           value="#{bb.state}"/>        *        */       private UIInput cityInput = null; // cityInput       public UIInput getCityInput() {           return cityInput;       }       public void setCityInput(UIInput cityInput) {           this.cityInput = cityInput;       }       private UIInput stateInput = null; // stateInput       public UIInput getStateInput() {           return stateInput;       }       public void setStateInput(UIInput stateInput) {           this.stateInput = stateInput;       }       /* validateZip is called by Ajax4jsf in response to        * an onblur in the zip code textfield:        *        * <a4j:support event="onblur"        *          immediate="true"        *     actionListener="#{bb.validateZip}"        *           reRender="city, state, errorMessage"/>       */       public void validateZip(ActionEvent e) {           // This executes too fast to see the busy indicator in           // the view, so we slow it down           try {               Thread.sleep(250);           }           catch (InterruptedException e1) {               e1.printStackTrace();           }           UIInput input = (UIInput)e.getComponent() // Ajax4jsf comp                                     .getParent();   // input comp           if (input != null) {               String zip = (String)input.getSubmittedValue();               if (zip != null) {                   // set city and state properties according                   // to the zip field's submitted value                   setCityAndState(zip);                   // validate the zip field                   FacesContext fc = FacesContext.getCurrentInstance();                   input.validate(fc);  // iterates over input's validators                   if ( ! input.isValid())                       setErrorMessage(fc, input);                   // Force JSF to refresh city and state input fields.                   // If an input component's submitted value is not null,                   // JSF will not refresh the field.                   cityInput.setSubmittedValue(null);                   stateInput.setSubmittedValue(null);                }            }        }        private void setErrorMessage(FacesContext fc, UIInput input) {            // set errorMessage to the first message for the zip field           Iterator it = fc.getMessages(input.getClientId(fc));           if (it.hasNext()) {              FacesMessage facesMessage = (FacesMessage)it.next();              errorMessage = facesMessage.getSummary();           }       }       private void setCityAndState(String zip) {           String cityAndState = ZipcodeDatabase.getCityAndState(zip);           if (cityAndState != null) { // zip recognized               String[] cityStateArray = cityAndState.split(",");               setCity(cityStateArray[0]);               setState(cityStateArray[1]);           }           else { // unknown zip with 5 chars               if (zip.length() == 5) {                   setCity ("no data for " + zip);                   setState("no data for " + zip);               }               else { // unknown zip without 5 chars                   setCity(null);                   setState(null);               }           }       }   }     

Remember that the a4j:support tag adds an action listener to the JSF life cycle. That is pretty evident considering that the name of the tag's corresponding attribute is actionListener. But the consequences of that listener may not be so readily apparent.

As the Ajax call goes through the JSF life cycle, JSF will validate our zip code component about halfway through the life cycle in the Process Validations phase. If validation fails, JSF immediately proceeds to the Render Response phase of the life cycle to redisplay the page, which is standard JSF behavior whenever validation fails. Realize that JSF itself has no idea that this is an Ajax call, so it cycles through the life cycle as though we had submitted the form in which the zip code component is contained.

If validation fails in the Process Validations phase and JSF proceeds to the Render Response phase as a result, it will skip any action listeners associated with the call, because action listeners are invoked just before the Render Response phase. So, we make the action listener immediate, which means JSF will invoke the listener at the front of the life cycle (after the Apply Request Values phase), and subsequently proceed to the Render Response phase. Therefore, if we don't make our action listener immediate, by specifying immediate=true for our a4j:status component, our action listener will never be invoked.

Now for part two of the monkey wrench, which is more insidious than part one. Notice that our action listener invokes the validate method on our zip code component. If the component is invalid after that call, we know that validation failed, and we set the errorMessage property of the backing bean accordingly, which is what we display in the view.

To recap so far: We use the a4j:support tag to add an action listener to the JSF life cycle when Ajax4jsf makes its Ajax call after we leave the zip code field. Our action listener manually validates the input field, and it must be immediate to keep JSF from performing validation and skipping our listener when validation fails.

One more part to the monkey wrench puzzle: When JSF performs validation, and validation succeeds for all input components, JSF sets all the input component's submitted values (which is what the user typed into the fields) to null. On the other hand, if validation fails for one or more inputs, JSF leaves the submitted values as they were.

When JSF subsequently rerenders the page, either because validation failed or because there was no navigation specified for the form submit, JSF checks the input components to see if their submitted values are null; if so, it refetches the component's values. However, if the submitted values are not null, meaning that validation failed, JSF does not repopulate the fields by fetching their associated values; instead, it redisplays those submitted values. That is how JSF retains erroneous values in input components when validation fails, which is desired behavior.

Because we are invoking validation manually, our input component's submitted values are never set to null when validation succeeds, as would be the case if JSF were invoking validation. So we must do that manually for the city and state fields to display Eugene and Oregon, respectively, when validation succeeds. To do that, we use component bindings and call setSubmittedValue(null) on the city and state components.

This exercise should convince you that using Ajax4jsf is not as simple as our form completion with Ajax4jsf example in the preceding section might lead you to believe. You must have a solid grasp of the JSF life cycle and what JSF does during that life cycle. For that, you will probably need a good book on JSF, and thankfully, you have one. You must also understand how Ajax4jsf implements Ajax. For example, Ajax4jsf's a4j:support tag does not pull the rug out from under JSF by invoking responseComplete on the Faces context, as we did early on in this chapter. Instead, the life cycle proceeds as it normally would with the addition of an action listener, and it is vital that you understand the ramifications of that design choice.

For completeness, we offer listings of our custom zip code validator, and a zip code database used by our backing bean. Here is the validator:

  package com.corejsf;   import com.corejsf.util.Messages;   import javax.faces.context.FacesContext;   import javax.faces.component.UIComponent;   import javax.faces.validator.ValidatorException;   import javax.faces.validator.Validator;   public class ZipcodeValidator implements Validator {       public void validate(FacesContext fc,                            UIComponent c,                            Object zip)           throws ValidatorException {           String cityAndState = ZipcodeDatabase                                 .getCityAndState((String)zip);           if (cityAndState == null) {               throw new ValidatorException(                   Messages.getMessage("com.corejsf.messages",                                       "badZip"));           }       }   }

And here is the database:

  package com.corejsf;   public class ZipcodeDatabase {       public static String getCityAndState(String zip) {           if ("97402".equals(zip))               return "Eugene,Oregon";           else               return null;       }   }



Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net