Skip to content

visaruruqi/oop-validator

Repository files navigation

oop-validator

npm version License: MIT version

A class-based validation library for JavaScript and TypeScript. Stack rules on individual fields, validate entire forms at once, and get structured error output — in any framework or none at all. Ships with full TypeScript type declarations.

Key Features

  • Class-based rules — each rule implements IValidationRule, making them easy to extend, compose, and test independently
  • Framework-agnostic core — works in Node.js, React, Angular, or vanilla JS with zero peer dependencies
  • Optional Vue 3 composablesuseValidation, useFormValidation, and useForm for reactive form state out of the box
  • AngularJS-style Vue directivesv-required, v-minlength, v-maxlength, v-pattern, v-min, v-max, v-type, v-messages, v-submit, and more via VueValidationPlugin
  • Form-level validationFormValidationEngine validates all fields at once and returns per-field errors plus a flat summary
  • Custom error messages — override the default error message per rule, per field
  • 20 built-in rules — required, min/max length, email, phone, URL, credit card, password strength, and more

Installation

npm install oop-validator

The package ships two entry points:

Import path Contents Vue required?
oop-validator Core rules + engines (framework-agnostic) No
oop-validator/vue Composables + directives + plugin Yes (peer dep)
// Core — works in any environment
import { ValidationEngine, FormValidationEngine } from 'oop-validator'

// Vue layer — composables, directives, plugin
import { useForm, useFormValidation, VueValidationPlugin } from 'oop-validator/vue'

Quick Start

Validate a single value:

import { ValidationEngine } from 'oop-validator';

const engine = new ValidationEngine(['required', 'email']);

const result = engine.validateValue('user@example.com');
console.log(result.isValid); // true
console.log(result.errors);  // []

const invalid = engine.validateValue('not-an-email');
console.log(invalid.isValid); // false
console.log(invalid.errors);  // ['This field must be a valid email address.']

Validate a whole form:

import { FormValidationEngine } from 'oop-validator';

const engine = new FormValidationEngine({
  email:    ['required', 'email'],
  password: ['required', { rule: 'min', params: { length: 8 } }],
});

const result = engine.validate({
  email:    'user@example.com',
  password: 'short',
});

console.log(result.isValid);      // false
console.log(result.fieldErrors);
// { email: [], password: ['This field must be at least 8 characters long.'] }

The core library is framework-agnostic — works in Node.js, React, Angular, or any environment. Vue composables and directives are available separately via oop-validator/vue and only require Vue as a peer dependency.

Table of Contents


Pure JavaScript API

The core library works in any JavaScript environment — vanilla JS, any framework, or server-side Node.js. All validation methods return a consistent shape:

  • validateValue(){ isValid: boolean, errors: string[] }
  • validate() (form) → { isValid: boolean, fieldErrors: Record<string, string[]>, summary: string[] }

Basic Validation Engine

Use ValidationEngine for validating individual fields or values. Perfect for real-time field validation, search inputs, or any single-value validation.

import { ValidationEngine } from 'oop-validator';

const productRules = [
  'required',
  { rule: 'min', params: { length: 3 }, message: 'Product name must be at least 3 characters.' },
  { rule: 'max', params: { length: 50 }, message: 'Product name cannot exceed 50 characters.' },
];

const productValidation = new ValidationEngine(productRules);

// ❌ Empty value — caught by required rule
const emptyResult = productValidation.validateValue('');
console.log(emptyResult.isValid); // false
console.log(emptyResult.errors);  // ['This field is required.', 'Product name must be at least 3 characters.']

// ❌ Too short — caught by min rule
const shortResult = productValidation.validateValue('AB');
console.log(shortResult.isValid); // false
console.log(shortResult.errors);  // ['Product name must be at least 3 characters.']

// ✅ Valid
const validResult = productValidation.validateValue('Premium Coffee Beans');
console.log(validResult.isValid); // true
console.log(validResult.errors);  // []

// --- Email ---
const emailValidation = new ValidationEngine(['required', 'email']);

// ❌ Invalid format
const badEmail = emailValidation.validateValue('not-an-email');
console.log(badEmail.isValid); // false
console.log(badEmail.errors);  // ['This field must be a valid email address.']

// ✅ Valid
const goodEmail = emailValidation.validateValue('user@company.com');
console.log(goodEmail.isValid); // true
console.log(goodEmail.errors);  // []

// --- Currency ---
const priceValidation = new ValidationEngine(['required', 'currency']);

// ❌ Not a currency value
const badPrice = priceValidation.validateValue('one hundred dollars');
console.log(badPrice.isValid); // false
console.log(badPrice.errors);  // ['This field must be a valid currency amount.']

// ✅ Valid
const goodPrice = priceValidation.validateValue('$99.99');
console.log(goodPrice.isValid); // true
console.log(goodPrice.errors);  // []

Stateful API

ValidationEngine tracks validation state internally, so you can check the result without re-running validation:

import { ValidationEngine } from 'oop-validator';

const emailValidation = new ValidationEngine(['required', 'email']);

// Validate a value
emailValidation.validateValue('invalid-email');

// Get current validation state (without re-validating)
console.log(emailValidation.getIsValid());  // false
console.log(emailValidation.getErrors());   // ['This field must be a valid email address.']

// Validate again with valid input
emailValidation.validateValue('valid@example.com');

console.log(emailValidation.getIsValid());  // true
console.log(emailValidation.getErrors());   // []

