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.

H5F.support = function() {
    return (H5F.isHostMethod(field,"validity") && H5F.isHostMethod(field,"checkValidity"));
};

The H5F.support() 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(!H5F.support()) {
    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.

Short URL: http://cssn.in/ja/029

 

Post filed under: javascript.

Skip to comment form.

  1. Daniel15 says:

    Nice work! :)
    The placeholder doesn’t seem to work in Opera, though. :(

  2. @Daniel15 – Hmm will look into that. Cheers.

  3. Pratheep says:

    Hi, this script is great. Now we can starting using all those new form functions and not wait till IE9 replace all the other IEs.

  4. @Pratheep – IE9 doesn’t support these new form elements yet, so the script it needed for all version of IE.

  5. Art says:

    Hi,

    Thanks for the effort! There is a slight usability problem though.

    When looking at the demo and iterating over fields with TAB key one gets the red error box with a message when, say, the email box gets focus.

    I think this is conceptually incorrect – I haven’t actually submitted the form with errors yet, why the shouting?

    If that was intended to be a notification message perhaps it should be more subtle?

    Another issue is that after postback with empty fields none of the required ones don’t get any king of indication that there’s an error, but I presume it’s jsut because there’s no server side support for that?

    Thanks for your work.

  6. @Art – Yes the red is probably the wrong use as it’s a notification, but that’s easily changes in the CSS.

  7. Kevin McGee / Netais LLC says:

    Great work!
    I’ve been playing with this a bit and have something for you to consider as an option.

    The immediacy of the GreenCheckmark *might* be too much (too immediate) for some. It can be disruptive to perceive a change to the textbox AS one is typing into it.

    Consider an adjustment such that the “OK” indication is provided when I move OFF the field.

    In other words, on a required field the sequence would be:
    * initial state: textbox empty, no feedback
    a. receive focus, no feedback
    b. type, no feedback
    c. leave the field (“onblur”), user data > ” “, display GreenCheckmark

    My 2c.

  8. @Kevin – Thanks for the great feedback. Will definitely look into a way of giving developers greater control of when and how the validity state should be updated on the front-end.

  9. flxa says:

    It works great except it doesn’t seem to handle textarea’s for validation. Is that why you are not using it on this comment form? I could live with it if it just left the textarea alone but it gives it an error icon that wont go away and still allows the form to submit.

    I’d like to use this on a comment or contact form both of which will require a textarea for message input so I’m going to see what I can do about it myself but just thought I’d holla.

  10. @flxa – Thanks for the feedback, I will be pushing out some more updates when I’m free (been moving houses).

  11. Eric Leads says:

    Hi,

    I was talking to Paul Irish today, and he asked me why we aren’t using H5F at Zumba Fitness to validate the shopping carts and other forms used by millions of customers, worldwide.

    Thought I’d stop by and offer feedback to you myself.

    I think you’ve done a great job here, and set the bar really high for me. However, I chose to start from scratch for a few reasons:

    1) Your implementation did not offer easy control over the events that trigger validation. That’s a big oversight.

    2) Your implementation is not flexible in how it handles validation. I didn’t see any documentation about how to override the methods that get called when a field is invalid, or valid, for that matter.

    3) There’s no easy way to hook the pattern attribute and assign patterns to elements based on element selectors. This is critical functionality that allows sharing a single pattern with many fields. It’s a great way to keep things DRY between the client and server validation methods.

    4) I understand your choice not to depend on a library, but you’ve sacrificed performance, convenience, and the stability of a unit-tested framework with thousands of qualified programmers watching the codebase in order to appeal to a broader audience. Being library-independent is not really a desirable feature in my view. I wanted a jQuery plugin to do this.

    That said, taking a closer look at your code has inspired me. I disagree with some of your choices (for instance, why are you forking for email and URL? Why not just add patterns to the pattern classes and use the same validation code you use for everything else?) — but there’s some cool stuff in here that will at least offer food for thought while I develop my jQuery plugin.

    Thanks for your interesting contribution to the HTML5 community. Keep up the good work!

    Check out h5Validate and let me know what you think of my effort. Maybe we can help each other out.

    - Eric

  12. @Eric – Thanks for you feedback, really great stuff.

    I think we are taking two different paths when it comes to validation. My intention was to replicate the HTML5 forms chapter, follow it’s conventions and replicate what supporting browsers are doing now and how they behave. What you’re doing is building on top of that by extending the native functionality to create the behaviour and hooks you want. Mine was never that intention.

    1) Your implementation did not offer easy control over the events that trigger validation. That’s a big oversight.

    This is in a planned update to give greater control to a user on when validation should happen.

    3) There’s no easy way to hook the pattern attribute and assign patterns to elements based on element selectors. This is critical functionality that allows sharing a single pattern with many fields. It’s a great way to keep things DRY between the client and server validation methods.

    I did see that functionality in your plugin and thought that was a great idea. However I didn’t add this as the whole idea was about following the draft specification, there a built in types such as email and url and for any others you use the pattern attribute.

    4) I understand your choice not to depend on a library, but you’ve sacrificed performance, convenience, and the stability of a unit-tested framework with thousands of qualified programmers watching the codebase in order to appeal to a broader audience. Being library-independent is not really a desirable feature in my view. I wanted a jQuery plugin to do this.

    I felt that including a javascript library was unnecessary, what I wanted to achieve didn’t need all the extra functionality that jQuery provided. I don’t need xhr, animation, selector engine or anything else. I massively reduced file size because I didn’t need it. As for performance of course there are improvements that can be made that’s why I put it up on github.

    I disagree with some of your choices (for instance, why are you forking for email and URL? Why not just add patterns to the pattern classes and use the same validation code you use for everything else?)

    It’s all about creating the behaviour exhibited in native supporting browsers. Email and URL have internal patterns that the browser uses, which I replicate, and is the reason I fork for those field types.

  13. Eric Leads says:

    We certainly had different requirements from the start. I needed to build something that would work today on a high-traffic production system. You set out to actually implement the spec as written.

    I have so far ignored the spec on several key points:

    1) Support for new input types. Because they’re being implemented differently on different browsers, and some browsers are not yet implementing them correctly, or allowing you to style or access them properly, using some of the new input types is a non starter in production.

    Since I’m on a tight deadline, even figuring out which ones are safe to use is a non-starter until after the crunch is over. This is one of the major problems that led to the selector-based pattern assignment in h5Validate.

    2) The entire validation API – the spec calls for the attachment of methods to every element to check for validity. In my case, I’m using event delegation to access the field being validated, which makes the method attachment to the elements a bit redundant. Of course, ignoring that makes it impossible to take advantage of native validation if it exists, and that is an oversight I will most likely correct in the future.

    Your plan to provide the methods if they don’t exist and then use those methods for all your validation was a good plan. It would reduce some of the redundancy that I would introduce by implementing the feature, and I will probably re-factor that code when I get around to it so that it more closely matches the behavior of your script in that regard.

    Following the spec to the letter was never my plan. My plan was to take advantage of the great ideas introduced by the spec (specifically, the tremendously useful “pattern” and “required” attributes), and use them in production today.

    BTW, my choice to use jQuery gives me a bunch of stuff for free — for example, the selector engine (enormously valuable for a validation plugin), and AJAX (eventually h5Validate will include the ability to validate against the back-end and submit the form via AJAX) — and since our websites already use jQuery, there is no additional cost associated with using it.

    For more on why I chose to use HTML5 forms for production at all, I wrote a blog post on the topic, here:

    http://ericleads.com/2010/11/html5-form-validation/

  14. Bug report using H5F in Firefox 3.6.12. For a not required field using a placeholder text, the placeholder is not shown as the page is being loaded. Only the placeholders for required fields are shown on pageload. The placeholder text is (incorrectly) shown as soon as the field first receives focus. The placeholder text stays in the field (correct) when focus moves. When the not required input receives focus for the second time, the placeholder text is (correctly) removed. Cache is disabled, but seems involved. As soon as the placeholder once has been shown, a page reload maintains correct behaviour. Only after a shift+reload, the placeholder text is removed again.

  15. Thanks for the feedback. I have added an issue in the github repo. If anyone else finds any bugs or issues please report them on github.

  16. jaisuganthi says:

    Hi,
    I am using your library for validation. But here you didn’t handle radio group options.

    How can I modify it for getting the error message to select any one of the option.

  17. Ruslan says:

    Hi,

    Great work!

    Is there any way to check form validity?

    What I’m trying to do, is to send AJAX request, but since default action is prevented it seems that form’s validation interrupted. I can’t find a way how to check if form is valid before sending it.

    Sorry for my poor english, hope my question is clear enough.

  18. Ryan Seddon says:

    @Ruslan

    You can check the if the form is valid by running checkValidity() on the form element e.g.

    document.getElementsByTagName("form")[0].checkValidity();

    That will return a boolean to let you know if the form is valid or not.

  19. [Axel] says:

    Hello Ryan,

    First off all, Thanks for your nice work!

    I just checked out the new Version (You should add a Version number to the script header) and found something nasty.

    If a field is not required, it is not checked for validity. I know an empty field is valid, but if a user starts to fill in the field, it can become invalid, so hence to this it should be validated for errors.

    As an example a phone number is not required, but can be entered, so if the user types in a number it should be checked if it is a valid number (if a pattern is defined).

    That way we can use CSS to give some “tool-tips” depending on the validity state.

  20. [Axel] says:

    Forgot to say, IMO only fields with the noValidate attribute should be skipped fro validation.

  21. Ezn says:

    How can I prevent first input from getting focus?

    Using H5F on a form in a YUI 2 Panel Container. When I open this container, focus is on the first element and therefore the placeholder for the first element is not seen until blur, but by then the input is ‘invalid’.

    Please advise. Thanks.

  22. Ezn says:

    You can strike my last comment, I don’t think it was H5F giving first element focus, must have been another js file.

  23. ricky spires says:

    Hello,

    Great script. I love it.

    The only thing is, i dont really know jscript and i cant get check password to work.

    is there a forum or support email i could contact ? or would anyone else be able to help me please.

    Thanks
    Ricky

  24. Nils says:

    Currently, when I’ve touched a field (not required), but not typed anything, it gets the valid class applied. This seems wrong to me.
    Is there any way to control that the valid class is only applied if the field has content (and it’s valid of course)?

  25. Ryan Seddon says:

    @Ricky Spires

    Take a look a this code example to do password comparison. http://jsfiddle.net/ryanseddon/Ch2tg/2/

  26. Evan says:

    Hi Ryan,

    Thanks a ton for your work on this, it’s really good of you to MIT this for us all.

    I have to ask about what I’m seeing though:
    I loaded up the ALA Demo in IE8 to try ‘er out, but am only getting flashes of feedback when I try to submit non-validating entries. I see the red + icon for a split-second, then the IE loading bar goes, and then I’m back to a blank form. When I resubmit without refreshing, nothing happens at all. Is this something to do with it being a demo and not actually submitting to something?

    On that pont, does the script disable standard submission to do the validation and then send it asynchronously if it passes? Sorry if I’ve missed that in the docs somewhere. The use I have in mind for the script right now is a very plain-jane POST to a PHP handler and the request can’t be asynchronous because the handler needs to display output.

    I’m using a vanilla IE install on a VM that I do testing with. No errors get thrown.

    Thanks in advance for your reply!

  27. Ryan Seddon says:

    @Evan

    The ALA demo is way out of date, go check out the project on github.

  28. Alistair says:

    Doesn’t work in IE9 though. Even the demo (which I assume is set up correctky) submits without any validation checks

Leave a comment