wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

4 Validation errors to avoid and some fun with XPages Validation


Input validation is a tricky business (on any platform). On one hand you need to ensure that your form contains sufficient and accurate data to be processes as intended. On the other hand validation needs to get out of the user's attempts to get things done. XPages offers client and serverside validation. While you could continue to use onSubmit or QuerySave the recommended approach is the use of validators. They nicely separate the validation logic from the validation trigger and from the error display. This gives you a great deal of flexibility. You can go to the extend to provide your own Java validators that can be used by you application developers without knowing Java, add your own message programmatically or let your code raise an error. Per summarised error management nicely and Andre explains better SSJS validation rules. So we are pretty much covered. Nevertheless I see quite some worst-practises in action:
  1. Only client side validation: Client side validation is for the comfort of your user. It is to make her life easier. Client side validation is not suitable for preserving data integrity! Using Firebug, NoScript, cURL or simply notepad/textedit/gedit (to create a simple html form with method="post") a user can bypass any client side validation. So it must be accompanied with server side validation code (unless of course all your users are angels)
  2. Abrasive error messages: Sounds familiar: "Input error: This is not a phone number", "You must fill xxx", "Wrong character detected"? Users translate (conscious or subconscious) those messages into "You stupid moron, you not even can fill in the form - serve me, I'm your digital overlord". Guess what happens next: an eMail, a phone call and application avoidance. The better way is to a) be more liberal with what you accept (phone number can contain spaces, plus, minus and slash - and how hard is it to filter out spaces from a credit card number? b) Let the computer apologise for its short comings: "Sorry you need to complete all indicated fields before this application can process your request" or (if you buy into the idea that users attribute personality to computers and applications): "Sorry I only understand credit card numbers that have no spaces"
  3. Lack of save as draft: If your form has very few fields only, you can get away without. But any modest complex application will get a user to a point where he is stuck with a missing piece of information (or a weekend (s)he wants to start). So you need to accept input however incomplete it might be, just don't process it yet. Since an eMail system on the market has a "draft mode" your users will be both familiar with it and actually expect a save as draft function
  4. Validation traps (I'm not talking about a Catch 22): fields are validated on field exit and on failure the user is prompted and send back to that field. While it might be well meaning (make sure everything is fine) is is actually evil. It disrupts the user in her workflow and forces a certain working style on them. For a better way to use onBlur see below
To summarise:
Validation always must be server side, it should be augmented with client side validation for the comfort of the user and in XPages should be implemented using Validators.
( Should in the context of this statement means: " Do this unless you have 7 good reasons to do it differently".)
Does that mean that you have to code every validation twice? The short answer: No if you use Validators. I imagined a modification of the XPages engine, where you could trigger a validation onBlur of a field that would only hint the user about the problem (so NO messagebox prompt) and not require to duplicate code that you use in your validator. In a chat with Máire Kehoe she enlightened me how this can be done in XPages, so I present the ValidationHelperControlfor continious validation:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.beforePageLoad>
// Limitation: must be added to the XPage after the input controls.

var inputIds:java.util.ArrayList = new java.util.ArrayList();
var root = view;
addValidatedInputIds(inputIds, root);

compositeData.inputIds = inputIds;
// refresh the entire view, instead of just the input control,
// so that the Display Error controls are refreshed too.
compositeData.refreshId = "surroundPanel";
return;

function addValidatedInputIds(inputIds, control){
   if( control instanceof javax.faces.component.UIInput ){
       if( control.isRequired() || control.getValidators().length > 0 || null != control.getValidator() ){
           inputIds.add(control.getId());
       }
   }
   if( control.getChildCount() > 0 ){
        var kids = control.getChildren();
        var kid;
        for(var i = kids.iterator(); i.hasNext();){
            kid = i.next();
            addValidatedInputIds(inputIds, kid);
        }
   }
   if( control.getFacetCount() > 0 ){
       var facets = control.getFacets();
       for(var j = facets.values().iterator(); j.hasNext();){
            var facet = j.next();
            addValidatedInputIds(inputIds, facet);
       }
   }
}}></xp:this.beforePageLoad>
    <xp:this.afterPageLoad><![CDATA[#{javascript:
if( compositeData.inputIds.size() > 0 ){
    var inputIds = compositeData.inputIds;
    var repeat1 = getComponent("repeat1");
    var repeatKids = repeat1.getChildren();
    for(var i = 0; i < inputIds.size(); i++){
        var inputId = inputIds.get(i);
        var inputControl = getComponent(inputId);
        if( null != inputControl ){
           
            var repeatContainer = repeatKids.get(i);
            var eventHandler = repeatContainer.getChildren().get(0);
            inputControl.getChildren().add(eventHandler);
        }
    }
}
}]]></xp:this.afterPageLoad>
    <xp:panel rendered="false">
        <xp:label id="label1" value="modifyValidationBehavior"></xp:label>
   
        <xp:br></xp:br>
        <xp:repeat id="repeat1" repeatControls="true"
            value="#{javascript:compositeData.inputIds.size()}">
            <xp:eventHandler event="onblur" submit="true"
                refreshMode="partial" refreshId="${compositeData.refreshId}" execMode="partial">
            </xp:eventHandler>
        </xp:repeat>
    </xp:panel>
</xp:view>
 
It is a first rough cut and needs some refinement. Time permitting I will create a sample database. To test it, just add it to the bottom of a page where you have controls that require server side validation. These fields now will - without a change to them- also validate on field exit without forcing the user back. Curser focus needs some attention and the visual feedback can be enhanced.Nevertheless the example clearly shows the flexibility of separating validation rules from validation triggers. A validator only defines the what a.k.a the rule and the message, it doesn't define the when or how (to display), thus alowing you to tweak them to your needs.
As usual YMMV.

Posted by on 02 May 2011 | Comments (0) | categories: XPages

Comments

  1. No comments yet, be the first to comment