// Reset validation state (useful for clearing form errors)
emailValidation.reset();

console.log(emailValidation.getIsValid());  // true
console.log(emailValidation.getErrors());   // []

Stateful methods:

  • getIsValid() - Returns current validation status (true/false)
  • getErrors() - Returns array of current error messages
  • reset() - Clears validation state (sets isValid to true, clears errors)

Use cases:

  • ✅ Check validation status without re-running validation
  • ✅ Reset form fields after successful submission
  • ✅ Clear errors when user starts typing
  • ✅ Track validation state across multiple validations

Custom Validation Rules

Create your own validation rules for business-specific requirements:

import { ValidationEngine, IValidationRule } from 'oop-validator'

// A custom rule that validates hex color codes (e.g. #FF5733)
// Use this pattern for any business-specific rule not covered by the built-ins
class HexColorValidationRule extends IValidationRule {
    errorMessage = 'This field must be a valid hex color (e.g. #FF5733).'

    isValid(param) {
        if (param == null || param === '') return [true, ''] // let required handle emptiness
        const isValid = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(param)
        return [isValid, isValid ? '' : this.errorMessage]
    }

    isMatch(type) {
        return type.toLowerCase() === 'hexcolor'
    }

    setParams(params) {}

    setErrorMessage(message) {
        this.errorMessage = message
    }
}

const colorValidation = new ValidationEngine(['required'])
colorValidation.addRule(new HexColorValidationRule())

// ❌ Empty — caught by required rule
const emptyColor = colorValidation.validateValue('')
console.log(emptyColor.isValid); // false
console.log(emptyColor.errors);  // ['This field is required.']

// ❌ Not a hex color — caught by custom rule
const badColor = colorValidation.validateValue('red')
console.log(badColor.isValid); // false
console.log(badColor.errors);  // ['This field must be a valid hex color (e.g. #FF5733).']

// ✅ Valid
const goodColor = colorValidation.validateValue('#FF5733')
console.log(goodColor.isValid); // true
console.log(goodColor.errors);  // []

Form Validation Engine

Use FormValidationEngine for validating multiple fields at once. Perfect for forms, API requests, or any multi-field validation.

import { FormValidationEngine } from 'oop-validator';

// Sample contact form data (typically from user input)
const contactForm = {
  firstName: '',
  lastName: '',
  email: '',
  phone: '',
  country: '',
};

// Define validation rules for each field
const contactFormEngine = new FormValidationEngine({
  firstName: ['required', { rule: 'min', params: { length: 2 } }], // Must exist and be at least 2 chars
  lastName: ['required', { rule: 'min', params: { length: 2 } }], // Must exist and be at least 2 chars
  email: ['required', 'email'], // Must exist and be valid email format
  phone: ['required', 'phone'], // Must exist and be valid phone format
  country: ['required'], // Must exist (not empty)
});

// ❌ Validate the empty form — every field fails
const contactResult = contactFormEngine.validate(contactForm);
console.log(contactResult.isValid); // false
console.log(contactResult.fieldErrors);
// {
//   firstName: ['This field is required.'],
//   lastName:  ['This field is required.'],
//   email:     ['This field is required.'],
//   phone:     ['This field is required.'],
//   country:   ['This field is required.']
// }
console.log(contactResult.summary);
// [
//   'firstName: This field is required.',
//   'lastName: This field is required.',
//   'email: This field is required.',
//   'phone: This field is required.',
//   'country: This field is required.'
// ]

// ✅ Validate a filled form — all fields pass
const validResult = contactFormEngine.validate({
  firstName: 'Jane',
  lastName:  'Doe',
  email:     'jane@example.com',
  phone:     '+1234567890',
  country:   'USA',
});
console.log(validResult.isValid);      // true
console.log(validResult.fieldErrors);  // { firstName: [], lastName: [], email: [], phone: [], country: [] }
console.log(validResult.summary);      // []

Banking Form Example

import { FormValidationEngine } from 'oop-validator';

const bankingForm = {
  accountNumber: '',
  routingNumber: '',
  accountType: '',
  currency: '',
  initialDeposit: '',
};

const bankingEngine = new FormValidationEngine({
  accountNumber:  ['required', 'bankAccount'],
  routingNumber:  ['required', { rule: 'min', params: { length: 9 } }],
  accountType:    ['required'],
  currency:       ['required', 'currency'],
  initialDeposit: ['required'],
});

// ❌ Validate the empty form
const bankingResult = bankingEngine.validate(bankingForm);
console.log(bankingResult.isValid); // false
console.log(bankingResult.fieldErrors);
// {
//   accountNumber:  ['This field is required.'],
//   routingNumber:  ['This field is required.'],
//   accountType:    ['This field is required.'],
//   currency:       ['This field is required.'],
//   initialDeposit: ['This field is required.']
// }

// ✅ Validate a filled form
const validBanking = bankingEngine.validate({
  accountNumber:  '12345678',
  routingNumber:  '021000021',
  accountType:    'checking',
  currency:       'USD',
  initialDeposit: '$500.00',
});
console.log(validBanking.isValid); // true

Vue.js Composables

The library includes optional Vue.js composables that provide reactive validation with automatic updates when your form data changes. Perfect for Vue 3 Composition API projects.

Installation for Vue projects:

npm install oop-validator vue

All Vue-specific exports come from the oop-validator/vue subpath:

