A modular, promise-based form validation plugin, free of third-party dependencies and built on top of native HTML5 validation.
- Intro
- Installation
- Dependencies
- Usage
- Options
- Methods
- Helpful Hints
- Common Use Case Examples
- Configuring Error Messages
- Rules
- Writing Your Own Rule
- Browser Support
You're right; there's no shortage of form validation plugins to choose from. That said, this plugin was written to fulfill some specific needs and workflows that many other validation plugins lack, including:
- No third-party dependencies (especially jQuery)
- Written as an ES6 module.
- Extensible, to allow new validation rules (also written as ES6 modules) to be easily written, installed and bundled as needed.
- Built to progressively enhance HTML5 validation patterns whenever possible.
- Promise-based, asynchronous validation rules.
- The ability to add and remove fields from the validation instance on the fly.
- Customizable error messaging that can be set at the field, form or rule level, on a field-by-field basis.
formValidation is an ES6 module. Consequently, you might need an ES6 transpiler (Babel is a nice one) as part of your Javascript workflow.
If you're already using NPM for your project, you can install formValidation with the following command:
$ npm install @degjs/form-validation
formValidation rule modules that are hosted on GitHub or NPM can similarly be installed:
$ npm install @degjs/form-validation-required
formValidation doesn't rely on any third-party dependencies, but does make use of three small dependencies from its own DEGJS ecosystem. These dependencies will be automatically installed and configured if you install formValidation via NPM; however, if you install manually, you'll also need to manually include the domUtils, objectUtils and scrollTo DEGJS modules.
formValidation is designed to be modular and does not include any rules modules out of the box. Therefore, you'll need to import and instantiate all of the rule modules needed for your form.
Sample Javascript:
import formValidation from "@degjs/form-validation";
/* Import the Required and Pattern rule modules */
import pattern from "@degjs/form-validation-pattern";
import required from "@degjs/form-validation-required";
/* Configure the rules array alongside other validation options. Default rule settings can be overridden at the rule level during instantiation by calling the rule as a function and passing it an options array. */
const validationOptions = {
rules: [
pattern,
required({
message: 'You idiot! This field is required!'
})
]
};
/* Instantiate the formValidation module on an element */
const formElement = document.querySelector('.form');
const validationInst = formValidation(formElement, validationOptions);Sample Markup:
<form class="form">
<fieldset>
<div class="js-validation-field">
<label for="zip">ZIP Code</label>
<input type="text" required pattern="^\d{5}(-\d{4})?$" id="zip" name="zip">
</div>
<button type="submit">Submit</button>
</fieldset>
</form>Type: Array
Default: null
An array of rule module names that should be registered with the validation instance. Each rule in the array must be imported before being instantiated. The rule can be instantiated by listing its name only, or as a function call with options.
Type: String
Default: .js-validation-field
The CSS selector for each field element. NOTE: only fields containing this selector will be included in the validation instance on page load.
Type: String
Default: input, select, textarea
The CSS selector for each field's input elements.
Type: String
Default: validation-field__errors
The CSS class added to all error wrapper elements within each field. NOTE: An error wrapper element with this class is automatically added to fields that don't contain one.
Type: String
Default: validation-field__error
The CSS class added to individual errors, within each field's error wrapper element.
Type: String
Default: has-errors
The CSS class added to fields when an error is present.
Type: String
Default: js-validation-field--
formValidation automatically adds randomly generated IDs to fields that don't already have IDs (i.e., <div class="js-validation-field" id="js-validation-field--9695013748541">). This option changes the string that preceds the randomly generated number.
Type: String
Default: data-validation-field-id
The data attribute name added to inputs, which corresponds with the ID of its parent field.
Type: Boolean
Default: true
Scrolls the page to the first field containing an error when form validation fails. This may be useful on long forms. This option uses the DEGJS scrollTo module. More information on this dependency can be found here.
Type: Integer
Default: 500
Sets the scroll speed of the scrollToErrorOnFormSubmit option.
Type: String
Default: easeIn
Sets the easing effect of the scrollToErrorOnFormSubmit option.
Type: String
Default: Validation error.
Essentially a worst-case scenario error message, should something go wrong with error messages set at the field, form and rule level. You'll probably never see this, but it's configurable, just in case.
Type: Function
Default: null
Returns: Object {fieldsArr, event}
The name of the callback fired when a form's validation begins.
Type: Function
Default: null
Returns: Object {fieldsArr, event}
The name of the callback fired when a field's validation begins.
Type: Function
Default: null
Returns: Object {fieldsArr, event}
The name of the event fired when a form's validation passes.
Type: Function
Default: null
Returns: Object {fieldsArr, event}
The name of the event fired when a field's validation passes.
Type: Function
Default: null
Returns: Object {fieldsArr, event}
The name of the event fired when a form's validation fails.
Type: Function
Default: null
Returns: Object {fieldsArr, event}
The name of the event fired when a field's validation fails.
Parameters: els
An element or array of elements to add to the validation instance.
Parameters: els
An element or array of elements to remove from the validation instance.
Parameters: none
Removes all registered fields from the validation instance.
- If FormValidation is being used on your form, never try to write other validation functionality in tandem with it. FormValidation disables HTML5 form validation and swallows several form- and field-related events. Trying to write separate validation will cause unexpected side effects and make debugging very difficult. A better approach is to write a custom FormValidation rule to handle your additional validation. You can also leverage FormValidation's built-in events (
on[Form/Field]ValidationStart,on[Form/Field]ValidationSuccess, andon[Form/Field]ValidationError) to write custom behavior during the form's validation lifecycle. - When writing custom rules, avoid adding side effects to your rule whenever possible. An example of a side effect would be querying or mutating the DOM within the rule's
.isRelevant()or.validate()methods. Doing so makes the rule less reusable and can introduce validation behavior that's difficult to troubleshoot. Instead, place your custom logic within FormValidation's built-in event callbacks. - When writing a custom rule or configuring an existing rule's events, use
focusout(notblur) as your "input no longer focused" event (theblurevent doesn't bubble up and won't always work as expected).
import formValidation from "@degjs/form-validation";
const validationInst = formValidation();
const toggleElement = document.querySelector('.js-toggle');
// By giving the dependent fields their own class, and not using the default class,
// the field will not be automatically added to the validation instance on page load.
const dependentFields = [...document.querySelectorAll('.js-dependent-field')];
toggleElement.addEventListener('change', e => {
if (e.target.checked) {
validationInst.addFields(dependentFields);
} else {
validationInst.removeFields(dependentFields);
});<form>
<input class="js-toggle" type="checkbox">
<div class="js-dependent-field">
<input required />
</div>
</form>When an error occurs, formValidation follows a hierarchy to determine which error message to show. This allows fine-grained control over messaging on a field-by-field basis. Error messages will be chosen in the following order of importance:
- Messages set on the field element via a data attribute. The name of this attribute should be configurable within each rule, and is the same as the attribute at the form level.
- Messages set on the form element via a data attribute. The name of this attribute should be configurable within each rule, and is the same as the attribute at the field level.
- A default message set by default within each rule module. This message can also be overridden via the "message" property when instantiating the rule.
- A generic fallback message built into formValidation itself.
After the correct error message has been determined, it's still possible to process the message before it's displayed (this can be useful when replacing tokens or characters in a message based on a user's input, for example). This can be done via the postprocessMessage method within the rule itself, or overridden as a postprocessMessage option when instantiating a rule (see the "Writing Your Own Rule" documentation for more information).
Several prebuilt rule modules are available via DEGJS, including:
By following formValidation's rule API, you can also write your own rule module that's asynchronous, Promise-based and can fire on most common DOM events (including form submission, which uses Promise.all to validate all of a validation instance's rules at once).
A rule module must return the following methods:
.settings()
Parameters: none
Required: yes
Returns: Object
Must return an object containing the following properties:
- message (optional): the default message the rule will display
- messageAttr (required): the attribute that will be checked on the field and form elements for message overrides
- events (required): an array of DOM event names on which the rule should fire
.isRelevant(field)
Parameters: field
Required: yes
Returns: Boolean
Must return a boolean value indicating if the rule is relevant to an individual field.
.validate(matchingField)
Parameters: field
Required: yes
Returns: Promise
Perhaps counterintuitively, the validate method should return a resolve() method, regardless of whether validation passes or fails.
Rules that pass validation should return a valid: true property within the resolve method's returned object:
resolve({
valid: true
});Rules that fail validation should also resolve the promise, but return a valid: false property within the returned object:
resolve({
valid: false
});The validate method should only reject the promise when there is a problem with the rule (i.e., when the field passed to it doesn't contain any input elements).
.postprocessMessage(msg)
Parameters: msg
Required: no
Returns: String
Must return the message it's passed, optionally reformatted if needed. This method should also check for a settings.postprocessMessage function, which allows it to be overriden during rule instantiation.
const required = (options) => {
const defaults = {
message: 'This field is required.',
messageAttr: 'data-validation-required-message',
events: [
'focusout',
'submit'
]
};
let settings = Object.assign({}, defaults, options);
function getSettings() {
return settings;
}
function isRelevant(field) {
return field.inputEls.some(el => el.getAttribute('required') !== null);
}
function validate (field) {
return new Promise(function(resolve, reject) {
if (field.inputEls) {
resolve({
valid: field.inputEls.some(el => el.value.length > 0)
});
} else {
reject('required: No inputs set.');
}
});
}
function postprocessMessage(msg) {
if (settings.postprocessMessage && typeof settings.postprocessMessage === 'function') {
return settings.postprocessMessage(msg);
} else {
return msg;
}
}
return {
settings: getSettings(),
isRelevant: isRelevant,
validate: validate,
postprocessMessage: postprocessMessage
};
}
export default required;formValidation depends on the following browser APIs:
- Array.find: Documentation | Polyfill
- Array.filter: Documentation | Polyfill
- Array.prototype.some: Documentation | Polyfill
- Object.assign: Documentation | Polyfill
- Array.from: Documentation | Polyfill
- Array.prototype.reduce: Documentation | Polyfill
To support legacy browsers, you'll need to include polyfills for the above APIs.