Centralize Rules with Vuetify Reusable Validation

Vuetify provides rules attribute for you to validate with your custom validation to make it more flexible according to your need but writing it, again and again, is not the best practice by this you can set up the centralized rules validation where you can reuse the common rule. Be careful with using regex to slow TT.

Setup centralized rules validation

This is where we set up our base rules validation for Vuetify validation. You can see that we create validation functions with the rule name prefix to easily identify the rule validation and avoid conflict with the local component function. From the root project in the src folder create a rules folder containing index.js.

In ./src/rules/index.js

export default {
  /**
   * Rule validate required
   * @param {any} v the given value to validate
   * @param {string} label
   * @returns validate
   */
  ruleRequired: (v, label = null) => {
    return !!v || `The ${label ?? 'field'} is required`;
  },
  /**
   * Rule validate min length
   * @param {any} v the given value to validate
   * @param {number} length min length to check
   * @param {string} label
   * @returns validate
   */
  ruleMinLength: (v, length, label = null) => {
    return (
      String(v).length >= length ||
      `The ${label ?? 'field'} minimum characters is ${length}`
    );
  },
  /**
   * Rule validate max length
   * @param {any} v the given value to validate
   * @param {number} length max length to check
   * @param {string} label
   * @returns validate
   */
  ruleMaxLength: (v, length, label = null) => {
    return (
      String(v).length <= length ||
      `The ${label ?? 'field'} maximum characters is ${length}`
    );
  },
  /**
   * Rule validate email
   * @param {any} v the given value to validate
   * @param {string} label
   * @returns validate
   */
  ruleEmail: (v, label = null) => {
    return (
      !v ||
      /^[a-zA-Z0-9-_.]+@[a-zA-Z0-9-_.]+(\.\w{2,3})+$/.test(String(v)) ||
      `The ${label ?? 'field'} must be a valid email`
    );
  },
  /**
   * Rule validate phone number
   * @param {any} v the given value to validate
   * @param {string} label
   * @returns validate
   */
  ruleTelephone: (v, label = null) => {
    return (
      !v ||
      /^([0-9\\-])*$/.test(String(v)) ||
      `The ${label ?? 'field'} must be a valid phone number`
    );
  },
  /**
   * Rule validate decimal
   * @param {any} v the given value to validate
   * @param {string} label
   * @param {number} decimalPlace the decimal place to check
   * @param {boolean} checkOverDecimalOnly to check only over decimal place only
   * @returns validate
   */
  ruleDecimal: ({
    v,
    label = null,
    decimalPlace = 2,
    checkOverDecimalOnly = true,
  }) => {
    const regex = new RegExp(
      `^(0|[1-9]\\d*)((\\.)${checkOverDecimalOnly ? '?' : ''}\\d{${
        checkOverDecimalOnly ? '0,' : ''
      }${decimalPlace}})$`
    );
    if (v && !v.toString().replace(/\s/g, '').match(regex)) {
      return `The ${
        label ?? 'field'
      } must be a valid decimal with ${decimalPlace} fraction`;
    }
    return true;
  },
  /**
   * Rule validate number less than
   * @param {any} v the given value to validate
   * @param {any} targetValue the target value to check againt
   * @param {string} label
   * @returns validate
   */
  ruleLessThan: (v, targetValue, label = null) => {
    return (
      !v ||
      !targetValue ||
      parseFloat(v) < parseFloat(targetValue) ||
      `The ${label ?? 'field'} must less than ${targetValue}`
    );
  },
  /**
   * Rule validate number greater than
   * @param {any} v the given value to validate
   * @param {any} targetValue the target value to check againt
   * @param {string} label
   * @returns validate
   */
  ruleGreaterThan: (v, targetValue, label = null) => {
    return (
      !v ||
      !targetValue ||
      parseFloat(v) > parseFloat(targetValue) ||
      `The ${label ?? 'field'} must greater than ${targetValue}`
    );
  },
  /**
   * Rule validate integer number
   * @param {any} v the given value to validate
   * @param {string} label
   * @returns validate
   */
  ruleNumber: (v, label = null) => {
    return (
      Number.isInteger(Number(v)) ||
      `The ${label ?? 'field'} must be a valid integer`
    );
  },
  /**
   * Rule validate date before date
   * @param {any} v the given value to validate
   * @param {any} targetValue the target value to check againt
   * @param {string} label
   * @returns validate
   */
  ruleBeforeDate: (v, targetValue, label = null) => {
    return (
      !v ||
      !targetValue ||
      v < targetValue ||
      `The ${label ?? 'field'} must before ${targetValue}`
    );
  },
  /**
   * Rule validate date after date
   * @param {any} v the given value to validate
   * @param {any} targetValue the target value to check againt
   * @param {string} label
   * @returns validate
   */
  ruleAfterDate: (v, targetValue, label = null) => {
    return (
      !v ||
      !targetValue ||
      v > targetValue ||
      `The ${label ?? 'field'} must after ${targetValue}`
    );
  },
  /**
   * Rule validate is
   * @param {any} v the given value to validate
   * @param {any} targetValue the target value to check againt
   * @param {string} label
   * @returns validate
   */
  ruleIs: (v, targetValue, label = null) => {
    return (
      !v ||
      !targetValue ||
      v === targetValue ||
      `The ${label ?? 'field'} must be ${targetValue}`
    );
  },
};