import { useValidation, useFormValidation, useForm } from 'oop-validator/vue'

useValidation - Single Field Validation

The useValidation composable is perfect for validating individual form fields with real-time reactive feedback.

import { ref } from 'vue';
import { useValidation } from 'oop-validator/vue';

// Single field validation with automatic reactivity
const email = ref('');

// Setup validation rules
const emailRules = ['required', 'email'];

// Get reactive validation state
const { errors, isValid, validate } = useValidation(email, emailRules);

// Validation runs automatically when email.value changes
// errors.value will contain array of error messages
// isValid.value will be true/false

// You can also manually trigger validation
const manualCheck = () => {
  validate(email.value);
};

Usage in Vue component:

<template>
  <div class="field">
    <input
      v-model="email"
      type="email"
      placeholder="Enter your email"
      :class="{ error: !isValid }"
    />
    <span v-if="errors.length" class="error-message">
      {{ errors[0] }}
    </span>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { useValidation } from 'oop-validator/vue';

const email = ref('');
const { errors, isValid } = useValidation(email, ['required', 'email']);
</script>

useFormValidation - Multi-Field Form Validation

The useFormValidation composable handles complex forms with multiple fields and provides comprehensive error management through the fields API.

Note: Works with any reactive type - ref(), reactive(), computed values, and props like modelValue.

Fields API (Recommended)

import { ref } from 'vue';
import { useFormValidation } from 'oop-validator/vue';

// Form data - works with ref(), reactive(), or any reactive type
const formData = ref({
  firstName: '',
  lastName: '',
  email: '',
  phone: '',
});

// Validation configuration
const validationConfig = {
  firstName: ['required', { rule: 'min', params: { length: 2 } }],
  lastName: ['required', { rule: 'min', params: { length: 2 } }],
  email: ['required', 'email'],
  phone: ['required', 'phone'],
};

// Get reactive validation state with new fields API
const {
  fields,         // Unified field state object (recommended)
  isValid,        // Form-level validity
  isModelDirty,   // Form-level dirty state (true if any field changed)
  validate,       // Manual validation trigger
  reset,          // Reset form to initial state
  touch,          // Mark specific field as touched
  touchAll,       // Mark all fields as touched
} = useFormValidation(formData, validationConfig);

// Access complete field state through fields object
console.log(fields.value.email.isValid)    // true/false - field is valid
console.log(fields.value.email.errors)     // string[] - error messages
console.log(fields.value.email.isDirty)    // true/false - value changed from initial
console.log(fields.value.email.isTouched)  // true/false - field was focused/blurred

// Check if form has unsaved changes
console.log(isModelDirty.value)            // true/false - any field is dirty

// Handle form submission
const handleSubmit = () => {
  touchAll() // Show errors on all fields
  const result = validate();
  if (result.isValid) {
    console.log('Form is valid, submitting...', formData.value);
    reset() // Reset form after successful submission
  }
};

// Handle field blur
const handleBlur = (fieldName) => {
  touch(fieldName) // Mark field as touched when user leaves it
};

// Warn user about unsaved changes
const handleNavigation = () => {
  if (isModelDirty.value) {
    const confirmed = confirm('You have unsaved changes. Are you sure you want to leave?');
    if (!confirmed) return false;
  }
  // Navigate away
  return true;
};

Full Vue component example with new fields API:

<template>
  <form @submit.prevent="handleSubmit">
    <!-- Unsaved changes indicator -->
    <div v-if="isModelDirty" class="alert alert-warning">
      You have unsaved changes
    </div>

    <div class="field">
      <input
        v-model="formData.firstName"
        placeholder="First Name"
        @blur="touch('firstName')"
        :class="{ 
          error: fields.firstName.isTouched && !fields.firstName.isValid,
          success: fields.firstName.isValid && fields.firstName.isDirty
        }"
      />
      <!-- Show checkmark if field is valid and modified -->
      <span v-if="fields.firstName.isValid && fields.firstName.isDirty" class="valid-icon"></span>
      <!-- Only show errors after field is touched -->
      <span v-if="fields.firstName.isTouched && fields.firstName.errors.length" class="error-message">
        {{ fields.firstName.errors[0] }}
      </span>
    </div>

    <div class="field">
      <input
        v-model="formData.email"
        type="email"
        placeholder="Email"
        @blur="touch('email')"
        :class="{ 
          error: fields.email.isTouched && !fields.email.isValid,
          success: fields.email.isValid && fields.email.isDirty
        }"
      />
      <span v-if="fields.email.isValid && fields.email.isDirty" class="valid-icon"></span>
      <span v-if="fields.email.isTouched && fields.email.errors.length" class="error-message">
        {{ fields.email.errors[0] }}
      </span>
    </div>

    <button type="submit" :disabled="!isValid">Submit Form</button>
    <button type="button" @click="reset">Reset Form</button>
  </form>
</template>

<script setup>
import { ref } from 'vue';
import { useFormValidation } from 'oop-validator/vue';

const formData = ref({
  firstName: '',
  email: '',
});

const config = {
  firstName: ['required'],
  email: ['required', 'email'],
};

const { fields, isValid, validate, reset, touch, touchAll } =
  useFormValidation(formData, config);

const handleSubmit = () => {
  touchAll() // Show all errors on submit attempt
  const result = validate();
  if (result.isValid) {
    console.log('Submitting:', formData.value);
    // After successful API call:
    reset() // Reset form state
  }
};
</script>

