DEV Community

Yogesh Galav
Yogesh Galav

Posted on

Create Validation Plugin in Vue

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:

  1. Validation rules and parameter will be passed through directive.
  2. It should validate all input fields in document.
  3. It should validate with custom rule and return custom messages.
  4. It should provide property which contains all errors and can be altered.
  5. 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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
    );
  },
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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}
  }
]
Enter fullscreen mode Exit fullscreen mode

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);
},
Enter fullscreen mode Exit fullscreen mode

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);
  });
};
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode
  setValidationRules(ruleObject){
    return Object.assign(validationRules, ruleObject);
  },

  setValidationMessages(msgObject){
    return Object.assign(validationMessages, msgObject);
  },
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

Here we are doing three main things

  1. Replacing underscore and value preceding # from field name.
  2. 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.
  3. 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({});
Enter fullscreen mode Exit fullscreen mode

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

  1. Define a new hidden input field bind with same name and value.
<input type="hidden" name="phone_no" :value="phone_value">
Enter fullscreen mode Exit fullscreen mode
  1. 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">
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode
  1. 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;
  },
Enter fullscreen mode Exit fullscreen mode

I hope you enjoyed reading this.
Please give your love and support to my Github open source repositories.

Thank You and Have Nice Day!

Top comments (0)