- Key Takeaways
- Intercepting Form Submissions
- The Field .willValidate Property
- Does the Browser Support the Input Type?
- The Field .checkValidity() Method
- The Field .validity Object
- Supporting .validity in Older Browsers
- The Field .setCustomValidity() Method
- Putting it all Together
- Frequently Asked Questions (FAQs) about HTML5 Forms and JavaScript Constraint Validation API
Key Takeaways
- HTML5 allows for client-side form validation without JavaScript coding, but for more sophisticated forms, JavaScript and the Constraint Validation API can be used to enhance native validation. This is due to limitations such as not all browsers supporting all HTML5 input types and CSS selectors, and the difficulty of styling error message bubbles.
- The Constraint Validation API provides methods and properties such as .willValidate, .checkValidity(), .validity, and .setCustomValidity(). These can be used to check if a field will be validated, validate a field, check the validity of a field, and set custom validity messages respectively. However, not all properties are supported in all browsers.
- A simple, generic cross-browser form validation system can be created using JavaScript and the Constraint Validation API. This system includes disabling native validation, looping through all fields to check if native validation is available and if the input type is supported, checking the validity of fields, and setting custom validity messages. This system can be adapted to support older browsers and different input types.
- not all browsers support all HTML5 input types and CSS selectors
- error message bubbles use generic text (‘please fill out this field’) and are difficult to style
:invalid
and:required
styles are applied on page load before the user interacts with the form.
Intercepting Form Submissions
Before HTML5, client-side validation involved attaching asubmit
handler to the form which would validate the fields, display errors and prevent the submit event.
In HTML5, the browser will perform its own validation first — the submit
event will not fire until the form is valid. Therefore, if you want to do something sophisticated such as displaying your own errors, comparing or auto-filling fields, you must switch off native validation by setting the form’s noValidate
property to true:
var form = document.getElementById("myform");
form.noValidate = true;
// set handler to validate the form
// onsubmit used for easier cross-browser compatibility
form.onsubmit = validateForm;
Of course, this means you must check for field errors in code, but it is still possible to leverage native browser validation as we’ll see shortly.
The Field .willValidate Property
Every input field has a.willValidate
property. This returns:
- true when the browser will natively validate the field
- false when the browser will not validate the field, or
- undefined when the browser does not support native HTML5 validation, e.g. IE8.
validateForm
handler which loops through all fields and checks whether native validation is available:
function validateForm(event) {
// fetch cross-browser event object and form node
event = (event ? event : window.event);
var
form = (event.target ? event.target : event.srcElement),
f, field, formvalid = true;
// loop all fields
for (f = 0; f < form.elements; f++) {
// get field
field = form.elements[f];
// ignore buttons, fieldsets, etc.
if (field.nodeName !== "INPUT" && field.nodeName !== "TEXTAREA" && field.nodeName !== "SELECT") continue;
The loop iterates through all fields in the form’s elements
collection and checks they’re inputs rather than other types such as buttons and fieldsets. The next line is important…
// is native browser validation available?
if (typeof field.willValidate !== "undefined") {
// native validation available
}
else {
// native validation not available
}
Both false and undefined are falsey values so you cannot check just field.willValidate
!
We now know code inside the first block will evaluate when native validation can be used. However…
Does the Browser Support the Input Type?
If you read part one, you’ll recall that unsupported input types fall back totext
. For example:
<input type="date" name="dob" />
is not natively supported in Firefox 29 or IE11. Those browsers will (effectively) render:
<input type="text" name="dob" />
BUT both browsers support validation for text
types so field.willValidate
will NOT return undefined! We must therefore check that our type
attribute matches the object’s .type
property — if they don’t match, we need to implement legacy fall-back validation, e.g.
// native validation available
if (field.nodeName === "INPUT" && field.type !== field.getAttribute("type")) {
// input type not supported! Use legacy JavaScript validation
}
The Field .checkValidity() Method
If native validation is available, the.checkValidity()
method can be executed to validate the field. The method returns true if there are no issues or false otherwise.
There is a similar .reportValidity()
method which returns the current state without re-checking although this is less useful and not supported in all browsers.
Both methods will also:
- set the field’s
.validity
object so errors can be inspected in more detail and - fire an
invalid
event on the field when validation fails. This could be used to show errors, change colors etc. Note there is no correspondingvalid
event so remember to reset error styles and messages if necessary.
The Field .validity Object
The.validity
object has the following properties:
.valid
– returns true if the field has no errors or false otherwise.
.valueMissing
– returns true if the field is required and but value has been entered.
.typeMismatch
– returns true if the value is not the correct syntax, e.g. a badly-formatted email address.
.patternMismatch
– returns true if the value does not match the pattern
attribute’s regular expression.
.tooLong
– returns true if the value is longer than the permitted maxlength
.
.tooShort
– returns true if the value is shorter than the permitted minlength
.
.rangeUnderFlow
– returns true if the value is lower than min
.
.rangeOverflow
– returns true
if the value is higher than max
.
.stepMismatch
– returns true if the value does not match the step
.
.badInput
– returns true if the entry cannot be converted to a value.
.customError
– returns true if the field has a custom error set.
Not all properties are supported in all browsers so be wary about making too many assumptions. In most cases, .valid
or the result of .checkValidity()
should be enough to show or hide error messages.
Supporting .validity in Older Browsers
You can manually emulate the.validity
object in legacy browsers, e.g.
// native validation not available
field.validity = field.validity || {};
// set to result of validation function
field.validity.valid = LegacyValidation(field);
This ensures .validity.valid
can be tested in all browsers.
The Field .setCustomValidity() Method
The.setCustomValidity()
method can either be passed:
- an empty string. This sets the field as valid so
.checkValidity()
and.validity.valid
will return true, or - a string containing an error message which will be shown in the message bubble (if used). The message also flags the field as failing so
.checkValidity()
and.validity.valid
will return false and theinvalid
event will fire.
.validationMessage
property.
Putting it all Together
We now have the basis of a simple, generic cross-browser form validation system:var form = document.getElementById("myform");
form.noValidate = true;
// set handler to validate the form
// onsubmit used for easier cross-browser compatibility
form.onsubmit = validateForm;
function validateForm(event) {
// fetch cross-browser event object and form node
event = (event ? event : window.event);
var
form = (event.target ? event.target : event.srcElement),
f, field, formvalid = true;
// loop all fields
for (f = 0; f < form.elements; f++) {
// get field
field = form.elements[f];
// ignore buttons, fieldsets, etc.
if (field.nodeName !== "INPUT" && field.nodeName !== "TEXTAREA" && field.nodeName !== "SELECT") continue;
// is native browser validation available?
if (typeof field.willValidate !== "undefined") {
// native validation available
if (field.nodeName === "INPUT" && field.type !== field.getAttribute("type")) {
// input type not supported! Use legacy JavaScript validation
field.setCustomValidity(LegacyValidation(field) ? "" : "error");
}
// native browser check
field.checkValidity();
}
else {
// native validation not available
field.validity = field.validity || {};
// set to result of validation function
field.validity.valid = LegacyValidation(field);
// if "invalid" events are required, trigger it here
}
if (field.validity.valid) {
// remove error styles and messages
}
else {
// style field, show error, etc.
// form is invalid
formvalid = false;
}
}
// cancel form submit if validation fails
if (!formvalid) {
if (event.preventDefault) event.preventDefault();
}
return formvalid;
}
// basic legacy validation checking
function LegacyValidation(field) {
var
valid = true,
val = field.value,
type = field.getAttribute("type"),
chkbox = (type === "checkbox" || type === "radio"),
required = field.getAttribute("required"),
minlength = field.getAttribute("minlength"),
maxlength = field.getAttribute("maxlength"),
pattern = field.getAttribute("pattern");
// disabled fields should not be validated
if (field.disabled) return valid;
// value required?
valid = valid && (!required ||
(chkbox && field.checked) ||
(!chkbox && val !== "")
);
// minlength or maxlength set?
valid = valid && (chkbox || (
(!minlength || val.length >= minlength) &&
(!maxlength || val.length <= maxlength)
));
// test pattern
if (valid && pattern) {
pattern = new RegExp(pattern);
valid = pattern.test(val);
}
return valid;
}
The LegacyValidation
is purposely left simple; it checks required
, minlength
, maxlength
and pattern
regular expressions, but you’ll need additional code to check for emails, URLs, dates, numbers, ranges, etc.
Which leads to the question: if you’re writing field validation code for legacy browsers, why bother using the native browser APIs? A very good point! The code above is only necessary if you wanted to support all browsers from IE6 up and offer a similar user experience. That wouldn’t always be necessary…
- You may not require any JavaScript code for simple forms. Those using legacy browsers could fall back to server-side validation — which should always be implemented.
- If you require more sophisticated forms but only need to support the latest browsers (IE10+), you can remove all the legacy validation code. You would only require additional JavaScript if your forms used dates which aren’t currently supported in Firefox and IE.
- Even if you do require code to check for fields such as emails, numbers etc. in IE9 and below, keep it simple and remove it once you cease to support those browsers. It’s a little messy now, but the situation will improve.
Frequently Asked Questions (FAQs) about HTML5 Forms and JavaScript Constraint Validation API
What is the JavaScript Constraint Validation API and how does it work with HTML5 forms?
The JavaScript Constraint Validation API is a set of methods and properties provided by the browser to validate HTML5 forms. It works by checking whether the data entered by the user meets certain criteria specified in the form’s attributes. For example, if a form field is marked as ‘required’, the API will check if the user has entered a value before the form can be submitted. If the validation fails, the browser will display an error message and prevent the form from being submitted.
How can I use the checkValidity() method in the Constraint Validation API?
The checkValidity() method is used to check if all the form fields meet the validation criteria. It returns a boolean value – true if all fields are valid, and false if at least one field is invalid. Here’s an example of how to use it:var form = document.getElementById('myForm');
if (form.checkValidity()) {
// form is valid, proceed with submission
} else {
// form is invalid, display error message
}
What is the difference between the checkValidity() and reportValidity() methods?
While both methods are used for form validation, they behave slightly differently. The checkValidity() method simply checks if the form is valid and returns a boolean value. On the other hand, the reportValidity() method not only checks the form’s validity, but also displays the browser’s built-in error messages if the form is invalid.
How can I customize the error messages displayed by the Constraint Validation API?
You can customize the error messages by using the setCustomValidity() method. This method takes a string as an argument, which will be the custom error message displayed when the form is invalid. Here’s an example:var emailField = document.getElementById('email');
emailField.setCustomValidity('Please enter a valid email address.');
What are the different properties provided by the ValidityState object in the Constraint Validation API?
The ValidityState object provides several properties that indicate different validation states. These include valueMissing (true if the field is required and empty), typeMismatch (true if the value doesn’t match the field’s type), patternMismatch (true if the value doesn’t match the field’s pattern attribute), and several others.
How can I use the Constraint Validation API with different types of form fields?
The Constraint Validation API can be used with various types of form fields, including text fields, email fields, number fields, and more. The validation criteria can be specified using attributes like ‘required’, ‘pattern’, ‘min’, ‘max’, etc. The API will then check if the user’s input meets these criteria.
Can I use the Constraint Validation API with forms that use AJAX?
Yes, the Constraint Validation API can be used with AJAX forms. You can call the checkValidity() or reportValidity() method before sending the AJAX request to ensure the form is valid.
How can I handle form validation for multiple forms on the same page?
You can handle validation for multiple forms by assigning a unique ID to each form and using the getElementById() method to access each form. You can then call the checkValidity() or reportValidity() method on each form separately.
Can I use the Constraint Validation API with older browsers that don’t support HTML5?
The Constraint Validation API is part of HTML5 and may not be fully supported by older browsers. However, you can use JavaScript libraries like Modernizr to detect if the browser supports the API and provide a fallback if it doesn’t.
How can I test if my form validation is working correctly?
You can test your form validation by entering different types of data into the form fields and checking if the correct error messages are displayed. You can also use the console.log() method to log the result of the checkValidity() or reportValidity() method and check if it matches the expected result.
Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.