Legacy API (Deprecated)

errors, getFieldErrors(), and isFieldValid() are still returned for backward compatibility but superseded by fields.fieldName.errors and fields.fieldName.isValid.

Working with Different Reactive Types

The composable works seamlessly with all Vue reactive types:

import { ref, reactive, computed } from 'vue';

// ✅ Works with ref()
const formData = ref({ email: '', password: '' });
useFormValidation(formData, config);

// ✅ Works with reactive()
const formData = reactive({ email: '', password: '' });
useFormValidation(formData, config);

// ✅ Works with computed()
const formData = computed(() => ({ ...someState }));
useFormValidation(formData, config);

// ✅ Works with props (like v-model)
const props = defineProps(['modelValue']);
useFormValidation(props.modelValue, config);

Adding Custom Validation Rules

You can extend the validation system with your own custom rules by accessing the engine property from useFormValidation.

⚠️ Important: Do not reference your custom rule's string name in the config — it won't exist in the built-in switch. Add the rule instance programmatically via engine.addRuleToField() instead.

import { ref } from 'vue';
import { useFormValidation } from 'oop-validator/vue';
import { IValidationRule } from 'oop-validator';

// 1. Define your custom validation rule
class EvenNumberValidationRule extends IValidationRule {
  private errorMessage = 'Value must be an even number';

  isValid(value: any): [boolean, string] {
    const num = Number(value);
    const valid = !isNaN(num) && num % 2 === 0;
    return [valid, valid ? '' : this.errorMessage];
  }

  isMatch(type: string): boolean {
    return type.toLowerCase() === 'evennumber';
  }

  setParams(params: any): void {}
  setErrorMessage(message: string): void {
    this.errorMessage = message;
  }
}

const formData = ref({
  age: '',
  luckyNumber: '',
});

// 2. Use only built-in rules in config
const config = {
  age: ['required'],  // Don't reference 'evenNumber' here
  luckyNumber: ['required'],
};

// 3. Get engine and add custom rule BEFORE any validation
const { fields, isValid, engine } = useFormValidation(formData, config, {
  validateOnMount: false
});

// 4. Add custom rule to specific fields
engine.addRuleToField('age', new EvenNumberValidationRule());
engine.addRuleToField('luckyNumber', new EvenNumberValidationRule());

Important Notes

  • ⚠️ Don't reference custom rule names in the config - they won't exist during initialization
  • ⚠️ addRuleToField() only works for fields that exist in the config - you'll see a console warning for non-existent fields
  • Add custom rules immediately after getting the engine reference, before user interaction
  • Custom rules must implement the IValidationRule interface with 4 required methods:
    • isValid(value) - Returns [boolean, string] tuple (valid status and error message)
    • isMatch(type) - Returns true if the rule matches the validation type string
    • setParams(params) - Sets custom parameters for the rule
    • setErrorMessage(message) - Sets a custom error message

Example - Correct field names:

const config = {
  age: ['required'],      // ✅ 'age' field exists
  email: ['required'],    // ✅ 'email' field exists
};

const { engine } = useFormValidation(formData, config);

// ✅ Works - field 'age' exists in config
engine.addRuleToField('age', new EvenNumberValidationRule());

// ❌ Shows warning - field 'agee' (typo) doesn't exist in config
// Console: "Cannot add rule to field "agee": field does not exist in validation config. Available fields: age, email"
engine.addRuleToField('agee', new EvenNumberValidationRule());

// ❌ Shows warning - field 'username' was never defined in config
// Console: "Cannot add rule to field "username": field does not exist in validation config. Available fields: age, email"
engine.addRuleToField('username', new CustomRule());

Custom Rule with Parameters and Error Messages:

class MinAgeValidationRule extends IValidationRule {
  private minAge = 18;
  private errorMessage = 'Age must be at least 18';

  isValid(value: any): [boolean, string] {
    const age = Number(value);
    const valid = !isNaN(age) && age >= this.minAge;
    return [valid, valid ? '' : this.errorMessage];
  }

  isMatch(type: string): boolean {
    return type.toLowerCase() === 'minage';
  }

  setParams(params: any): void {
    if (params.age) this.minAge = params.age;
  }

  setErrorMessage(message: string): void {
    this.errorMessage = message;
  }
}

// Usage with custom parameters
const { engine } = useFormValidation(formData, config);

const ageRule = new MinAgeValidationRule();
ageRule.setParams({ age: 21 });
ageRule.setErrorMessage('You must be at least 21 years old');
engine.addRuleToField('age', ageRule);

Validation Configuration Options

The useFormValidation composable accepts an optional third parameter for configuration:

const { fields, isValid } = useFormValidation(formData, config, {
  validationStrategy: 'all' | 'changed',  // How to validate (default: 'all')
  validateOnMount: true | false            // Validate immediately (default: depends on strategy)
})

Validation Strategies

validationStrategy: 'all' (default)

  • Validates all fields whenever any field changes
  • Safest option for forms with cross-field validation (like password confirmation)
  • Default behavior - ensures consistency

validationStrategy: 'changed'

  • Only validates fields that changed for better performance
  • Ideal for large forms or real-time validation scenarios
  • May miss cross-field validation dependencies

Validate On Mount

