views:

1204

answers:

2

I've been doing some development using the xVal framework for .NET to link up some of the validation rules for models on the server side along with some client side validation using the jQuery Validation plugin along with the jQuery Form plugin for submitting the form.

The scenario is the following:

  1. Allow the client to perform basic validation using rules defined by calling rules("add", options") plugin for jQuery Validation (this is what xVal uses to get rules defined on the server side on the model).

  2. If the client validation succeeds, make the call to the server to input the form data performing validation again (on items that were validated on the client, as well as any other validation which could not be performed in the client).

  3. Have the server return an object in JSON which indicate any errors which might have specific fields and then set the error display for the fields.

Setting up the metdatata for the plugin is easy. If you are using xVal, it simply is a call on the server side in ASP.NET:

<%= Html.ClientSideValidation<Model>("model") %>

This translates into something like the following on the client side:

<script type="text/javascript">
xVal.AttachValidator("model", 
{
    "Fields": 
    [ 
        {
            "FieldName":"title",
            "FieldRules": 
            [
                {
                    "RuleName":"Required",
                    "RuleParameters":{}
                },
                {
                    "RuleName":"StringLength",
                    "RuleParameters":
                    {
                        "MaxLength":"250"
                    }
                }
            ]
        },
        {
            "FieldName":"body",
            "FieldRules":
            [
                {
                    "RuleName":"Required",
                    "RuleParameters":{}
                }
            ]
        }
    ]
}, {})
</script>

Note that the above is not really a .NET-specific issue, as ultimately, the above really just translates into a series of calls to rules("add", options), so this is not a .NET-specific issue.

The most important thing to look out for when putting all of this together is the little piece of documentation (which isn't really apparent in the documentation for xVal, which abstracts the call to rules("add", options) in the call to xVal.AttachValidator) for rules("add", options) (emphasis mine):

Adds the specified rules and returns all rules for the first matched element. Requires that the parent form is validated, that is, $("form").validate() is called first.

This is especially important when the jQuery Form plugin comes into play, and you want to submit the form via AJAX, as you have to set up a submitHandler option in the call to validate(options), like so:

<script type="text/javascript">
    $(document).ready(function() {
        // Initialize the form.  Store the validator.
        var validator = $("form").validate({

            // Called when the form is valid.
            submitHandler: function(form) {

                // Submit the form via ajax.
                $(form).ajaxSubmit({

                    // The return data type is json.
                    dataType: "json",

                    // The callback on a successful form
                    // submission.
                    success: function(data, statusText) {

                        // If the new location is not null, then
                        // redirect to that location.
                        if (data.data.newLocation) {
                            // Go to the new location.
                            document.location.href = data.data.newLocation;

                            // Get out.
                            return;
                        }

                        // There are errors, pass them to the validator
                        // for display.
                        validator.showErrors(data.data.errors);
                    }
                });
            }
        });
    });
</script>

Because of the documentation quoted above regarding calls to rules("add", options), the call to validate(options) must come before the calls to rules("add", options).

If they do not, then the submitHandler is ignored, never called.

In the end, this means that your client side code has to look like this when putting it all together:

<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="jquery.validate.min.js"></script>
<script type="text/javascript" src="jquery.form.js"></script>
<!-- Note this is only needed if using xVal. -->
<script type="text/javascript" src="xVal.jquery.validate.js"></script>
<!-- The call to validate the form must come first. -->
<script type="text/javascript">
    $(document).ready(function() {
        // Initialize the form.
        $("form").validate({

            // Called when the form is valid.
            submitHandler: function(form) {

                // Submit the form via ajax.
                $(form).ajaxSubmit({

                    // The return data type is json.
                    dataType: "json",

                    // The callback.
                    success: function(data, statusText) {

                        // Alert the users to the message.
                        window.alert(statusText);
                    }
                });
            }
        });
    });
</script>

<!-- Now make the calls to rules("add", options), AFTER the call to -->
<!-- validate (options). It's separated into another block for      -->
<!-- emphasis, but could be done in the block above.                -->
<script type="text/javascript">
    // Make calls to rules("add", options).
</script>

<!-- Or, if you are using xVal, make the following call in the ASP.NET -->
<!-- page but again, note it must come AFTER the call to               -->
<!-- validate(options).                                                -->
<%= Html.ClientSideValidation<Model>("model") %>

Finally, with all of this wired up, the last thing to do is what to do when the server-side method returns.

For my methods, I have a standard data JSON object structure which looks like this:

{
    // An integer, non-zero indicates faulure, with predefined ranges
    // for standard errors across all operations, with other ranges for custom
    // errors which are operation-specific.  Examples of shared errors
    // are not authenticated, not authorized, etc, etc.
    resultCode: 0,

    // A string, which is to be displayed to the user (usually in the
    // form of a jQuery dialog, usually used for the common ranges of
    // errors defined above.
    message: null,

    // An object with operation-specific results.
    data: null
}

For the errors on the server, I'll return the same as above, but with a location which has the URL which the user should be redirected to on success (or null if it was not successful) and a map which can be passed directly to the showErrors(errors) method if there are errors on the fields:

{
    resultCode: 0,

    message: null,

    data:
    {
        // Returned as a string.  If not null, then this is the url
        // that the client should be redirected to, as the server-side
        // operation was successful.
        newLocation: null,

        // If not-null, then this is a map which has the names of the
        // fields with the errors, along with the errors for the fields.
        errors:
        {
            "model.title": "The title already exists in the system.",
            "model.body": "The body cannot have malicious HTML code in it."
        }
    }
}

Given that, the success field of the options parameter passed to ajaxSubmit should be clear:

// The callback on a successful form
// submission.
success: function(data, statusText) {

    // If the new location is not null, then
    // redirect to that location.
    if (data.data.newLocation) {
        // Go to the new location.
        document.location.href = data.data.newLocation;

        // Get out.
        return;
    }

    // There are errors, pass them to the validator
    // for display.
    validator.showErrors(data.data.errors);
}

All it does is check to see if the newLocation property is defined. If it is defined, then it redirects the current document to the location (which typically would be the url of the newly saved resource).

If it is not defined, then it takes the map and passes it to showErrors on the validator returned by a call to validate(options), setting the error messages using the positioning and style specified by the call to validate(options).

+1  A: 

I'm not sure that I completely understand your problem, but it sounds like a chicken/egg situation with the form plugin and the validate plugin. I have a jquery validation plugin, imValidateForm, that does not require the form plugin as my plugin validates and submits the form (and can return a json object with an error or redirection url).

You can read the documentation here

I hope this helps

Les Green
@Les Green: There isn't a problem per se; I marked this as a Community Wiki so that others who are using the jQuery Validator plugin along with the jQuery Forms plugin and creating their own rules (as opposed to attributing HTML elements) have this available for them and don't run into the same pitfalls that I did when trying to figure it all out. xVal is a little bit of a red herring here, it's really the issue with setting rules in javascript in the jQuery Validation plugin, and the jQuery Forms plugin not working if you don't do it in the correct order (as documented, but hard to find).
casperOne
+1  A: 

Great post! It saved my day!

Radu