Propagating Client-Side View State

Realtime Validation

As we saw in "Form Completion" on page 534, you can implement basic Ajax in a JSF application with little more than a servlet and a few lines of JavaScript. In this example, we will implement realtime validation, which will give us the opportunity to explore some of the more complex interactions between Ajax and JSF.

The application shown in Figure 11-3 performs realtime validation. When the user enters the fifth character of the zip code, we execute an Ajax request to validate those five digits.

Figure 11-3. Realtime validation


On the server, we obtain a reference to the component that represents the zip code field, and iterate over that component's validators, invoking each in turn. If any of the validators attached to the component throws an exception, we catch the exception, write an error to the response, and terminate the request.

Subsequently, back on the client, we display that error response in a DIV. That scenario is shown in Figure 11-3, where the user has entered an invalid zip code.

Here is the pertinent JSP:

  <html>      <f:view>         <body>            ...            <p><div  ></div></p>            <h:form >               <h:panelGrid columns="2">                   ...                   <h:inputText                            size="5"                           value="#{bb.zip}"                           onfocus="clearCityAndStateFields();"                           onblur="zipBlurred(this.value);"                           onkeyup="zipChanged(this.value);">                        <f:validator                           validator/>                     </h:inputText>                     ...                  </h:panelGrid>               </h:form>           </body>       </f:view>   </html>     

We have wired the onkeyup event for our zip code field to a function called zipChanged, which we discuss momentarily. We have also added a custom validator to the zip code component. That validator recognizes only the 97402 zip code; all other zip codes are considered invalid. Finally, we have added our errors DIV, which is initially empty.

This realtime validation example is more complex than the application discussed in "Form Completion" on page 534 because realtime validation forces us to access view state, meaning the component tree, for the JSP page.

Remember that we need to get a reference to the zip code component and iterate over its validators. To do that, we need access to the view state, which is only available to POST requests. Because of that restriction, we issue a POST request:

  <script type="text/javascript" language="Javascript1.1">   <!--   function zipChanged(zip) {      if(zip.length != 5) {         hideErrorsDiv();         return;      }      new Ajax.Request(window.document.forms[0].action,         {      method: "post",            parameters: "ajax=true&zip=" + zip,            onComplete: processRealtimeValidation      });   }

Notice the URL that we are using for this request: window.document.forms[0].action. That URL represents the action for the lone form in the page; in effect, we are tricking JSF into thinking that the user submitted the form. We are doing that because we want to access the view state for the current page, which JSF makes available after the Restore View phase of the life cycle when the form is submitted.

The preceding code shows how we invoke the URL from the client, but how do we handle the URL on the server? In this case, because we need access to view state, we cannot use a servlet. Servlets know nothing about JSF, so they cannot access view state. Instead, we use a phase listener and handle the request after the Restore View phase, when JSF has restored the component tree for us. Figure 11-4 shows the JSF life cycle when our phase listener handles the request.

Figure 11-4. The JSF life cycle with an Ajax phase listener


After the Restore View phase, JSF invokes our phase listener, which checks to see if this is an Ajax request, signified by a request parameter; if so, the phase listener validates the component, generates a response, and short-circuits the life cycle. Here is a truncated listing of that phase listener:

       public class AjaxPhaseListener implements PhaseListener {            ...            public PhaseId getPhaseId() {                return PhaseId.RESTORE_VIEW; // interested in RESTORE VIEW phase only            }            public void beforePhase(PhaseEvent phaseEvent {                 // not interested because view state hasn't been restored yet            }        public void afterPhase(PhaseEvent phaseEvent) {           FacesContext   fc = FacesContext.getCurrentInstance();           Map requestParams = fc.getExternalContext()                              .getRequestParameterMap();           String ajaxParam = (String)requestParams.get("ajax");           if("true".equals(ajaxParam)) {               // Get a reference to the zip code component               ...               if(zip != null) {                   // Get the servlet response and the zip code                   // component's validators                   ...                   for (int i = 0; i < validators.length; i++) {                      // Invoke each validator and catch exceptions                   }               }               fc.responseComplete(); // that's all for this response           }       }   }     

The preceding code fragment leaves out the majority of the details of the method. Here, we emphasize that the phase listener goes into action after the Restore View phase of the life cycle, and after JSF has prepped the view state for us. If the current request is an Ajax request signified by a request parameter named ajax whose value must be true the phase listener locates the zip code component and its validators, iterates over the validators, and generates a response. Finally, it calls the responseComplete method on the Faces context to short-circuit the rest of the life cycle.

Notice that the phase listener only short-circuits the life cycle when we explicitly qualify a request as an Ajax request. That qualification is necessary because we must distinguish between Ajax requests and non-Ajax requests: We cannot short-circuit the life cycle on every request, or our application would not work at all.

Finally, back on the client, we process the response:

   function processRealtimeValidation(xhr) {       var rt = xhr.responseText;       if(xhr.responseText == "okay") {          hideErrorsDiv();       }       else {          showErrorsDiv(rt);       }    }

If the response text is okay, we know the zip code was valid, so we hide the errors DIV. If the response text is anything other than okay, we display the response text in the errors DIV.

javax.faces.context.FacesContext

  • void responseComplete()

    Forces JSF to short-circuit the life cycle. After this method is called, JSF will not execute the next phase in the life cycle.


javax.faces.event.EditableValueHolder

  • Validator[] getValidators()

    Returns an array of validators associated with an input component. All input components implement the EditableValueHolder interface.


Note

POST requests are meant to change state on the server, whereas GET requests are for obtaining information only. Because of that distinction, JSF makes view state available to only to POST requests, which allows modifications to view state. If you're implementing functionality that requires access to view state, then you must send a POST request.



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