validateOnMount: boolean

  • Controls whether validation runs immediately when the form loads
  • Default for 'all' strategy: true (show all errors immediately)
  • Default for 'changed' strategy: false (wait for user interaction)

Configuration Examples

// Default: validate all fields immediately
const { fields, isValid } = useFormValidation(formData, config)

// Performance mode: only validate changed fields, no initial errors
const { fields, isValid } = useFormValidation(formData, config, {
  validationStrategy: 'changed'
})

// Validate all fields, but wait for user interaction
const { fields, isValid } = useFormValidation(formData, config, {
  validationStrategy: 'all',
  validateOnMount: false
})

// Show all errors immediately, but only revalidate changed fields
const { fields, isValid } = useFormValidation(formData, config, {
  validationStrategy: 'changed',
  validateOnMount: true
})

Vue Directives

oop-validator ships with a full set of AngularJS-style validation directives for Vue 3. They wire directly into the DOM — no manual event listeners, no boilerplate — and automatically apply CSS classes for valid/invalid/touched/dirty state.

Setup

Register the plugin once in your app entry:

import { createApp } from 'vue'
import { VueValidationPlugin } from 'oop-validator/vue'
import App from './App.vue'

createApp(App).use(VueValidationPlugin).mount('#app')

useForm

useForm is the companion composable for the directive system. It binds a reactive data object to a named <form> element and returns a form instance used by all child directives.

<script setup>
import { reactive } from 'vue'
import { useForm } from 'oop-validator/vue'

const data = reactive({ name: '', email: '' })
const form = useForm('myForm', data)
</script>

<template>
  <form name="myForm" v-submit="handleSubmit">
    <input name="name" v-model="data.name" v-required />
    <input name="email" type="email" v-model="data.email" v-required v-type />
  </form>
</template>

useForm(name, data, options?)

Param Type Description
name string Must match the name attribute on the <form> element
data ref / reactive The reactive form data object
options object Same options as useFormValidation

The returned form object is a Proxy that exposes all useFormValidation methods plus shorthand field access: form.email is equivalent to form.fields.value.email.


Directive Reference

All directives require:

  1. A parent <form name="..."> element
  2. A name attribute on the input element (used as the field key)

v-required

Marks a field as required. Accepts a dynamic boolean.

<input name="email" v-model="data.email" v-required />
<input name="email" v-model="data.email" v-required="true" />
<input name="terms" type="checkbox" v-model="data.terms" v-required="mustAgree" />

v-minlength / v-maxlength

Validates string length bounds.

<input name="username" v-model="data.username" v-minlength="3" v-maxlength="20" />

v-pattern

Validates against a regex. Accepts a regex literal or string.

<input name="slug" v-model="data.slug" v-pattern="/^[a-z0-9-]+$/" />
<input name="slug" v-model="data.slug" v-pattern="'^[a-z0-9-]+$'" />

v-min / v-max

Validates numeric value bounds.

<input name="age"  type="number" v-model="data.age"  v-min="18" />
<input name="qty"  type="number" v-model="data.qty"  v-min="1" v-max="100" />

v-type

Registers a type-based validation rule derived from the input's type attribute or an explicit value. Supported types: email, url, phone, number, date.

<input name="email" type="email" v-model="data.email" v-type />
<input name="site"  v-model="data.site"  v-type="'url'" />

v-messages / v-message

v-messages is a container directive placed on a wrapper element. It receives the field's $error map and controls visibility of individual v-message children.

<div v-messages="form.email?.$error ?? {}">
  <span v-message="'required'">Email is required.</span>
  <span v-message="'type'">Enter a valid email address.</span>
</div>

Each v-message="'ruleKey'" child is shown only when that specific rule is failing.


v-submit

Replaces @submit.prevent. Calls the handler only when the form is valid; otherwise marks all fields as touched to reveal errors.

<form name="myForm" v-submit="handleSubmit">
  ...
</form>
const handleSubmit = () => {
  // only called when all fields are valid
  console.log('submitting', data)
}

v-form-group

Applied to a wrapper element (e.g. <div class="field">). Mirrors the CSS state classes of the named field onto the container, useful for styling entire field groups.

<div v-form-group="'email'" class="field">
  <label>Email</label>
  <input name="email" v-model="data.email" v-required v-type />
</div>

CSS Classes

Directives automatically toggle these classes on the input element:

Class When applied
v-valid Field passes all rules
v-invalid Field fails at least one rule
v-pristine Value has not changed since mount
v-dirty Value has changed
v-touched Field has been blurred
v-untouched Field has not been blurred
v-pending Async validation in progress
v-valid-{rule} Specific rule is passing
v-invalid-{rule} Specific rule is failing

Default styles for v-invalid.v-touched (red border) and v-valid.v-dirty (green border) are included. Override them in your own CSS:

