The FormValidation.js
is a JavaScript form validation class that provides a flexible and customizable way to handle form validations. The library provides a set of features to handle form validation, including:
- A constructor that initializes form validation with a form selector, configuration options, and optional custom validation rules.
- Functions to sanitize input and prevent Cross-Site Scripting (XSS) attacks.
- Customizable field validation rules, messages, and configurations.
- Predefined validation methods and the ability to add custom validation methods.
- Error handling and visual feedback for invalid fields.
- Support for form submission handling with validation.
- Manual validation of the entire form or specific fields.
Note: This library is not a replacement for server-side validation. It is intended to be used as a client-side validation tool to provide a better user experience and reduce the number of requests to the server.
Just install it into your project using npm:
npm install @ltvco/form-validation
import { Validation } from '@ltvco/form-validation';
Then we can set up any configuration options we want to add to our form. This is just an example, but you can find a more in-depth explanation of each of the options in the Configuration Section.
const myConfig = {
fields: { // Configuration for each form field
fieldName: {
rules: ['rule1', 'rule2', 'rule3'], // Validation rules to add to the field
inputContainer: '.error-placement-selector', // [Optional] Where to add the errorClass
errorClass: '.custom-error', // [Optional] Error class to add to the inputContainer element
messages: {
rule1: 'This is a custom error message for rule1',
rule2: 'This is a custom error message for rule2',
rule3: 'This is a custom error message for rule3',
},
},
...
},
};
You can add custom validation rules by passing them as the third parameter in the constructor.
const myRules = {
customRule: {
validator: (field) => field.value.length > 5, // Custom validation function
message: 'This field must have more than 5 characters', // Custom error message
},
};
Finally, we can initialize the Validation object with a form element or selector, a configuration object, and an optional custom rules object:
const myValidation = new Validation('#myForm', myConfig, myRules);
And that's it, the form validation is now ready! Now there are a few other things that the library can do to further customize or help you validate in the way you need to.
You can also add or modify rules after initialization using the addMethod
function:
myValidation.addMethod(
'customRule2',
(field) => field.value !== '',
'This field cannot be empty'
);
If the rule already exists, you can also modify either the default message or the validation function.
Modify only the validator function of existing rule (without modifying message)
myValidation.addMethod('customRule2', (field) => field.value !== '');
Modify only the default message of existing rule (without modifying the validator)
myValidation.addMethod('customRule2', null, 'This field cannot be empty');
The component provides a way to change the rules a field has in case there's something that needs to change dynamically.
Add a rule to a field
myValidation.addFieldRule(
'fieldName',
'customRule',
'This is a custom error message for customRule'
);
Remove a rule to a field
myValidation.removeFieldRule('fieldName', 'customRule');
Replace the rules of a field
myValidation.setFieldRules('fieldName', ['customRule', 'customRule2'], {
customRule: 'This is a custom error message for customRule',
customRule2: 'This is a custom error message for customRule2',
});
Last, but not least, the validator also can manually validate the entire form or a specific field, and for both of those, the possibility of doing it silently (no error messages are shown) or explicitly (error messages are shown). By default, the validation is done silently. This section is explained in more detail in the Public Methods Section.
Check if all fields are valid
const isFormValid = myValidation.validateForm();
Check if a specific field is valid
const isFieldValid = myValidation.isFieldValid('myField');
- Fork this repository.
- Clicking the fork button at the top right of the page.
- Select your personal account as the owner for the fork.
- Uncheck the checkbox to keep all the branches in sync not just the main branch.
- Click the fork button.
- Clicking the fork button at the top right of the page.
- Go to your github account.
- Go to your repositories and clone you fork of
form-validation
repository using SSH.- Example:
git clone git@github.com:username/form-validation.git
- Username should be your github username.
- Example:
- Once inside your working environment, open the repository in your IDE
- Add the upstream repository as a remote:
git remote add upstream https://github.com/ltvco/form-validation
- Now upstream will refer to the original repository.
- Check that you have the remotes correctly configured:
git remote -v
- You should see something like this (with your username):
origin git@github.com:JohnDoe/form-validation.git (fetch)
origin git@github.com:JohnDoe/form-validation.git (push)
upstream https://github.com/ltvco/form-validation (fetch)
upstream https://github.com/ltvco/form-validation (push)
- Origin is your fork.
- Upstream is the original repository.
constructor(form, config, rules);
form
{HTMLElement | string}
: Form element or selector that will be validated.config
{Object}
: [Optional] Object containing fields to validate, which rules to apply, and other configurations.rules
{Object}
: [Optional] Custom rules to use in validation.
An instance with the validation.
Sets up all variables and configurations for validation to work on a form.
const config = {
validationFlags: ['onSubmit', 'onChange', 'onKeyUpAfterChange'],
submitCallback: (formObj) => console.log(formObj);
invalidHandler: (errors) => console.error(errors),
fields: {
field1: {
rules: ['rule1', 'rule2'],
inputContainer: '.error-placement-selector',
errorClass: '.custom-error-class',
validClass: '.custom-valid-class',
fieldErrorHandler: (field, message) => alert(`The input ${field.name} has an error: ${message}`),
fieldValidHandler: (field) => alert(`The input ${field.name} is valid!`),
messages: {
rule1: 'Custom error message for rule1.',
rule2: (field) => `rule2 validation has not been met for ${field.name}`,
}
}
},
}
Array with triggers that define when will the validator check for the validity of the fields.
Available Flags
Flag | Description |
---|---|
onChange |
[Default] Validates a field when the change event is triggered. |
onKeyUpAfterChange |
[Default] Works the same as the onKeyUp flag, however, it's added after the first time the field is changed. |
onSubmit |
[Default] Validates all fields before the form is submitted. |
onKeyUp |
Validates a field when the keyup event is triggered on the field. This is only added to inputs that can be written on. (text , date , etc.) |
Default Value
validationFlags: ['onChange', 'onKeyUpAfterChange', 'onSubmit'];
Usage
const myConfig = {
...
validationFlags: ['onKeyUp', 'onSubmit'],
...
}
Function to be called when the form is submitted successfully. It's important to mention that by default, the inputs will be sanitized before calling this function.
Available Parameters
Parameter | Type | Description |
---|---|---|
formObject |
Object |
An object containing all the key-value pairs of each of the inputs. The field's name will be the key and the value will be whatever the user set as input. |
form |
HTMLFormElement |
The reference to the form element where the validation is being applied. |
Default Value
submitCallback: (formObj, form) => form.submit();
Usage
const myConfig = {
...
submitCallback: function(formObject, form) {
form.classList.add('is-loading');
return fetch('http://my.api.com/url', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(formObject)
}).then((response) => {
console.log(response);
form.classList.remove('is-loading');
})
},
...
}
Function to be called when the form is submitted with errors.
Available Parameters
Parameter | Type | Description |
---|---|---|
errors |
Array<[HTMLElement, string]> |
An array containing all the errors found inside the form. Each element of the array is a tuple with the input as an HTMLElement and the errorMessage as a string . |
form |
HTMLFormElement |
The reference to the form element where the validation is being applied. |
Default Value
invalidHandler: () => {};
Usage
const myConfig = {
...
invalidHandler: function(errors, form) {
const formErrorsSection = form.querySelector('.js-error-section');
formErrorsSection.innerHTML = '';
errors.forEach([field, errorMessage] => {
const errorElement = document.createElement('li');
errorElement.innerHTML = errorMessage;
formErrorsSection.appendChild(errorElement);
});
},
...
}
An object where we configure each of the fields we want to validate, along with each of the rules we want to add to the field. The key
of each object must match the name
of an input.
Structure
const myConfig = {
...
fields: {
'zipCode': {
rules: ['required', 'characterAmount(5,9)', 'numbersOnly', ...],
messages: {
'required': 'Please enter a valid zip code.',
'characterAmount': function(field, ...args) { return `Zip code must have between ${args[0]} and ${args[1]} characters.` },
},
inputContainer: '.input-container',
errorClass: 'zip-error',
errorElement: 'span',
validClass: 'zip-valid',
fieldHandlerKeepFunctionality: true,
fieldErrorHandler: function(field, message, fieldConfig, form) {
...
},
fieldValidHandler: function(field, fieldConfig, form) {
...
},
},
'state': {
...
},
...
}
...
}
An array with all the rules to validate for each of the fields. The order of the array is important as well, as the validator will prioritize rules in ascending order, meaning the smaller the index, the higher priority it has.
Usage
const myConfig = {
...
fields: {
'fn': {
rules: ['required', ...],
...
},
...
}
...
}
An object containing any messages to be overwritten with a custom error message. Each object key
must match a rule
name. The value
can either be a string
or a function that resolves to a string
.
Something to keep in mind is that as of now, if the message is a function, the function will resolve when the Form Validator is initialized, and not each time the input is validated.
It's also important to know that the messages are added to the DOM as innerHTML
and not innerText
, so you can add custom styling and HTML
tags to the messages.
Usage
const myConfig = {
...
fields: {
'email': {
rules: ['required', 'validEmail', 'maxCharacterAmount(100)', ...],
messages: {
'required': 'Please enter a valid email address.',
'maxCharacterAmount': function(field, ...args) { return `Email address can't be over ${args[0]} characters long.` }
...
},
...
},
...
}
...
}
Boolean to define if the field is optional or not. If true
, the field will be marked as optional and won't be validated if it's empty. If false
, the field will be marked as required
and will be validated even if it's empty.
A few things to keep in mind:
- If no
optional
flag is passed and therequired
rule is not present, the field will be marked asoptional
. - If no
optional
flag is passed and therequired
rule is present, the field will be marked as notoptional
. - If an
optional
flag is passed as true and the rules also include therequired
rule, therequired
rule will have priority and the field will be marked as notoptional
instead. - Finally, if an
optional
flag is passed as false and therequired
rule is not present, therequired
rule will be automatically added.
Default value: false
Usage
const myConfig = {
...
fields: {
email: {
optional: true,
rules: ['validEmail', 'maxCharacterAmount(100)', ...],
messages: {
'required': 'Please enter a valid email address.',
'maxCharacterAmount': function(field, ...args) { return `Email address can't be over ${args[0]} characters long.` }
...
},
...
},
...
}
...
}
A string
with a selector matching one of the field's parents where the errorClass
will be added when the field is invalid. If the selector doesn't match any of the field's parents, the errorClass
will be placed on the parent element.
Default Value: input.parentElement
Usage
const myConfig = {
...
fields: {
'ln': {
inputContainer: '.input-container',
...
},
...
}
...
}
Function to be called when the error element is created. The function receives the input
element and the error
element (not yet attached to the DOM) as parameters. The function is expected to add the error
element to the DOM.
Available Parameters
Parameter | Type | Description |
---|---|---|
input |
HTMLElement |
The reference to the input element that throws the error. |
errorElement |
HTMLElement |
An HTMLElement with the error message that has yet to be added to the DOM. |
inputContainer |
HTMLElement |
The reference to the inputContainer element. |
form |
HTMLFormElement |
A reference to the form element where the validation is being applied. |
Default Value:
errorPlacement: (input, errorElement) => input.parentElement!.appendChild(errorElement),
Usage
const myConfig = {
...
fields: {
'fn': {
errorPlacement: (input, errorElement) => input.after(errorElement),
...
},
...
}
...
}
A string with a class that will be added to the inputContainer
element when the field is invalid. By default, the class error
is added when the field is invalid.
It's also important to note that the class error
will also be added to the input itself as well, however this class isn't modified by the errorClass
property.
Default Value: error
Usage
const myConfig = {
...
fields: {
'zipcode': {
errorClass: 'zip-error',
...
},
...
}
...
}
A string of the element tag in which the error will be displayed. An element of the type errorTag
will be created and placed as the last child of the inputContainer
element. It will always have the class error
.
Default Value: p
Usage
const myConfig = {
...
fields: {
'ln': {
errorTag: 'span',
...
},
...
}
...
}
A string of the class name that will be added to the inputContainer
element when the input
is valid. By default, the class valid
is added when the field is valid.
Like with the error
class, it's important to note that the class valid
will also be added to the input itself as well, however this class isn't modified by the validClass
property.
Default Value: 'valid'
Usage
const myConfig = {
...
fields: {
'fn': {
validClass: 'success',
...
},
...
}
...
}
This function serves the purpose of adjusting the value in any way before it's validated. This function is destructive so it will change the value of the input. If the function is present, it will be added to the onKeyDown
event to writeable inputs (text
, password
, email
, etc.), or onChange
to all other inputs before being validated.
Available Parameters
Parameter | Type | Description |
---|---|---|
value |
string |
The current value of the input to be normalized. |
input |
HTMLElement |
The reference to the input element. |
form |
HTMLFormElement |
A reference to the form element where the validation is being applied. |
Default Value:
normalizer: (value, input, form) => {},
Usage
const myConfig = {
...
fields: {
'vin': {
normalizer: (value, input, form) => value.toUpperCase(),
...
},
...
}
...
}
Function to be called when the field is validated and it has errors. There's 2 ways to use this function, either you can use it to completely replace the default functionality, or you can use it to add your own functionality on top of the default one. This is done with the fieldHandlerKeepFunctionality
property, which if true
will keep the default functionality, and if false
will replace it.
Available Parameters
Parameter | Type | Description |
---|---|---|
input |
HTMLElement |
The reference to the input throwing the error. |
message |
string |
Error message to be displayed. |
fieldConfig |
Object |
An object with the config options that this field was initialized with. |
form |
HTMLFormElement |
A reference to the form element where the validation is being applied. |
Default Functionality:
function onError(field, fieldConfig) {
this.errors.push([field, message]);
const { optional, errorClass, inputContainer, validClass } = fieldConfig;
// Here the 'fieldErrorHandler' would be called if present, and replace the default functionality if 'fieldHandlerKeepFunctionality' is set to false.
// if (fieldConfig.fieldErrorHandler) {
// fieldConfig.fieldErrorHandler(field, message, fieldConfig, this.form);
// return;
// }
// If there's no error element yet, create it.
if (!this.form.querySelector(`.${field.name}-error-element`)) this.createError(field, fieldConfig);
const inputParent =
typeof inputContainer === 'string' ?
(field.closest(inputContainer) as HTMLElement) :
inputContainer || (field.parentElement as HTMLElement);
if ((optional && field.value) || !optional) {
const errorElement = this.form.querySelector(`.${field.name}-error-element`) as HTMLElement;
errorElement.innerHTML = message;
errorElement.style.display = 'block';
inputParent.classList.add(errorClass || 'error');
field.classList.add('error');
}
// Remove Valid Class if present
inputParent.classList.remove(validClass || 'valid');
field.classList.remove('valid');
// Finally, if the 'fieldErrorHandler' is present and the 'fieldHandlerKeepFunctionality' is set to true, here it would be called, adding any extra functionality on top of the default one.
// if (fieldConfig.fieldErrorHandler && fieldConfig.fieldHandlerKeepFunctionality) {
// fieldConfig.fieldErrorHandler(field, message, fieldConfig, this.form);
// }
}
Usage
const myConfig = {
...
fields: {
'vin': {
fieldHandlerKeepFunctionality: true,
fieldErrorHandler: (input, message, fieldConfig, form) => {
// Focus input on error
input.focus();
// Add error class to form
form.classList.add('has-errors');
},
...
},
...
}
...
}
Function to be called when the field is validated and it passes all the rules. Similarly to the fieldErrorHandler
, there's 2 ways to use this function, either you can use it to completely replace the default functionality, or you can use it to add your own functionality on top of the default one. This is done with the fieldHandlerKeepFunctionality
property, which if true
will keep the default functionality, and if false
will replace it.
Available Parameters
Parameter | Type | Description |
---|---|---|
input |
HTMLElement |
The reference to the input element that passed validations. |
fieldConfig |
Object |
An object with the config options that this field was initialized with. |
form |
HTMLFormElement |
A reference to the form element where the validation is being applied. |
Default Functionality:
function onValid(field, fieldConfig) {
const { optional, errorClass, inputContainer, validClass } = fieldConfig;
this.errors = this.errors.filter(([errorField]) => errorField !== field);
// Here the 'fieldValidHandler' would be called if present, and replace the default functionality if 'fieldHandlerKeepFunctionality' is set to false.
// if (fieldConfig.fieldValidHandler) {
// fieldConfig.fieldValidHandler(field, fieldConfig, this.form);
// return;
// }
const inputParent =
typeof inputContainer === 'string' ?
(field.closest(inputContainer) as HTMLElement) :
inputContainer || (field.parentElement as HTMLElement);
const errorElement = this.form.querySelector(`.${field.name}-error-element`) as HTMLElement;
inputParent.classList.remove(errorClass || 'error');
field.classList.remove('error');
if (errorElement) errorElement.style.display = 'none';
if (!optional || (optional && field.value)) {
inputParent.classList.add(validClass || 'valid');
field.classList.add('valid');
} else {
inputParent.classList.remove(validClass || 'valid');
field.classList.remove('valid');
}
// Finally, if the 'fieldValidHandler' is present and the 'fieldHandlerKeepFunctionality' is set to true, here it would be called, adding any extra functionality on top of the default one.
// if (fieldConfig.fieldValidHandler && fieldConfig.fieldHandlerKeepFunctionality) {
// fieldConfig.fieldValidHandler(field, message, fieldConfig, this.form);
// }
}
Usage
const myConfig = {
...
fields: {
'vin': {
fieldHandlerKeepFunctionality: true,
fieldValidHandler: (input, fieldConfig, form) => {
// Remove error class to form
form.classList.remove('has-errors');
},
...
},
...
}
...
}
Boolean to define if the default functionality of the fieldErrorHandler
and fieldValidHandler
should be kept or not. If true
, the default functionality will be kept, and if false
it will be replaced.
Default Value: false
Usage
const myConfig = {
...
fields: {
'vin': {
fieldHandlerKeepFunctionality: true,
fieldErrorHandler: (input, message, fieldConfig, form) => {
// Focus input on error
input.focus();
// Add error class to form
form.classList.add('has-errors');
},
fieldValidHandler: (input, fieldConfig, form) => {
// Remove error class to form
form.classList.remove('has-errors');
},
...
},
...
}
...
}
There's also a way to add dynamic rules, where we can pass arguments to the rule's validator. This way we can have rules that differ in just a certain variable without having to have a rule for each of the variants.
So a use case for this is for example if we want a rule to limit the amount of characters inside a field. Then on our form, we have one field that can have a maximum amount of characters to 5
, and another one that can have a maximum amount of 10
characters. Instead of creating 2 different rules, we can have a single rule that takes in the number of characters as a parameter.
So the way this works is you define a rule like normal, but pass the parameters as a third argument when calling the validator
. This also applies to the message
, as the parameters will be passed to the function, but it's not a requirement.
{
...,
characterAmount: {
validator: function (element, value, ...args) {
const [min, max] = args;
return value.length >= parseInt(min) && value.length <= parseInt(max);
},
message: function (element, ...args) {
const [min, max] = args;
return `Please enter a minimum of ${min} and a maximum of ${max} characters.`;
},
},
...,
}
The way to use it is to call it with the wanted parameters in the rules
array in the configuration. In this case, it would be as follows:
const myConfig = {
...
fields: {
'zipcode': {
rules: ['characterAmount(5, 9)', ...],
},
...
}
...
}
The validator comes with a predefined amount of rules ready to be used.
Name | Description | Error Message |
---|---|---|
required |
Value must not be empty or contain only spaces | This field is required |
notEmail |
Value must not be an email | Email addresses are not searchable here |
validEmail |
Value must have a correct email format. | Please enter a valid email address in the format of example@test.com |
noSpecialCharacters |
Value must not contain any of the following characters: [$-/:-?{-~!"^_ []` |
Special characters are not allowed |
noEmptySpacesOnly |
Value must not be just empty spaces | Empty spaces are not allowed |
emptyOrLetters |
Value must either be empty or contain letters only | Alphabetic characters required |
onlyAlphanumeric |
Value must be alphabetic characters only | Only alphanumeric values are allowed |
phoneUS |
Value must have the structure of a common US phone | Please specify a valid phone number |
numbersOnly |
Value must contain numeric values only | Only numeric values are allowed |
lettersOnly |
Value must contain alphabetic values only | Only alphabetic characters are allowed |
characterAmount |
Value's length must be between min and max |
Please enter a minimum of min and a maximum of max characters |
maxCharacterAmount |
Value's length must be a maximum of max characters |
Please enter a maximum of max characters |
minCharacterAmount |
Value's length must be at least min characters long |
Please enter a minimum of min characters |
Function to check if all fields in the form are valid. This doesn't re-validate fields, it just checks if there are any errors in the form from the previous validation done, but if some of the fields have not been validated, it's better to use the validateForm
section.
Returns
boolean
: True
if all the fields are valid, false
otherwise.
Usage
if (myValidation.isValid()) {
console.log('The form is valid');
} else {
console.log('The form is invalid');
}
This function forces all the fields to be validated. A flag can be passed to display errors or not.
Parameters
Parameter | Type | Default | Description |
---|---|---|---|
silently |
boolean |
true |
[Optional] If the value is true, error messages won't be displayed, false means they will be displayed. |
Returns
boolean
: True
if all the fields are valid, false
otherwise.
Usage
const isFormValid = myValidation.validateForm();
This function forces a specific field to be validated. A flag can be passed to display errors or not.
Parameters
| Parameter | Type | Default | Description |
| ---------- | ------------ | ------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| field
| HTMLElement | string
| | [Required] The field to validate. Can be either an HTMLElement
or a string
with the name of the field. |
| silently
| boolean
| true
| [Optional] If the value is true, error messages won't be displayed, false means they will be displayed. |
Returns
boolean
: True
if the field is valid, false
otherwise.
Usage
const isFieldValid = myValidation.isFieldValid('myField');
This function adds a new validation rule or modifies an existing one. If you pass a name that already exists, the function will overwrite the existing rule.
Parameters
Parameter | Type | Description |
---|---|---|
name |
string |
[Required] The name of the validation rule. |
validator |
Function |
[Required] The function that validates the field. |
message |
string |
[Required] The message that will be displayed when the field is invalid. |
Usage
myValidation.addMethod(
'customRule2',
(field) => field.value !== '',
'This field cannot be empty'
);
This function sets the validation rules for a specific field. If the field already has rules, they will be overwritten.
Parameters
Parameter | Type | Description |
---|---|---|
fieldName |
string |
[Required] The name of the field. |
rules |
Array<string> |
[Required] An array of strings, where each string is the name of the validation rule. |
messages |
Object |
[Required] An object that defines the custom messages for each validation rule. |
Usage
myValidation.setFieldRules('fieldName', ['customRule', 'customRule2'], {
customRule: 'This is a custom error message for customRule',
customRule2: 'This is a custom error message for customRule2',
});
This function adds a validation rule to a specific field. If the field already has the rule, it will be overwritten.
Parameters
Parameter | Type | Description |
---|---|---|
fieldName |
string |
[Required] The name of the field. |
ruleName |
string |
[Required] The name of the validation rule. |
message |
string |
[Required] The custom message for the validation rule. |
Usage
myValidation.addFieldRule(
'fieldName',
'customRule3',
'This is a custom error message for customRule3'
);
This function removes a validation rule from a specific field.
Parameters
Parameter | Type | Description |
---|---|---|
fieldName |
string |
[Required] The name of the field. |
ruleName |
string |
[Required] The name of the validation rule. |
Returns
boolean
: True
if the rule was removed successfully, false
otherwise.
Usage
myValidation.removeFieldRule('fieldName', 'customRule3');