Wednesday, June 8, 2011

Client-side validation with jQuery validation plugin. Advanced examples.

In the last post I covered the basics of the great jQuery validation plugin - state-of-the-art for client-side validation. I'm going to show now an advanced example how to write validation rules, own validation methods and put all together. I'm writing a collaborative whiteboard just now and needed two dialogs placed in the same HTML form. My task were:

1) Write a dialog to input a valid image URL, image width and height. This dialog looks as follows


and was written in JSF (PrimeFaces) as
<p:dialog id="dialogInputImage" header="Input image URL" resizable="false" closable="true" modal="true" styleClass="validatable">
    <ul id="errorImageUl" class="errormsg"/>
    <h:panelGrid id="dlgImageGrid" columns="2" columnClasses="...">
        <h:outputLabel value="Image URL" for="inputUrl"/>
        <p:inputText id="inputUrl"/>
        <h:outputLabel value="Image width (px)" for="imgWidth"/>
        <h:panelGroup>
            <p:inputText id="imgWidth" maxlength="4"/>
            <h:outputLabel value="Image height (px)" for="imgHeight" style="margin: 0 10px 0 10px;"/>
            <p:inputText id="imgHeight" maxlength="4"/>
        </h:panelGroup>
    </h:panelGrid>
</p:dialog>

2) Write a second dialog with input fields for whiteboard width and height. This dialog looks as follows


and was written in JSF (PrimeFaces) as
<p:dialog id="dialogResize" header="Resize Whiteboard" resizable="false" closable="true" modal="true" styleClass="validatable">
    <ul id="errorResizeUl" class="errormsg"/>
    <h:panelGrid id="dlgResizeGrid" columns="4" columnClasses="...">
        <h:outputLabel value="Whiteboard width (px)" for="wbWidth"/>
        <p:inputText id="wbWidth" maxlength="4"/>
        <h:outputLabel value="Whiteboard height (px)" for="wbHeight"/>
        <p:inputText id="wbHeight" maxlength="4"/>
    </h:panelGrid>
</p:dialog>

3) Validation requirements:

First dialog.
- If nothing was input (all fields are empty) ==> no validation for all fields.
- If the URL field is empty ==> no validation for all fields.
- If the URL field is not empty ==> check if the URL is valid and input image width / height are positive digits (greater than 0).

Second dialog.
- Always check input width and height which should be positive digits (greater than 0).

Furthermore, error messages should be groupped. I use "errorPlacement" option for this. We need an error container to play nice with "errorPlacement". Therefore, I defined an error container in each dialog by UL element which will contain LI elements if any errors occur.
 
<ul id="..." class="errormsg"/>
 
Let me show the JavaScript part now. Comments help to understand the logic behind my code. The code should be placed after all p:dialog tags
jQuery(function() {
    // add a new validation method to validate image width / height
    jQuery.validator.addMethod("imageSize", function(value, element, param) {
        // check parameter "#inputUrl:filled" (see validate(...) method below) 
        if (jQuery.find(param).length < 1) {
            return true;
        }

        // use built-in "digits" validator and check if digits are positive
        return !this.optional(element) && jQuery.validator.methods['digits'].call(this, value, element) && parseInt(value) > 0;
    }, "Please enter a valid image size (only positive digits are allowed).");

    // create an object with rules for convenience (using in validate(...))
    var dimensionRules = {
        required: true,
        digits: true,
        min: 1
    };

    // create a validator with rules for all dialog fields
    dialogValidator = jQuery("#mainForm").validate({
        // validation is on demand ==> set onfocusout and onkeyup validation to false
        onfocusout: false,
        onkeyup: false,
        errorPlacement: function(label, elem) {
            elem.closest(".validatable").find(".errormsg").append(label);
        },
        wrapper: "li",
        rules: {
            inputUrl: {
                url: true
            },
            imgWidth: {
                // validation of image size depends on input URL - validate size for not empty URL only
                imageSize: "#inputUrl:filled"
            },
            imgHeight: {
                // validation of image size depends on input URL - validate size for not empty URL only
                imageSize: "#inputUrl:filled"
            },
            wbWidth: dimensionRules,
            wbHeight: dimensionRules
        },
        messages: {
            // define validation messages
            inputUrl: "Please enter a valid image URL.",
            imgWidth: "Please enter a valid image width (only positive digits are allowed).",
            imgHeight: "Please enter a valid image height (only positive digits are allowed).",
            wbWidth: "Please enter a valid whiteboard width (only positive digits are allowed).",
            wbHeight: "Please enter a valid whiteboard height (only positive digits are allowed)."
        }
    });

    // configure the first dialog
    jQuery("#dialogInputImage").dialog("option", "buttons", {
        "Accept": function() {
            // validate all fields if user click on the "Accept" button
            var isValid1 = dialogValidator.element("#inputUrl");
            var isValid2 = dialogValidator.element("#imgWidth");
            var isValid3 = dialogValidator.element("#imgHeight");

            if ((typeof isValid1 !== 'undefined' && !isValid1) || (typeof isValid2 !== 'undefined' && !isValid2) ||
               (typeof isValid3 !== 'undefined' && !isValid3)) {
                // validation failed
                return false;
            }

            // do something ...
        },
        "Close": function() {
            jQuery(this).dialog("close");
        }
    }).bind("dialogclose", function(event, ui) {
        // reset input
        jQuery(this).find("#inputUrl").val('');
        // clean up validation messages
        jQuery("#errorImageUl").html('');
    });

    // configure the second dialog
    jQuery("#dialogResize").dialog("option", "buttons", {
        "Accept": function() {
            // validate all fields if user click on the "Accept" button
            var isValid1 = dialogValidator.element("#wbWidth");
            var isValid2 = dialogValidator.element("#wbHeight");

            if ((typeof isValid1 !== 'undefined' && !isValid1) || (typeof isValid2 !== 'undefined' && !isValid2)) {
                // validation failed
                return false;
            }

            // do something ...

            jq.dialog("close");
            
            // do something ...
        },
        "Close": function() {
            jQuery(this).dialog("close");
        }
    }).bind("dialogclose", function(event, ui) {
        // clean up validation messages
        jQuery("#errorResizeUl").html('');
    });
});
Validation looks now as follows



 In fact I already implemented partially client-side validation as JSF components ;-).

4 comments:

  1. Hello, please could you send me the example. I have problems with p: commandButton. What about that?.

    My E-mail: sidec_57@hotmail.com

    ReplyDelete
  2. hello oleg it is a nice article about csv on primefaces could you share the code or sample project please thank you.

    ReplyDelete
  3. The code for this article was here http://code.google.com/p/online-whiteboard/source/checkout

    ReplyDelete
  4. Hi Oleg,

    Great Post!

    Two questions though if you would be kind to answer:


    1. "The code should be placed after all p:dialog tags"

    Just a question though about your instruction above. I wanted to separate all of my javascript into a separate file per page. Can this code still work? And whats the process of doin this?

    I really dont like my JSF tags mixed with javascript so I prefer putting them into its own file.

    2. Externalize the javascript message?
    I see the messages as hardcoded in the javascript. Is there a way to handle any locale specific messages?

    ReplyDelete

Note: Only a member of this blog may post a comment.