The H5F library, emulate the HTML5 forms chapter

Recently I wrote an article for A List Apart about Forward thinking form validation and I introduced a script I wrote that emulates some of the new HTML Forms chapter functionality in older browsers, allowing a developer to use all these new features and have it work the same across the board.

In the spirit of giving back to the community I have released it under a dual BSD and MIT license so you can do as you please with it. I’ve setup a github repository so you can fork it, watch it suggest ways to improve it and submit any issues.

This script doesn’t rely on any libraries. It uses feature detection to find out if the browser already supports the constraint validation API and either hooks into the native support or creates methods to emulate it.

How does it work?

Let’s dive into the inner workings of the script and see how some of it works. As discussed in the Forward thinking form validation article the script adds the support for some of the HTML5 forms chapter input types and attributes as well as the constraint validation API.

On page load you run the H5F.setup() method which accepts two arguments, the second being optional. The first argument can be a HTMLFormElement, HTMLCollection of form elements or an array of HTMLFormElements. The second optional argument is an object to setup the class names to be applied to the fields in their different validity states.

H5F.setup([d.getElementById("signup"),d.getElementById("anotherform")], {
    validClass: "valid",
    invalidClass: "invalid",
    requiredClass: "required"

The above example passes an array of two HTMLFormElements. The script will then detect that more than one form is being passed and loop through the array and attach the functionality. The second argument specifies the class names to be applied when a field is valid, invalid or required.

Event capturing

In order to avoid potentially adding hundreds of event listeners we use the event models capturing behaviour and attach it to the parent form. This way when a field triggers an event we want to use, it will start at the window object work its way down the DOM tree and the form will capture it. To work around IE not bubbling blur and focus events we use the focusout and focusin events which do bubble.

if(type === "blur") {
    type = "focusout";
} else if(type === "focus") {
    type = "focusin";
node.attachEvent( "on" + type, fn );

The above code is taken from the H5F.listen() method which inside the IE fork will check if we are using blur or focus and change them to focusout and focusin respectively.

Detecting native support

Opera 9.6, Chrome 4, Firefox 4 and Safari 5 all support the constraints validation API natively so in order to not overwrite any built in functionality we use feature detection. = function() {
    return (H5F.isHostMethod(field,"validity") && H5F.isHostMethod(field,"checkValidity"));

The method will return a boolean value depending on the browsers support level. We check for native support of the checkValidity() method and the validity attribute.

Adding support

If the browser doesn’t support the necessary features we add them. Each form will have the checkValidity() method and we loop through each form control and also add checkValidity and the form controls validity attribute.

if(! {
    form.checkValidity = function() { return H5F.checkValidity(form); };
    while(flen--) {
        isRequired = !!(f[flen].attributes["required"]);
        if(f[flen].nodeName !== "FIELDSET" && isRequired) {
            H5F.validity(f[flen]); // Add validity object to field

Looping over each form control we check that it isn’t a fieldset (Firefox includes fieldsets in the elements array whereas webkit doesn’t) and that it is required. We only check the existence of the required attribute and not its value since it is a boolean attribute.

H5F.validity = function(el) {
   var elem = el,
       missing = H5F.valueMissing(elem),
       type = elem.getAttribute("type"),
       pattern = elem.getAttribute("pattern"),
       placeholder = elem.getAttribute("placeholder"),
       isType = /^(email|url)$/i,
       evt = /^(input|keyup)$/i,
       fType = ((isType.test(type)) ? type : ((pattern) ? pattern : false)),
       patt = H5F.pattern(elem,fType),
       step = H5F.range(elem,"step"),
       min = H5F.range(elem,"min"),
       max = H5F.range(elem,"max");
   elem.validity = {
       patternMismatch: patt,
       rangeOverflow: max,
       rangeUnderflow: min,
       stepMismatch: step,
       valid: (!missing && !patt && !step && !min && !max),
       valueMissing: missing
   if(placeholder && !evt.test(curEvt)) { H5F.placeholder(elem); }
   elem.checkValidity = function() { return H5F.checkValidity(elem); };

In native supporting browsers each form control has the validity attribute that has boolean values to know what state the control is in.

Compare with regular expressions

When a pattern attribute is used we first check the field’s type against a regular expression.

var isType = /^(email|url)$/i,
   fType = ((isType.test(type)) ? type : ((pattern) ? pattern : false));

If a fields type is either email or url we want to handle that differently compared to just a normal text input with the pattern attribute.

H5F.pattern = function(el, type) {
   if(type === "email") {
       return !emailPatt.test(el.value);
   } else if(type === "url") {
       return !urlPatt.test(el.value);
   } else {
       usrPatt = new RegExp(type);
       return !usrPatt.test(el.value);

Knowing what type the form control is we can than fork it to handle emails and url’s by using our predefined regular expressions.

For all other form controls which don’t match any of our predefined types we pass in the pattern attribute value to a regular expression and test the field’s value against that.

Keep a form control within range

H5F also supports a simplified version of min, max and step attributes. It will only support whole numbers and in the demo we use it for a postcode example, although most international postcodes aren’t all numbers like Australia but we’ll use this example just to demonstrate the feature.

The postcode form control has both the min and max attributes set to keep it within two values, 1001 – 8000 in our case. Since min, max and step are all related I created a single method called H5F.range() to handle it all.

H5F.range = function(el,type) { 
    var min = parseInt(el.getAttribute("min"),10) || 0, 
        max = parseInt(el.getAttribute("max"),10) || false, 
        step = parseInt(el.getAttribute("step"),10) || 1, 
        val = parseInt(el.value,10), 
        mismatch = (val-min)%step; 
    if(!H5F.valueMissing(el) && !isNaN(val)) { 
        if(type === "step") { 
            return (el.getAttribute("step")) ? (mismatch !== 0) : false; 
        } else if(type === "min") { 
            return (el.getAttribute("min")) ? (val < min) : false; 
        } else if(type === "max") { 
            return (el.getAttribute("max")) ? (val > max) : false; 
    } else if(el.getAttribute("type") === "number") { 
        return true; 
    } else { 
        return false; 

If a form control specifies only the step attribute the H5F.range() method works around that by using the OR (||) operator to give the variables fallback values so the calculations can still be made. To emulate the step attribute we use modulus (%) which will divide our value by our minimum value, will fallback to 0 if it isn’t specified, and return the remainder.

mismatch = (val-min)%step; 
if(!H5F.valueMissing(el) && !isNaN(val)) { 
    if(type === "step") { 
        return (el.getAttribute("step")) ? (mismatch !== 0) : false; 

Using the modulus value we can find out if it is conforming to the specified increment. We make sure the field isn’t empty and that it is actually a number. Then we do a check to make sure it doesn’t equal zero, if it does it will return true which will indicate that there is a step mismatch if false the number is conforming. E.g. If the field has a step value of 3 and the user enters 11 we work out the remainder: (11-0)%3, 3 goes into 11 three times and leaves us with a remainder of 2 which tells us there is a step mismatch.

Browser differences

Since HTML5 is still a working draft there a few difference between browsers and browser version, I’ve listed some of them I’ve come across here:

  • In Firefox 4 invalid form controls have a red box-shadow applied to them; this can be reset by setting the box-shadow to none.
  • Firefox 4 will override the title tooltip value depending on the validity state of the form control.
  • Safari 51, Chrome 4-61 and Opera 9.6+ all block form submission until all form control validation constraints are met.
  • Opera 9.6+ upon form submission will focus to the first invalid field and bring up a UI block indicating that it is invalid.

1. Safari 5.0.1, and Chrome 7 have removed form submission blocking if a form is invalid, most likely due to legacy forms now not submitting on older sites.

[link href=”″]