Programming

Validation

JSF has strong support for server-side validation of a single component. However, if you want to carry out client-side validation or to validate relationships among components, you are on your own. The following sections tell you how you can overcome these issues.

How Do I Write My Own Client-Side Validation Tag?

Suppose you have developed a JavaScript function for validation and tested it on multiple browsers. Now you would like to use it in your JSF applications. You need two tags:

  1. A validator tag that is attached to each component that requires validation.

  2. A component tag that generates the JavaScript code for validating all components on the form. The component tag must be added to the end of the form. Note that you cannot use a validator tag for this purpose. Only components can render output.

As an example, we show you how to make use of the credit card validation code in the Apache Commons Validator project. You can download the code from http://jakarta.apache.org/commons/validator.

We produce two tags: a creditCardValidator tag that can be added to any JSF input component and a component tag validatorScript that generates the required JavaScript code.

The creditCardValidator tag has two attributes. The message attribute specifies the error message template, such as

  {0} is not a valid credit card number

The arg attribute is the value that should be filled in for {0}, usually the field name. For example,

  <corejsf:creditCardValidator      message="#{msgs.invalidCard}" arg="#{msgs.primaryCard}"/>

The code for the validator is in Listing 13-24 on page 661. The validator class has two unrelated purposes: validation and error message formatting.

The class carries out a traditional server-side validation, independent of the client-side JavaScript code. After all, it is not a good idea to rely solely on client-side validation. Users may have deactivated JavaScript in their browsers. Also, automated scripts or web-savvy hackers may send unvalidated HTTP requests to your web application.

The getErrorMessage method formats an error message that will be included in the client-side JavaScript code. The error message is constructed from the message and arg attributes.

The validatorScript component is far more interesting (see Listing 13-25 on page 663). Its encodeBegin method calls the recursive findCreditCardValidators method, which walks the component tree, locates all components, enumerates their validators, checks which ones are credit card validators, and gathers them in a map object. The writeValidationFunctions method writes the JavaScript code that invokes the validation function on all fields with credit card validators.

You must place the validatorScript tag at the end of the form, like this:

  <h:form  onsubmit="return validatePaymentForm(this);">      ...      <corejsf:validatorScript functionName="validatePaymentForm"/>   </h:form>

Caution

If you place the validatorScript tag before the tags for the components that need to be validated, then the code that traverses the form component may not find the validators! This unintuitive and annoying behavior is a consequence of the JSP mechanism on which the JSF reference implementation is based. When a JSP-based implementation renders a JSF page for the first time, the component tree does not yet exist. Instead, it intermingles rendering and construction of the component tree.


Listing 13-26 on page 665 shows a sample JSF page. Figure 13-11 shows the error that is generated when a user tries to submit an invalid number.

Figure 13-11. Client-side credit card validation


The details of the writeValidationFunctions method depend on the intricacies of the JavaScript code in the Commons Validator project.

First, the writeValidationFunctions method produces the validation function that is called in the onsubmit handler of the form:

  var bCancel = false;   function functionName (form) { return bCancel || validateCreditCard(form); }     

If a form contains "Cancel" or "Back" buttons, their onclick handlers should set the bCancel variable to true, to bypass validation.

The validateCreditCard function is the entry point into the Commons Validator code. It expects to find a function named formName_creditCard that constructs a configuration object. The writeValidationFunctions method generates the code for the creditCard function.

Unfortunately, the details are rather convoluted. The formName_creditCard function returns an object with one instance field for each validated form element. Each instance field contains an array with three values: the ID of the form element, the error message to display when validation fails, and a validator-specific customization value. The credit card validator does not use this value; we supply the empty string.

The instance field names do not matter. In the writeValidationFunctions method, we take advantage of the flexibility of JavaScript and call the fields 0, 1, 2, and so on. For example,

  function paymentForm_creditCard() {      this[0] = new Array("paymentForm:primary",         "Primary Credit Card is not a valid card number", "");      this[1] = new Array("paymentForm:backup",         "Backup Credit Card is not a valid card number", "");   }