input.v-invalid.v-touched { border-color: #e53e3e; }
input.v-valid.v-dirty     { border-color: #38a169; }

Full Example

<script setup>
import { reactive } from 'vue'
import { useForm } from 'oop-validator/vue'

const data = reactive({ name: '', email: '', age: '' })
const form = useForm('contact', data)

const handleSubmit = () => {
  console.log('valid form:', data)
}
</script>

<template>
  <form name="contact" v-submit="handleSubmit">

    <div class="field">
      <label>Name</label>
      <input name="name" v-model="data.name" v-required v-minlength="2" />
      <div v-messages="form.name?.$error ?? {}">
        <span v-message="'required'">Name is required.</span>
        <span v-message="'minlength'">At least 2 characters.</span>
      </div>
    </div>

    <div class="field">
      <label>Email</label>
      <input name="email" type="email" v-model="data.email" v-required v-type />
      <div v-messages="form.email?.$error ?? {}">
        <span v-message="'required'">Email is required.</span>
        <span v-message="'type'">Enter a valid email.</span>
      </div>
    </div>

    <div class="field">
      <label>Age</label>
      <input name="age" type="number" v-model="data.age" v-required v-min="18" v-max="120" />
      <div v-messages="form.age?.$error ?? {}">
        <span v-message="'required'">Age is required.</span>
        <span v-message="'min'">Must be at least 18.</span>
        <span v-message="'max'">Cannot exceed 120.</span>
      </div>
    </div>

    <button type="submit">Submit</button>
  </form>
</template>

React Integration

The core validation engines work perfectly with React's state management. Here are practical examples for different React patterns.

React Hook Examples

import { useEffect, useState } from 'react';
import { FormValidationEngine } from 'oop-validator';

// Contact form with React hooks
function ContactForm() {
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
    email: '',
    phone: '',
  });
  const [errors, setErrors] = useState({});
  const [isValid, setIsValid] = useState(false);

  // Create validation engine
  const contactEngine = new FormValidationEngine({
    firstName: ['required', { rule: 'min', params: { length: 2 } }],
    lastName: ['required', { rule: 'min', params: { length: 2 } }],
    email: ['required', 'email'],
    phone: ['required', 'phone'],
  });

  // Validate on form data changes
  useEffect(() => {
    const result = contactEngine.validate(formData);
    setErrors(result.fieldErrors);
    setIsValid(result.isValid);
  }, [formData]);

  const handleInputChange = (field, value) => {
    setFormData((prev) => ({ ...prev, [field]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (isValid) {
      console.log('Submitting form:', formData);
      // API call or further processing
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="text"
          placeholder="First Name"
          value={formData.firstName}
          onChange={(e) => handleInputChange('firstName', e.target.value)}
          className={errors.firstName?.length ? 'error' : ''}
        />
        {errors.firstName?.map((error, index) => (
          <span key={index} className="error-message">
            {error}
          </span>
        ))}
      </div>

      <div>
        <input
          type="email"
          placeholder="Email"
          value={formData.email}
          onChange={(e) => handleInputChange('email', e.target.value)}
          className={errors.email?.length ? 'error' : ''}
        />
        {errors.email?.map((error, index) => (
          <span key={index} className="error-message">
            {error}
          </span>
        ))}
      </div>

      <button type="submit" disabled={!isValid}>
        Submit Contact Form
      </button>
    </form>
  );
}

Component Integration

import { useEffect, useState } from 'react';
import { ValidationEngine } from 'oop-validator';

// Single field validation component
function ValidatedInput({ value, onChange, rules, placeholder }) {
  const [errors, setErrors] = useState([]);
  const validationEngine = new ValidationEngine(rules);

  useEffect(() => {
    const result = validationEngine.validateValue(value);
    setErrors(result.errors);
  }, [value]);

  return (
    <div className="field">
      <input
        type="text"
        value={value}
        onChange={onChange}
        placeholder={placeholder}
        className={errors.length ? 'error' : ''}
      />
      {errors.map((error, index) => (
        <span key={index} className="error-message">
          {error}
        </span>
      ))}
    </div>
  );
}

// Usage in parent component
function ProductForm() {
  const [productName, setProductName] = useState('');
  const [price, setPrice] = useState('');

  return (
    <form>
      <ValidatedInput
        value={productName}
        onChange={(e) => setProductName(e.target.value)}
        rules={['required', { rule: 'min', params: { length: 3 } }]}
        placeholder="Product Name"
      />

      <ValidatedInput
        value={price}
        onChange={(e) => setPrice(e.target.value)}
        rules={['required', 'currency']}
        placeholder="Price"
      />
    </form>
  );
}

Node.js Environment

The validation library is perfect for server-side validation in Node.js applications, API request validation, and data processing.

Server-side Validation

const { FormValidationEngine, ValidationEngine } = require('oop-validator');

// Express.js middleware for request validation
function validateContactForm(req, res, next) {
  const contactEngine = new FormValidationEngine({
    firstName: ['required', { rule: 'min', params: { length: 2 } }],
    lastName: ['required', { rule: 'min', params: { length: 2 } }],
    email: ['required', 'email'],
    phone: ['required', 'phone'],
    message: ['required', { rule: 'max', params: { length: 1000 } }],
  });

  const result = contactEngine.validate(req.body);

  if (!result.isValid) {
    return res.status(400).json({
      error: 'Validation failed',
      details: result.fieldErrors,
      summary: result.summary,
    });
  }

  next(); // Validation passed, continue to next middleware
}

// Use in Express routes
app.post('/api/contact', validateContactForm, (req, res) => {
  // Process validated contact form data
  console.log('Valid contact form:', req.body);
  res.json({ success: true, message: 'Contact form submitted successfully' });
});

API Request Validation

const { FormValidationEngine, MatchFieldValidationRule } = require('oop-validator');

// User registration validation
function validateUserRegistration(userData) {
  const userEngine = new FormValidationEngine({
    username:        ['required', 'username'],
    email:           ['required', 'email'],
    password:        ['required', 'password'],
    confirmPassword: ['required', new MatchFieldValidationRule('password')],
    age:             ['required'],
  });

  return userEngine.validate(userData);
}

// Product validation for e-commerce API
function validateProduct(productData) {
  const productEngine = new FormValidationEngine({
    name: ['required', { rule: 'min', params: { length: 3 } }],
    sku: ['required', { rule: 'regex', params: { regex: '^[A-Z0-9-]+$' } }],
    price: ['required', 'currency'],
    category: ['required'],
    description: [{ rule: 'max', params: { length: 2000 } }],
  });

  return productEngine.validate(productData);
}

// Usage in API handlers
async function createProduct(req, res) {
  const validationResult = validateProduct(req.body);

  if (!validationResult.isValid) {
    return res.status(400).json({
      error: 'Invalid product data',
      validation_errors: validationResult.fieldErrors,
    });
  }

  try {
    // Save to database
    const product = await Product.create(req.body);
    res.status(201).json({ success: true, product });
  } catch (error) {
    res.status(500).json({ error: 'Database error' });
  }
}

// Database model validation
class User {
  static validate(userData) {
    const userEngine = new FormValidationEngine({
      email: ['required', 'email'],
      firstName: ['required'],
      lastName: ['required'],
      dateOfBirth: ['required', 'date'],
      socialSecurity: ['ssn'], // Optional field
    });

    return userEngine.validate(userData);
  }

  static async create(userData) {
    const validation = User.validate(userData);

    if (!validation.isValid) {
      throw new Error(
        `User validation failed: ${validation.summary.join(', ')}`
      );
    }

    // Proceed with user creation
    return await database.users.insert(userData);
  }
}

Available Validation Rules

The library includes a comprehensive set of built-in validation rules for common business needs:

Basic Rules

  • required - Field must not be empty
  • min - Minimum string length: { rule: 'min', params: { length: 3 } }
  • max - Maximum string length: { rule: 'max', params: { length: 50 } }

Format Rules

  • email - Valid email address format
  • url - Valid URL format
  • phone - Valid phone number format
  • date - Valid date format (YYYY-MM-DD only)

Financial Rules

  • currency - Valid currency format ($123.45, €99.00, etc.)
  • bankAccount - Valid bank account number (8–20 digits; IBAN format not supported)
  • creditCard - Valid credit card number

Geographic Rules

  • zipCode - Valid US ZIP code (5-digit or ZIP+4 format; international postal codes not supported)
  • domain - Valid domain name
  • ip - Valid IP address (IPv4 and IPv6 including compressed notation like ::1)

Identity Rules

  • ssn - Valid social security number
  • username - Valid username format

Advanced Rules

  • regex - Custom regex pattern: { rule: 'regex', params: { regex: '^[A-Z]+$' } }
  • password - Password strength validation (requires uppercase, lowercase, digit, special character, min 8 chars)
  • MatchFieldValidationRule - Cross-field match (use the class directly, no string key): new MatchFieldValidationRule('otherFieldName')

Custom Messages

All rules support custom error messages:

{
  rule: 'min',
  params: { length: 8 },
  message: 'Password must be at least 8 characters long'
}

API Reference

ValidationEngine

Constructor:

new ValidationEngine(rules: Array<string | RuleConfig>)

Methods:

  • validateValue(value: any): ValidationResult - Validates a value against all rules and stores the result
  • addRule(rule: IValidationRule): void - Adds a custom validation rule instance
  • getIsValid(): boolean - Returns the last stored validation status (no re-validation)
  • getErrors(): string[] - Returns the last stored error array (no re-validation)
  • reset(): void - Clears stored state (sets isValid to true, errors to [])

Return Type:

{
  isValid: boolean,
  errors: string[]
}

FormValidationEngine

Constructor:

new FormValidationEngine(config: { [fieldName: string]: Array<string | RuleConfig> })

Methods:

  • validate(data: object): FormValidationResult - Validates all form fields
  • validateField(fieldName: string, value: any, allValues?: object): ValidationResult - Validates a single field
  • addRuleToField(fieldName: string, rule: IValidationRule): void - Adds a custom validation rule to a specific field
  • reset(): void - Resets validation state for all field engines

Return Type:

{
  isValid: boolean,
  fieldErrors: { [fieldName: string]: string[] },
  summary: string[]
}

Vue Composables

useValidation

useValidation(
  value: Ref<any>,
  rules: Array<string | RuleConfig>
): {
  errors: Ref<string[]>,
  isValid: Ref<boolean>,
  validate: (value?: string) => boolean
}

useFormValidation

useFormValidation(
  formData: Ref<object> | object,
  config: { [fieldName: string]: Array<string | RuleConfig> },
  options?: {
    validationStrategy?: 'all' | 'changed',  // default: 'all'
    validateOnMount?: boolean                 // default: true for 'all', false for 'changed'
  }
): {
  fields: Ref<{
    [fieldName: string]: {
      isValid: boolean,
      errors: string[],
      isDirty: boolean,
      isTouched: boolean
    }
  }>,
  
  // Form-level state
  isValid: Ref<boolean>,
  isModelDirty: Ref<boolean>,
  summary: Ref<string[]>,
  
  // Actions
  validate: (values?: object) => FormValidationResult,
  reset: () => void,
  touch: (fieldName: string) => void,
  touchAll: () => void,
  
  // Engine access for custom rules
  engine: FormValidationEngine,
  
  // Deprecated (still supported)
  errors: Ref<{ [fieldName: string]: string[] }>,
  getFieldErrors: (field: string) => Ref<string[]>,
  isFieldValid: (field: string) => Ref<boolean>
}

Options:

  • validationStrategy:

    • 'all' (default): Validates all fields when any field changes
    • 'changed': Only validates fields that changed (better performance)
  • validateOnMount:

    • Controls whether validation runs immediately on mount
    • Default: true for 'all' strategy, false for 'changed' strategy

AngularJS Migration Guide

oop-validator v0.6.0 adds a Vue 3 equivalent of AngularJS's FormControllerng-* form directives mapped 1:1 to v-* directives.

Before (AngularJS)

<form name="userForm" ng-submit="save()" novalidate>
  <input type="text" ng-model="user.name" name="name"
         ng-required="true" ng-minlength="3" ng-maxlength="50">
  <div ng-messages="userForm.name.$error"
       ng-show="userForm.name.$touched || userForm.$submitted">
    <span ng-message="required">Name is required.</span>
    <span ng-message="minlength">At least 3 characters.</span>
  </div>

  <input type="email" ng-model="user.email" name="email" ng-required="true">
  <div ng-messages="userForm.email.$error"
       ng-show="userForm.email.$touched || userForm.$submitted">
    <span ng-message="required">Email is required.</span>
    <span ng-message="email">Invalid email.</span>
  </div>

  <button type="submit" ng-disabled="userForm.$invalid">Submit</button>
</form>

After (Vue 3 with oop-validator)

<script setup>
import { reactive } from 'vue'
import { useForm } from 'oop-validator/vue'

const user = reactive({ name: '', email: '' })
const form = useForm('userForm', user)  // ← one line replaces ng-form

function save() { console.log('Saving:', user) }
</script>

<template>
  <form name="userForm" v-submit="save">
    <input type="text" v-model="user.name" name="name"
           v-required="true" v-minlength="3" v-maxlength="50">
    <div v-messages="form.name.$error"
         v-show="form.name.$touched || form.$submitted">
      <span v-message="'required'">Name is required.</span>
      <span v-message="'minlength'">At least 3 characters.</span>
    </div>

    <input type="email" v-model="user.email" name="email" v-required="true" v-type>
    <div v-messages="form.email.$error"
         v-show="form.email.$touched || form.$submitted">
      <span v-message="'required'">Email is required.</span>
      <span v-message="'email'">Invalid email.</span>
    </div>

    <button type="submit" :disabled="form.$invalid">Submit</button>
  </form>
</template>

Migration Table

AngularJS Vue 3 (oop-validator) Notes
ng-form="userForm" useForm('userForm', data) One line in <script setup>
ng-submit="save()" v-submit="save" Auto-adds novalidate, calls save only when valid
ng-required="true" v-required="true" Supports dynamic true/false
ng-minlength="3" v-minlength="3" String length
ng-maxlength="50" v-maxlength="50" String length
ng-pattern="/regex/" v-pattern="/regex/" String or RegExp
ng-min="18" v-min="18" Numeric value (not length)
ng-max="120" v-max="120" Numeric value (not length)
<input type="email" ng-model> v-type on <input type="email"> Infers email/url/number/date rule
ng-messages="form.name.$error" v-messages="form.name.$error" Container directive
ng-message="required" v-message="'required'" Value is a quoted string
userForm.name.$error form.name.$error Via Proxy on useForm result
userForm.$submitted form.$submitted (Ref)
userForm.$valid form.$valid (ComputedRef)
userForm.$invalid form.$invalid (ComputedRef)
userForm.$pristine form.$pristine (ComputedRef)
userForm.$dirty form.$dirty (ComputedRef)
userForm.$setPristine() form.$setPristine()
$setValidity('key', bool) form.$setValidity('field', 'key', bool) Server errors etc.

Install the Plugin

// main.ts
import { createApp } from 'vue'
import { VueValidationPlugin } from 'oop-validator/vue'
import App from './App.vue'

createApp(App).use(VueValidationPlugin).mount('#app')

Key Differences from AngularJS

  1. One setup line: const form = useForm('userForm', user) in <script setup> — AngularJS did this implicitly via ng-form
  2. v-type for type inference: AngularJS inferred validation from type="" automatically. In Vue 3, add v-type to inputs with type="email", type="number", etc.
  3. Quoted string values: v-message="'required'" (quoted) vs ng-message="required" (bare)
  4. Ref access: form.$submitted.value in <script setup>, but just form.$submitted in templates (Vue auto-unwraps refs in templates)

Numeric Validation

For <input type="number"> fields use v-min / v-max (numeric value) instead of v-minlength / v-maxlength (string length):

<input type="number" v-model.number="user.age" name="age"
       v-required="true" v-min="18" v-max="120">

HMR Compatibility

oop-validator is fully compatible with Vite's Hot Module Reload (HMR) and other modern bundlers (Webpack, Rollup, etc.). Vue is externalized from the bundle — it is only required as a peer dependency when using the Vue composables.


License

This project is licensed under the MIT License. See the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Changelog

See CHANGELOG.md for version history and updates.

About

oop-validator is a versatile and robust validation library designed to seamlessly integrate with any UI framework or library. Whether you're building applications with Vue.js, React, Angular, or any other front-end technology, oop-validator provides a comprehensive and flexible solution for all your validation needs.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages