Client Validation Implementation

Implementation details:

Server Side

      • ClientValidatorBehavior
        • getScript method checks for proper component type. For EditableValueHolder it delegates call to the ClientValidatorRenderer, ActionSource uses FormClientValidator. In all other cases, FacesException should be thrown.
        • (Server Side pp 2 ) findMessages should find all UIMessage components which points to target UIInput component, and all UIMessages included in view. If no message components found, it should throw MessageNotFoundException. This is optional check that should be performent in "DEVELOPMENT" stage only. Postpone implementation to M5 or late.
        • ( ServerSide pp 3 ) getConverter should determine Converter instance used in target UIInput component. If it cannot find appropriate converter, it should throw ConverterNotFoundException. If Converter found, it returns ConverterDescription object created by ConverterService.
        • ( Server Side pp 4 ) getValidators should find all JSF and JSR-303 validators associated with target UIInput and return collections of their descriptors. If no validators there, it returns empty collection. For JSR-303 validators, it uses BeanValidatorUtils class, available as service in ServiceTracker.
        • Behavior should wrap AjaxBehavior to generate AJAX call script and process decode phase. ClientValidatorRenderer gets script for AJAX call by getAjaxScript method.
        • decode method delegates call to wrapped AjaxBehavior. If AJAX request detected by later, modifies it to render response after PROCESS_VALIDATORS phase, and also add to "render" list all UIMessage components that pointed to parent input.
      • ClientValidatorRenderer getScript method:
        • performs arguments check ( not null, behavior is instance of ClientValidatorBehavior.
        • get Converter instance from behavior by getConverter method, then use ClientScriptLookupService to find client-side JavaScript. Both ConverterNotFoundException and ScriptNotFoundException enforce AJAX callback rendering unless input component has no validators.
        • If Converter script found, get Collection of validators from behavior. If collection is empty, return null instead of script. Otherwise, loop through all validators to construct client-side validator function, using ClientScriptLookupService. Record number of validators that cannot be performed on client.
        • If there is no Converter script or at least one validator has no client-side version, add AJAX callback to client-side validator.
        • Find or Create UIValidatorScript in UIView.viewResources associated with "form" target. Create ScriptObject that will be used to build validator call. If UIValidatorScript already contains ScriptObject with same signature use its function name for page size optimization.  Problem: JSF 2.0 doesn't like components tree changes, and specification does not allow dynamic including of script libraries. At least, it seems not possible to change controls with client-side validation by AJAX. Solution
        • Return JavaScript code that calls function build by ScriptObject.
      • UIValidatorScript
        • Holds Set of per-field validator functions ( ScriptObject instances ).
        • encodeAll method:
          • encode html <script> element for all libraries used by validators/converters.
          • Sncode JavaScript that registers localised messages ( key -> Message object ).
          • encode all ScriptObject as <script> element with validator function definition.
          • Encode form-wide function for ActionSource components. That function should call all per-field validation functions.
          • Encode MessageDispatcher JavaScript object used to delivery messages from validators.
      • ScriptObject creates JavaScript function definition that performs per-field validation. Attributes:
        • functionName.
        • description for converter function call, with parameters and messages.
        • Collection of description for validator functions, with actual parameters and messages.
        • AJAX callback code.
        • implements equals() method so all validators with same functionality would use single function.
      • ScriptBuilder takes ScriptObject collection and builds client-side JavaScript code.
      • ClientScriptService looks for client-side version of Java class ( for Converter or Validator )
        • getScript method gets Java class for JSF Validator or JSR-303 annotation and returns instance of ClientScript interface that describes JavaScript resource and function name. First ( minimal ) implementation:
          • Tries to get resource with the same library as the Java package name and the same name as Java class has, but with ".js" extension. If such resource exists, use it with default function name ( simple class name with first character lowercase eg Max -> max() ).
          • read all META-INF/csv.xml resources and look up for given class name. The format of these files is:
        • <?xml version="1.0" encoding="UTF-8"?>

        • <scripts>

        •    <component>

        •        <type>java.lang.String</type>

        •        <library>org.richfaces</library>

        •        <resource>csv.js</resource>

        •        <function>stringConverter</function>

        •    </component>

        •    <component>

        •        <type>java.lang.Integer</type>

        •        <library>org.richfaces</library>

        •        <resource>csv.js</resource>

        •        <function>intConverter</function>

        •    </component>

        • </scripts>

          • finally, use Java Annotations on custom validators or BeanValidator ...
          • Throws ScriptNotFoundException otherwise.
      • ConverterService provides methods to extract information from Converter instance:
        • extracts appropriate messages for conversation error.
        • detects converter options.
      • BeanValidatorService provides services for JSR-303 validators:
        • Determines JSR-303 validators from EL-expression of UIInput "value" attribute.
        • Validates EL-expressions using constrains declared in model.
        • Validates objects
        • Creates ValidatorDescriptor for actual annotations, with optional parameters and message for current locale.
      • FacesValidatorService
        • Creates ValidatorDescriptor for actual validator instance and faces locale.
    • Client Side
      • Behavior event function expects two parameters: "clientId"  "this" and "event" , there "this" represents base HTML element for component, and clientId is JSF client id for component. For input element value, it expects this.value attribute. Event parameter is necessary for jsf AJAX call.
      • Converter function expects two mandatory parameters: "value" and "message" and returns converted value. Optional parameters can be encoded as JavaScript hash: var convertedValue = convertInt(value,"Invalid number"{"length":3});
      • If conversion error happens, It should throw exception with appropriate message.
      • Validator function should use the same signature as Converter, but return nothing.
      • In the case of validation errors, validator should throw exception with appropriate message.
      • At the end of validation process, function should clear error messages or call AJAX, if necessary.
      • In the case of error, validation function updates Message components with messages from exception.
      • Form validation function calls all per-field validation methods but disables AJAX callback.
    <form ...>
    <input value="" onblur="v(this,event)" />
    <script src="/..../converters.js"/>
    <script src="/...../fooValidator.js"/>
    <script type="text/javascript">
    function v(clientId,element,event,disableAjax){
      try {
             var value = RichFaces.csv.getValue(clientId);
             // Generated on the fly
             var convertedValue = org.rf.numberConverter(value,"Invalid number");
             foo.bar.validate(convertedValue,"Value should be less or more {}",{"max":10});
             // ....
            // 
            if(disableAjax){
                 RichFaces.csv.clearMessage(clientId);
            } else {        // Generated by AJAX behavior.
                jsf.ajax.request(...);
            }          return true;
           } catch(e) {
               RichFaces.csv.sendMessage(clientId,e.message);
               return false;
           }
    }
    function validateForm() {
          return v("input",$("input"),true)&&v1("input1",$("input1"),true);
    }
    
    </script>
    
    • Tests.
      • Server side - separate tests for all public methods of server side components and services.
      • Client side: qUnit tests for all Converter and Validator implementations.