If you design your own JavaScript functions, you can provide a saner mechanism for bundling up the parameters.

Listing 13-24. clientside-validator/src/java/com/corejsf/CreditCardValidator.java

  1. package com.corejsf;   2.   3. import java.io.Serializable;   4. import java.text.MessageFormat;   5. import java.util.Locale;   6. import javax.faces.application.FacesMessage;   7. import javax.faces.component.UIComponent;   8. import javax.faces.context.FacesContext;   9. import javax.faces.validator.Validator;  10. import javax.faces.validator.ValidatorException;  11.  12. public class CreditCardValidator implements Validator, Serializable {  13.    private String message;  14.    private String arg;  15.  16.    // PROPERTY: message  17.    public void setMessage(String newValue) { message = newValue; }  18.  19.    // PROPERTY: arg  20.    public void setArg(String newValue) { arg = newValue; }  21.    public String getArg() { return arg; }  22.  23.    public void validate(FacesContext context, UIComponent component,  24.          Object value) {  25.       if(value == null) return;  26.       String cardNumber;  27.       if (value instanceof CreditCard)  28.          cardNumber = value.toString();  29.       else  30.          cardNumber = getDigitsOnly(value.toString());  31.       if(!luhnCheck(cardNumber)) {  32.          FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,  33.             getErrorMessage(value, context), null);  34.          throw new ValidatorException(message);  35.       }  36.    }  37.  38.    public String getErrorMessage(Object value, FacesContext context) {  39.       Object[] params = new Object[] { value };  40.       if (message == null)  41.          return com.corejsf.util.Messages.getString(  42.                "com.corejsf.messages", "badLuhnCheck", params);  43.       else {  44.          Locale locale = context.getViewRoot().getLocale();  45.          MessageFormat formatter = new MessageFormat(message, locale);  46.          return formatter.format(params);  47.       }  48.    }  49.  50.    private static boolean luhnCheck(String cardNumber) {  51.       int sum = 0;  52.  53.       for(int i = cardNumber.length() - 1; i >= 0; i -= 2) {  54.          sum += Integer.parseInt(cardNumber.substring(i, i + 1));  55.          if(i > 0) {  56.             int d = 2 * Integer.parseInt(cardNumber.substring(i - 1, i));  57.             if(d > 9) d -= 9;  58.             sum += d;  59.          }  60.       }  61.  62.       return sum % 10 == 0;  63.    }  64.  65.    private static String getDigitsOnly(String s) {  66.       StringBuilder digitsOnly = new StringBuilder ();  67.       char c;  68.       for(int i = 0; i < s.length (); i++) {  69.          c = s.charAt (i);  70.          if (Character.isDigit(c)) {  71.             digitsOnly.append(c);  72.          }  73.       }  74.       return digitsOnly.toString ();  75.    }  76. }     

Listing 13-25. clientside-validator/src/java/com/corejsf/UIValidatorScript.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Map;   5. import java.util.LinkedHashMap;   6. import javax.faces.component.EditableValueHolder;   7. import javax.faces.component.UIComponent;   8. import javax.faces.component.UIComponentBase;   9. import javax.faces.context.FacesContext;  10. import javax.faces.context.ResponseWriter;  11. import javax.faces.validator.Validator;  12.  13. public class UIValidatorScript extends UIComponentBase {  14.    private Map<String, Validator> validators  15.       = new LinkedHashMap<String, Validator>();  16.  17.    public String getRendererType() { return null; }  18.    public String getFamily() { return null; }  19.  20.    private void findCreditCardValidators(UIComponent c, FacesContext context) {  21.       if (c instanceof EditableValueHolder) {  22.          EditableValueHolder h = (EditableValueHolder) c;  23.          for (Validator v : h.getValidators()) {  24.             if (v instanceof CreditCardValidator) {  25.                String id = c.getClientId(context);  26.                validators.put(id, v);  27.             }  28.          }  29.       }  30.  31.       for (UIComponent child : c.getChildren())  32.          findCreditCardValidators(child, context);  33.    }  34.  35.    private void writeScriptStart(ResponseWriter writer) throws IOException {  36.       writer.startElement("script", this);  37.       writer.writeAttribute("type", "text/javascript", null);  38.       writer.writeAttribute("language", "Javascript1.1", null);  39.       writer.write("\n<!--\n");  40.    }  41.  42.    private void writeScriptEnd(ResponseWriter writer) throws IOException {  43.       writer.write("\n-->\n");  44.       writer.endElement("script");  45.    }  46.  47.    private void writeValidationFunctions(ResponseWriter writer,  48.       FacesContext context) throws IOException {  49.       writer.write("var bCancel = false;\n");  50.       writer.write("function " );  51.       writer.write(getAttributes().get("functionName").toString());  52.       writer.write("(form) { return bCancel || validateCreditCard(form); }\n");  53.  54.       writer.write("function ");  55.       String formId = getParent().getClientId(context);  56.       writer.write(formId);  57.       writer.write("_creditCard() { \n");  58.       // for each field validated by this type, add configuration object  59.       int k = 0;  60.       for (String id : validators.keySet()) {  61.          CreditCardValidator v = (CreditCardValidator) validators.get(id);  62.          writer.write("this[" + k + "] = ");  63.          k++;  64.  65.          writer.write("new Array('");  66.          writer.write(id);  67.          writer.write("', '");  68.          writer.write(v.getErrorMessage(v.getArg(), context));  69.          writer.write("', '');\n"); // Third element unused for credit card validator  70.       }  71.       writer.write("}\n");  72.    }  73.  74.    public void encodeBegin(FacesContext context) throws IOException {  75.       ResponseWriter writer = context.getResponseWriter();  76.  77.       validators.clear();  78.       findCreditCardValidators(context.getViewRoot(), context);  79.  80.       writeScriptStart(writer);  81.       writeValidationFunctions(writer, context);  82.       writeScriptEnd(writer);  83.    }  84. }     

Listing 13-26. clientside-validator/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.    <%@ taglib uri="http://corejsf.com/creditcard" prefix="corejsf" %>   5.    <f:view>   6.       <head>   7.          <link href="styles.css" rel="stylesheet" type="text/css"/>   8.          <script src="/books/2/24/1/html/2/scripts/validateCreditCard.js"   9.             type="text/javascript" language="JavaScript1.1">  10.          </script>  11.          <title><h:outputText value="#{msgs.title}"/></title>  12.       </head>  13.       <body>  14.          <h:form  onsubmit="return validatePaymentForm(this);">  15.             <h1><h:outputText value="#{msgs.enterPayment}"/></h1>  16.             <h:panelGrid columns="3">  17.                <h:outputText value="#{msgs.amount}"/>  18.                <h:inputText  value="#{payment.amount}">  19.                   <f:convertNumber minFractionDigits="2"/>  20.                </h:inputText>  21.                <h:message for="amount" style/>  22.  23.                <h:outputText value="#{msgs.creditCard}"/>  24.                <h:inputText  value="#{payment.card}" required="true">  25.                   <corejsf:creditCardValidator  26.                      message="#{msgs.unknownType}"arg="#{msgs.creditCard}"/>  27.                </h:inputText>  28.                <h:message for="card" style/>  29.  30.                <h:outputText value="#{msgs.expirationDate}"/>  31.                <h:inputText  value="#{payment.date}">  32.                   <f:convertDateTime pattern="MM/dd/yyyy"/>  33.                </h:inputText>  34.                <h:message for="date" style/>  35.             </h:panelGrid>  36.             <h:commandButton value="Process" action="process"/>  37.             <corejsf:validatorScript functionName="validatePaymentForm"/>  38.          </h:form>  39.       </body>  40.    </f:view>  41. </html>     

How Do I Use the Shale Validator for Client-Side Validation?

In the preceding section, you saw how to write your own validator tag that puts a validation script from the Commons Validator project to work. If you like that approach, there is no need to get busy and replicate that work for all of the other validators.

The Apache Shale project (http://struts.apache.org/struts-shale) has provided a custom tag library for this purpose. (We originally wrote that tag library for the first edition of this book and then contributed it to the Shale project.)

To use the Shale Validator, follow these instructions:

  1. Place the library files shale-core.jar and commons-validator.jar into the WEB-INF/lib directory of your web application, together with the libraries that are needed to support them. At the time of this writing, the following additional JAR files are required: commons.logging.jar, commons.digester.jar, commons-beanutils.jar, commons-collections.jar, and jakarta-oro.jar. To get the correct version of these files, download the shale-dependencies-date.zip file that matches your Shale distribution.

  2. Include a tag library declaration, such as

    <%@ taglib uri="http://struts.apache.org/shale/core" prefix="s" %>

    Here, we use s as a prefix. As always, you can use any prefix of your choice.

  3. Include a call to the validation method in the onsubmit handler of your form, like this:

    <h:form  onsubmit="return validatePaymentForm(this);">

    Just before the </h:form> tag, add an s:validatorScript tag:

    <s:validatorScript functionName="validatePaymentForm"/>

    The function name must match the function in the onsubmit handler.

  4. Add validators to your components, for example:

    <h:inputText  value="#{payment.amount}">    <s:commonsValidator type="floatRange"arg="#{msgs.amount}"          client="true" server="true">       <s:validatorVar name="min" value="10"/>       <s:validatorVar name="max" value="10000"/>    </s:commonsValidator> </h:inputText>

    The type argument is the validator type. It should be one of the types listed in Table 13-3. Supply the argument for the error message in the arg attribute. Typically, this is the name of the field.

    Table 13-3. Struts Client-Side Validators
    Validator Name Parameters Purpose
    required none Checks that the field has characters other than white space.
    maxlength maxlength Checks that the field length is at most the value of the maxlength parameter.
    minlength minlength Checks that the field length is at least the value of the minlength parameter.
    byte none Checks that the field contains an integer between 128 and 127.
    short none Checks that the field contains an integer between 32768 and 32767.
    integer none Checks that the field contains an integer between 2147483648 and 2147483647.
    float none Checks that the field contains a floating-point number.
    intRange min, max Checks that the field contains an integer between min and max. Both must be specified.
    floatRange min, max Checks that the field contains a floating-point number between min and max. Both must be specified.
    date datePatternStrict Checks that the field contains a date with the given pattern.
    email none Checks that the field contains a syntactically correct email address.
    creditCard none Checks that the field contains a credit card number that passes the Luhn check.
    mask mask Checks that the field matches the regular expression given in the mask parameter.


    Supply parameters min, max, minlength, maxlength, mask, and datePatternStrict as required by the validation method that you choose.

  5. If you want to tweak the validation behavior, or add new validator rules, read http://wiki.apache.org/struts/Shale/Validation.

Note

Unfortunately, the Commons Validator displays a pop-up when it finds a validation error. It would be nicer to place an error message next to the offending field. This feature is supported in Cagatay Civici's client-side validation package at http://jsf-comp.sourceforge.net/components/clientvalidators/index.html.


How Do I Validate Relationships Between Components?

In JSF, validators are intended to check the validity of a single component. However, you often need to test relationships between components. For example, it is common to ask users to reenter a password. You would like to show a validation error if the two passwords are not identical.

The sophisticated approach is to design a custom component that renders two input fields, one for the password and one for the confirmed password. That is quite elegant, but of course it is a lot of work.

The other approach is to let the last of the related components do the validation. The preceding components will have their local values set, and your validator code can read them. For the last component only, you need to use the value that was passed by the validation method.

The validation method is most conveniently placed in a backing bean that also holds the components of the form. For example,

  public class BackingBean   {      private UIInput passwordField;      private UIInput confirmField;      ...      public void validateConfirmField(FacesContext context, UIComponent component,         Object value) {         if (!passwordField.getLocalValue().equals(value))            throw new ValidatorException(...);   }     

You then attach the validator to the confirmation field:

  <h:inputText binding="#{bb.confirmField}" required="true"      validator="#{bb.validateConfirmField}"/>

For a more complete example of this technique, see Chapter 6.



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