In my last blog entry, I introduced the basic concepts of form validation under HTML5. The focus was on basic tag/attribute support. In other words, what could be done without writing JavaScript code. In today's entry I want to talk about how you can use JavaScript to further enhance and work with validation, specifically the Constraint Validation API.

At a high level, this API covers the following features:

  • Form fields have a validity property. This property is a set of keys and boolean values that represent the validity, or lack of, of a particular form. A great example of this is the numeric field type. You can specify that a form field should be numeric, higher than 0, and less than 70. The validity property would actually be able to tell you if the value wasn't a number, or was too low or too high.
  • Form fields also have a generic checkValidity() function that returns true or false. So if you don't care why a field is invalid, you can simply use that. Or you can use that before digging into the validity property to determine exactly why the field isn't valid.
  • Finally, there is a setCustomValidity() method that lets you create a custom validation error. You can think of this like a throw() call as it both lets you set a message and, by default, sets the field as being in an error state. If you use an empty string, the field is considered valid.

I began my investigation by looking at the validity property. If you read the spec (no, I'm not joking, you really should), you'll see a nice list of the validity properties and their meanings. I'm quoting here from that spec:

element . validity . valueMissing
Returns true if the element has no value but is a required field; false otherwise.

element . validity . typeMismatch
Returns true if the element's value is not in the correct syntax; false otherwise.

element . validity . patternMismatch
Returns true if the element's value doesn't match the provided pattern; false otherwise.

element . validity . tooLong
Returns true if the element's value is longer than the provided maximum length; false otherwise.

element . validity . rangeUnderflow
Returns true if the element's value is lower than the provided minimum; false otherwise.

element . validity . rangeOverflow
Returns true if the element's value is higher than the provided maximum; false otherwise.

element . validity . stepMismatch
Returns true if the element's value doesn't fit the rules given by the step attribute; false otherwise.

element . validity . customError
Returns true if the element has a custom error; false otherwise.

element . validity . valid
Returns true if the element's value has no validity problems; false otherwise.

That's quite a few properties. I created a form that made use of various new input types and wrote code to iterate over those properties and write them out so I could see in real time how the values would change based on the inputs I provided.

var fields = document.querySelectorAll("input"); for(var i=0,len=fields.length; i<len; i++) { var thisId = fields[i].id; var s = "<div class='results'>Validity for "+thisId; s += "<table>"; //first, call checkValidity as a whole s += "<tr><td><b>VALID:</b></td><td>"+fields[i].checkValidity()+"</td></tr>"; for(prop in fields[i].validity) { s += "<tr><td>"+prop+"</td><td>"+fields[i].validity[prop]+"</td></tr>"; } s+= "</table></div>"; fields[i].insertAdjacentHTML("afterend",s); }

You can see my simple for/in loop there generating nice table rows. Also note I use the checkValidity API to show, at a high level, if the field is valid or not.

Here's an example:

I mentioned the checkValidity API and how it can be used as a shortcut for determining if a field is valid. What's cool is that you can also do it for the entire form. Here is another example:

//check the form as a whole var form = getBySel("#mainForm"); var formStatus = getBySel("#formStatus"); formStatus.innerHTML = "<p>Form validity as a whole is "+form.checkValidity()+"</p>";

As a quick aside, getBySel was just a shortcut function I wrote:

//jQuery is a drug - a shiny, happy drug... function getBySel(id) { return document.querySelector(id); }

Put together, I've got a nice demo application that lets you test this API against a variety of constraints. Since it is entirely client-side, I'll encourage you to View Source:

Now let's look at the next piece of the puzzle - custom validation. I mentioned the setCustomValidity API before. It works like so:

Given a field - if you set any non-empty string value via setCustomValidity, it is implied that the field is in error. If you set an empty string, it is implied that the field is ok.

So that makes sense, but it feels a bit awkward to me. I know I tend to rail against making things overly complex, but it feels weird that setting a string value for the message also sets the field as being invalid. I'd rather a two step process along the lines of setValid(false);setCustomValidity("....");.

Let's look at an example. This is a bit contrived, but in the form below we want to error out if the value is ray.

<!DOCTYPE html> <html> <head> <script> function getBySel(id) { return document.querySelector(id); }

function demoForm() { var field = getBySel("#field1"); var value = field.value; if(value == 'ray') { field.setCustomValidity("Ray is wack!"); } else { field.setCustomValidity(""); } }

function init() { getBySel("#testForm").addEventListener("click",demoForm,false); } </script> <style> </style> </head> <body onload="init()">


<p> Just required: <input type="text" id="field1"> </p>

<p> <button id="testForm">Test</button> </p> </form> </body> </html>

As I said - it's a rather trivial example, but you can see where I use a simple event handler to check the value of a form field. It is important to remember that once you setCustomValidity and pass a non-empty string, the field is considered in error until you setCustomValidity with an empty string. Basically you've marked the field as bad until you explicitly set it to good.

Obviously one could do something a bit more complex here. You're options are pretty much anything at all. You could do an Ajax request to ensure the value was unique. You could check other values in the form. Try the demo yourself: