jQuery Form Validation

In this tutorial, we’ll continue developing the HTML5 form that we’ve been working on.  We’re going to import jQuery and the jQuery Validate plugin into our page, so that we can validate any data entered into the form.  In the next part, we’ll work on propping up any HTML5 UI improvements that aren’t supported by the user’s browser using jQuery plugins.

At the bottom of our html contact form, just before the closing body tag, import jQuery from the Google CDN into the page.  Unfortunately, the Google CDN doesn’t currently host the jQuery validate plugin, but Microsoft do, so we’ll import it from their CDN.  After this, import a new script – I’ve called mine ‘global.js’ – into the page.  Don’t forget to create the new empty script file.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

A lot of jQuery code samples you see start a script with the document.ready function.  There isn’t any need for us to do that here, as we’ve put our script tags at the bottom of the page – the document has already loaded before we’ve told the browser to fetch any scripts.

We’re going to check the data entered against the following patterns:

Fieldset 1

  • Name: letters, hyphens and spaces only, required field
  • Company: no validation
  • Email: email format (a@b.c), required field
  • Telephone: numbers only, min 11 chars, max 11 chars, required field
  • Website: url format

Fieldset 2

  • Type of Project: no validation (one item is selected by default), dynamically generate a textarea to collect details
  • Estimated Completion Date: no validation (we’ll use a jQuery datepicker plugin here later)
  • Estimated Budget: required field, numbers only
  • Additional Details: no validation

Fieldset 3

  • Please contact me by… : no validation required, as an option is selected by default
  • Best time to contact: disabled unless ‘contact by phone’ is selected

If all the tests pass, the form will be submitted; otherwise, the user will get instant feedback regarding any errors.  In global.js, write the following code:

$('form').validate();

If you specify which fields to validate in the html itself (usually by adding class=”required” to the input field you want to validate), this would initialise the plugin with the default options.  However, we want some custom validation and custom error messages, so we’re going to have to delve a little more deeply into the plugin’s interface.  For reference, all the methods associated with the plugin can be found here.

Options are specified by passing them into the validate() function with an object literal, as follows:

$('form').validate({
    rules : {}
});

This is where we will define the validation rules for each of our form inputs.  Each form input is referred to by it’s ‘name’ attribute.

$('form').validate({
    rules : {
        name : {},
        email : {},
        telephone : {},
        website : {},
        'project-budget' : {},
        'contact-time' : {}
    }
});

Notice that we’ve had to wrap any keys containing a hyphen in quotes, as hyphens are not permitted in property names.  Also, be sure to omit the comma for the last item of an object literal, as this will throw an error in IE.  Lets go through the form and add a ‘required’ rule for all the appropriate fields.

$('form').validate({
    rules : {
        name : {
            required : true
        },
        company : {},
        email :  {
            required : true
        },
        telephone :  {
            required : true
        },
        website : {},
        'project-budget' :  {
            required : true
        },
        'contact-time' : {}
    }
});

If we were to leave a field empty now, we’d see the default error message, telling us that the field is required.

Lets fill out the rest of the options.

$('form').validate({
    rules : {
        name : {
            required : true,
            letters : true
        },
        email :  {
            required : true,
            email : true
        },
        telephone :  {
            required : true,
            digits : true,
            minlength : 11,
            maxlength : 11
        },
        website : {
            url : true
        },
        'project-budget' :  {
            required : true,
            digits : true
        },
        'contact-time' : {
            required : function(element) {
                return $('.contact-method input:last').is(':checked');
            }
        }
    }
});

The above code is pretty self-explanatory, but for a couple of points.  Firstly, ‘required’ is not set to true or false on the ‘contact-time’ field, as we want the field to be a required field only if the ‘phone’ option is selected.  We need to pass the ‘required’ method a function containing an expression to be evaluated; this value is then set to be the return value of the function.  If the ‘phone’ option is selected, the expression will evaluate to true, so the field will be required, otherwise, the expression will be false, and the field will not be required.