Integrate rules to global function with mixin

This is to integrate the rules to the Vue project as a global rules validation that can easily use in any components without importing the base rules above. To integrate it we use Vue mixin to inject it into the application. In the src folder create a mixins folder containing index.js. Import the base rules and spread them to the methods object.

In ./src/mixins/index.js

import baseRules from '@/rules';

export default {
  methods: {
    ...baseRules,
  },
};

Inject mixins to Vue application.

In ./src/main.js

import Vue from 'vue';
import App from './App.vue';
import vuetify from './plugins/vuetify';
import router from './routes';
import mixins from './mixins';

Vue.config.productionTip = false;
Vue.mixin(mixins);

new Vue({
  router,
  vuetify,
  render: (h) => h(App),
}).$mount('#app');

Sample usage of the global rules

In any component that you want to validate the Vuetify input simply called the rule name directly with the custom label, and parameters according to your need. For some rules if you pass only the value to validate, you can just call the rule directly without using the arrow function it will know to put the value to validate in the first parameter. See the example below.

<template>
  <v-container>
    <v-row class="text-center">
      <!-- Validate with default error message -->
      <v-col cols="12">
        <v-text-field
          label="Email"
          v-model="email"
          :rules="[
            ruleRequired,
            (v) => ruleMinLength(v, 8),
            (v) => ruleMaxLength(v, 50),
            ruleEmail,
          ]"
        ></v-text-field>
      </v-col>
      <v-col cols="12">
        <v-text-field
          label="Telephone"
          v-model="telephone"
          :rules="[
            (v) => ruleMinLength(v, 8),
            (v) => ruleMaxLength(v, 20),
            ruleTelephone,
          ]"
        ></v-text-field>
      </v-col>
      <!-- Validate with custom label error message -->
      <v-col cols="12">
        <v-text-field
          label="Decimal"
          v-model="decimal"
          :rules="[(v) => ruleDecimal({ v, label: 'Decimal' })]"
        ></v-text-field>
      </v-col>
      <v-col cols="6">
        <v-text-field
          label="Min"
          v-model="min"
          :rules="[(v) => ruleLessThan(v, max, 'Min')]"
        ></v-text-field>
      </v-col>
      <v-col cols="6">
        <v-text-field
          label="Max"
          v-model="max"
          :rules="[(v) => ruleGreaterThan(v, min, 'Max')]"
        ></v-text-field>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      email: null,
      telephone: null,
      decimal: null,
      min: null,
      max: null,
    };
  },
};
</script>

Sample usage of the import rules

In case you don’t want to integrate rules validation as global skip the step “Integrate rules to global function with mixin” and import manually the rules into your component where you want to validate with the centralized rules and spread it to the data object as the example below.

<template>
  <v-container>
    <v-row class="text-center">
      <!-- Validate with default error message -->
      <v-col cols="12">
        <v-text-field
          label="Email"
          v-model="email"
          :rules="[
            ruleRequired,
            (v) => ruleMinLength(v, 8),
            (v) => ruleMaxLength(v, 50),
            ruleEmail,
          ]"
        ></v-text-field>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import baseRules from '@/rules';

export default {
  name: 'HelloWorld',
  data() {
    return {
      ...baseRules,
      email: null,
    };
  },
};
</script>