Create Validation Plugin in Vue
Yogesh Galav
Posted on January 1, 2023
From long time me and my team were using Vee-Validate plugin for validating form inputs fields, But after the breaking changes in version 3, I decided to create Validation plugin which would behave in same way.
If you wanna directly jump to plugin here's the link
https://github.com/yogeshgalav/vue-nice-validate
https://www.npmjs.com/package/vue-nice-validate
This blog will only focus on building validation package if you want to understand how to create and publish basic package in VueJs, Click here
Now before writing the code let's discuss the feature it would consist.
Features:
- Validation rules and parameter will be passed through directive.
- It should validate all input fields in document.
- It should validate with custom rule and return custom messages.
- It should provide property which contains all errors and can be altered.
- It should be able to validate components with no input tag(For eg. vue-otp-input, vue-tel-input).
Let's start with index file. Here's what it looks like
import VueNiceValidate from "./VueNiceValidate";
export default {
install: (app, options) => {
app.config.globalProperties.$validator = VueNiceValidate;
app.directive("validate", VueNiceValidate.ValidateDirective);
}
};
export const fieldErrors = VueNiceValidate.field_errors;
export const validateDirective = VueNiceValidate.validateDirective;
export const validateForm = VueNiceValidate.validateForm;
export const validateInputs = VueNiceValidate.validateInputs;
export const validateInput = VueNiceValidate.validateInput;
export const addField = VueNiceValidate.addField;
It exports all the things you basically need. By default it exports a plugin(Object with install fn) which can be globally used. Here's how you can use this plugin in your code
import ValidatePlugin from 'vue-nice-validate';
const app = createApp(App);
app.use(ValidatePlugin);
In other words it's just a combination of other named exports, If you want to use them individually and locally in any component use like this
import { validateInputs, validateDirective, fieldErrors } from 'vue-nice-validate';
const vValidate = validateDirective;
So now you know what this index file is all about. Now let's jump to our main file VueNiceValidate.js and implement the functionality one by one.
Feature 1: Validation rules and parameter will be passed through directive.
We want to define a custom directive which would be used on input fields and which would accept validation rules as bindings.
v-validate="'required|max:5'"
According to Vue docs for defining a custom directive we need to define a function which accepts the element and bindings.
validateDirective(el, binding) {
setFormFieldData(
el.getAttribute("name"),
binding.value
);
},
Here we will set all the data in good form to validate input field with it's name and rules. Let's define this setFormFieldData
var form_fields = [];
function setFormFieldData(fieldName, rules) {
let validation_rules = {};
rules.split("|").map((node) => {
let ru = node.split(":");
validation_rules[ru[0]] = ru[1] ? ru[1] : true;
});
let already_present_field =
form_fields.find(el=>el.field_name===fieldName);
if(already_present_field){
already_present_field.rules = validation_rules;
return true;
}
form_fields.push({
field_name: fieldName,
rules: validation_rules,
});
return true;
};
This function formats the validation rule into object and then set all information into global array form_fields which contain field's name attribute and rule object. It will have value something like this
[
{
'field_name':'phone_no',
'rules':{required:true, max:5}
}
]
Till now we have only created data containing input name and rule. To complete it's validation we need to implement feature 2.
Feature 2: It should validate all input fields in document.
As we have already exported validateForm function from index.js let's define it along with a private function runValidation.
validateForm(FormName=''){
const to_be_validated_fields = form_fields;
return runValidation(to_be_validated_fields);
},
This runValidation function iterates over fields passed in parameter and validate them against given rule. And if some field doesn't pass it transfers the job of preparing validation message to setFieldError().
function runValidation(to_be_validated_fields){
//run validation and add error to form_errors
return new Promise((resolve, reject) => {
form_errors.length = 0;
try{
to_be_validated_fields.forEach((field)=>{
for (const [rule_name, rule_parameter] of Object.entries(field.rules)) {
let field_element = document.getElementsByName(field.field_name)[0];
//continue if field not found during validation
if(!field_element) continue;
let field_value = field_element.value;
let form_error = {
'field_name':field.field_name,
'rule_name':rule_name,
'rule_param':rule_parameter,
'form_name':field.form_name,
};
if(!validationRules[rule_name](field_value, rule_parameter)){
form_errors.push(form_error);
setFieldError(form_error);
}else{
setFieldError(form_error, true);
}
}
});
}catch(e){
reject(e);
}
if(form_errors.length){
resolve(false);
}
resolve(true);
});
};
We call validateForm from our app and expects a promise which resolves with true if all field passes and resolve with false if any one fails. And if some error occurs like field not found then it rejects with error.
YourProject.vue
validateForm().then(result=>{
if(result===true) console.log('validation passed');
if(result===false) console.log('validation failed');
}).catch(e=>console.error('field not found',e));
Feature 3: It should validate with custom rule and return custom messages.
For defining validation rules we are importing Object from other file containing functions with name of rule.
And similarly for validation message we are importing object containing key value pair of rule and it's message.
VueNiceValidate.js
import validationRules from './validationRules.js';
import validationMessages from './validationMessages.js';
setValidationRules(ruleObject){
return Object.assign(validationRules, ruleObject);
},
setValidationMessages(msgObject){
return Object.assign(validationMessages, msgObject);
},
Now from our app we can simply call setValidationRules or with object of rules or setValidationMessages with object of messages.
We have already used validationRule object in our runValidation function now lets see how validationMessages is used in setFieldError.
function setFieldError(form_error,clear=false){
//get current field errors from form_errors
let key = form_error.field_name;
if(clear==true){
field_errors[key] = '';
return false;
}
let field_name = form_error.field_name;
field_name=field_name.replace(/_/g, ' ').split('#')[0];
let val = validationMessages[form_error.rule_name]
.replace(':attribute', field_name)
.replace(':param', form_error.rule_param);
field_errors[key] = val;
return true;
};
Here we are doing three main things
- Replacing underscore and value preceding # from field name.
- Replacing :attribute with field name and :param with rule param. Hence while defining your own message just write :attribute and :param to be replaced respectively.
- Finding message for failed rule and pushing it to field_errors with field name as key.
Feature 4: It should provide property which contains all errors and can be altered.
As we have already set field_errors above it can be used inside our app directly and we should also be able to manipulate it in case we want to remove specific or all error or want to add server error.
There's just one important thing to not down if you are using vue3. we need to make this field_errors property reactive so changes are observed in our app.
const field_errors = reactive({});
Feature 5: It should be able to validate components with no input tag.
Many time the validation becomes a headache for custom components or packages like vue-otp-input and vue-tel-input.
These packages doesn't have either name or value or appear as simple div when rendered in dom. Hence our package is not able to identify it. There are 3 possible solution to this issue
- Define a new hidden input field bind with same name and value.
<input type="hidden" name="phone_no" :value="phone_value">
- Define custom attribute validation-name or validation-value on our custom or third party component.
<custom-input validation-name="phone_no" :validation-value="phone_value">
For this we also need to modify our runValidation fn a little bit
let field_element = document.getElementsByName(field.field_name)[0] || document.querySelector('[validation-name="'+field.field_name+'"]');
let field_value = field_element.value || field_element.getAttribute('validation-value');
- Directly add a field object to our form_fields variable in package.
addField(field,validation_rules){
if(form_fields.some(el=>el.field_name===field)) return false;
form_fields.push({
'field_name':field,
'rules':validation_rules
});
return true;
},
I hope you enjoyed reading this.
Please give your love and support to my Github open source repositories.
Thank You and Have Nice Day!
Posted on January 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.