Secondly, the ‘letters’ method is not a method native to the plugin, so we have to create a custom method using $.validator.addMethod.  We want to create a method that will check the data to match a pattern of letters, spaces and hyphens only.  We do this by matching the data against a regular expression.  Follow the code below as we build up a regular expression for matching lettters:

  1. [a-z] – lowercase letters only
  2. [a-zA-Z] – lowercase and capitals
  3. [- a-zA-Z] – lowercase, capitals, hyphen, space
  4. ^[- a-zA-Z]$ – ^marks the beginning of a pattern, $ marks the end
  5. ^[- a-zA-Z]+$ – the plus(+) sign after the brackets but before the end of the pattern denotes that we want to find more than 1 character
  6. /^[- a-zA-Z]+$/ – we wrap the enter regular expression in the JavaScript regex literal (//)

Lastly, we use the match method to check if the field data is a match:

6.   value.match(/^[- a-zA-Z]+$/);

The final code for the custom letters method is as follows:

$.validator.addMethod('letters', function(value) {
    return value.match(/^[- a-zA-Z]+$/);
});

Next we need to specify the error messages that we want to be displayed for each test, as below.

$('form').validate({
    rules : {
        name : {
            required : true,
            letters : true
        },
        email :  {
            required : true,
            email : true
        },
        telephone :  {
            required : true,
            digits : true,
            minlength : 11,
            maxlength : 11
        },
        website : {
            url : true
        },
        'project-budget' :  {
            required : true,
            digits : true
        },
        'contact-time' : {
            required : function(element) {
                return $('.contact-method input:last').is(':checked');
            }
        }
    },
    messages : {
        name : {
            required : 'The name field cannot be blank',
            letters : 'Please enter letters only'
        },
        email :  {
            required : 'The email field cannot be blank',
            email : 'Please enter a valid email address'
        },
        telephone :  {
            required : 'The telephone field cannot be blank',
            digits : 'Please enter numbers only',
            minlength : 'Please enter a UK phone number (11 digits)',
            maxlength : 'Please enter a UK phone number (11 digits)'
        },
        website : {
            url : 'Please enter a valid url'
        },
        'project-budget' :  {
            required : 'The project budget field cannot be blank',
            digits : 'Please enter numbers only'
        },
        'contact-time' : {
            required : 'The contact time field cannot be blank'
        }
    }
});

That’s all the validation we’re going to do in this part of the tutorial, in the next section we’ll improve the UI with widgets in browsers that don’t support the native HTML5 form improvements yet. Before we finish, we’ll just tidy up those error messages.

From the screenshot above, we can see that the errors are displaying in the wrong place – they should be underneath the error field, not to the bottom left where the field labels are.  To remedy this, we simply need to override the default css for a label element.  Fortunately, errors generated by the plugin have the class “error”, so we can simply target this class:

label.error { text-align: left; margin-left: 200px; color: #CC0000; padding: 0; height: auto; width: 208px; font-size: 11px }
input.error { border: 2px solid #CC0000 }

View the Demo

HTML5 Forms Support Detection with JavaScript

In this tutorial, we’ll continue developing our HTML5 form.  In the last tutorial, we styled the form with some of the new CSS3 properties; this time we’re going to detect support for the new HTML5 input types.

We need to detect whether the browser supports the new HTML5 input types natively – if not, we can use this knowledge to fall back to JavaScript where appropriate.  It is important to note that there currently isn’t a lot of browser support for these input types: Opera 10.6 has the most complete support, Safari 5 and Chrome 5 have partial support, Firefox 3.6.8 and IE8 (as usual) are dead in the water.  If you’re dabbling with HTML5 at all, you’ll know that support is patchy at best, and you should treat tutorials like this one as instructional rather than definitive.  So whilst this information is current now, the specification is still undergoing revision (and will be for a while yet!).

Support Detection

If a browser doesn’t support a particular attribute value, it will degrade to the default value text.  In theory, this is how we test for browser support – we create a new input element in JavaScript (in memory only, don’t append it to the DOM), assign to it the attribute value we want to detect support for, and then query the element’s type attribute.  If the attribute retains the value that you assigned to it, the browser supports it.  Otherwise, if it returns the default value, the browser doesn’t support it. Using this method, we can loop through all the new input types and log the results to console to see what we get back:

var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
    len = inputs.length;

for (var i = 0; i < len; i++) {
    var input = document.createElement('input');
    input.setAttribute('type', inputs[i]);
    console.log(inputs[i] + ' -> ' + input.type);
}

Notice that I began the above statement with the qualifier “in theory.”  Whilst this method should technically work, some browsers are prone to giving false positives, i.e. asserting that they support a particular input type, when in reality, they don’t.  Take a look at the output produced for the basic test above:

Only Firefox, IE8 and Opera are telling the truth! If you visually check in Safari and Chrome whether the UI improvements or validation functionality is present (using Mike Taylor’s HTML5 Input form, for example), it’s pretty evident that Safari and Chrome are not being entirely honest with us.  So how do we test for support if two of the major browsers are telling porkies?  Well, you have to go a step further and test for both UI improvements and validation functionality manually.

However, support for data validation is currently pretty flakey – that’s interpreting it generously.  It appears that some browsers validate the data properly, whilst others don’t check the data but pass it as valid anyway (see this discussion for details).  This probably won’t be resolved for a long time, so we’re going to validate data with JavaScript – experiments are one thing, but the state of implementation is so fragmented at this point that it’s not actually of any use to us.  So in the next part of this tutorial, validation will be handled by JavaScript exclusively.  What about the UI improvements?  If the UI improvements are supported, we’ll use those, otherwise we’ll provide the functionality ourselves.  Sounds like a plan!

We need to test for these UI improvements to see if they’re present in the browser.  UI improvements can be best described as things that we’d usually have to implement a widget for in Calendar in OperaJavaScript –  like a date picker or calendar – except now they’re implemented by the browser. We test for them by passing a text value to our mock-input, and then checking what gets returned – any new UI functionality shouldn’t be able to hold a text value.  If a string is returned, there’s no UI improvement; if anything else is returned (e.g. a numerical value in the case of range, or an empty value in most other cases) then a UI improvement is present.  One caveat: there are no detectable improvements or validation where the telephone and search inputs are concerned as yet, so we have to eliminate these from the first part of the test.

var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
len = inputs.length,
uiSupport = [];

for (var i = 0; i < len; i++) {
    var input = document.createElement('input');
    input.setAttribute('type', inputs[i]);
    var notText = input.type !== 'text';

    if (notText && input.type !== 'search' && input.type !== 'tel') {
        input.value = 'testing';
        if (input.value !== 'testing') {
            console.log(input.type);
        }
    }
}

In the FF and IE consoles, we don’t get any data returned, as expected.  Safari and Chrome return a non-string value for range only.  Opera returns a non-string value for datetime, date, month, week, time, datetime-local, number and range.  The last thing we have to do is store this information in an array so that we can refer to it later, then wrap the whole code block in a function so that the variables have some semblance of a namespace.

(function() {
    var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
    len = inputs.length,
    uiSupport = [];

    for (var i = 0; i < len; i++) {
        var input = document.createElement('input');
        input.setAttribute('type', inputs[i]);
        var notText = input.type !== 'text';

        if (notText && input.type !== 'search' && input.type !== 'tel') {
            input.value = 'testing';
            if (input.value !== 'testing') {
                uiSupport.push(input.type);
                // console.log(uiSupport);
            }
        }
    }
})();

uiSupport will store the names of the input type attributes that are supported, so we can use them when we come to create our fallbacks in the next tutorial.

Quick Tip: If you need to detect support for some of the other input attributes – autofocus, for example, or any of the other new HTML5 elements, it’s definately worth checking out Modernizr.  Modernizr has the added benefit of having IE support baked in, so there’s no need to include an additional HTML5 shim to get the new (header, section, etc.) tags working.