Javascript form validator

Here we are going to build a form validator class it will expose four methods forField, addRule, validate and required.

forField

This method will take field name as argument which we want to validate and returns an current object.

addRule

This method will take 3 arguments (fieldName, conditionMethod, failureMessage) first argument is optional if we didn’t pass fieldName it will add rules to the current field which we set using forField.

validate

validate method will take JSON as object, key is field name and value is field value. It will validate based on rules returns true if all are valid else it will return error message.

required

This method will add required rules for current field

Create a file name it validator.js

Creating Validator class

class Validator {}

we need to add rules object for validator class

rules = {}

next we need to add setField and setRule methods to Validator class

  setFeild(name) {
    this.feild = name;
    return this;
  }

  setRule(...args) {
    if (this.rules[this.feild])
      this.rules[this.feild].push({ rule: args[0], errMsg: args[1] });
    else this.rules[this.feild] = [{ rule: args[0], errMsg: args[1] }];
  }

Now Validator class will be like this

class Validator {
  rules = {}

  setField(name) {
    this.field = name
    return this
  }

  setRule(...args) {
    if (this.rules[this.field])
      this.rules[this.field].push({ rule: args[0], errMsg: args[1] })
    else this.rules[this.field] = [{ rule: args[0], errMsg: args[1] }]
  }
}

Adding addField method to validator class

Validator.prototype.forField = function (field) {
  this.setField(field)
  return this
}

we need two helper functions _addRule and clone

const _addRule = (obj, ...args) => {
  if (args.length === 3) {
    obj.setField(args[0])
    args.shift()
  }
  obj.setRule(...args)
  return clone(obj)
}

function clone(obj) {
  return Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj)
  )
}

Adding addRule method to validator class

Validator.prototype.addRule = function (...args) {
  return _addRule(this, ...args)
}

Adding addRule metohod to validator class

Validator.prototype.required = function () {
  const isEmpty = e => !!e
  const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
  this.setRule(isEmpty, capitalize(this.field) + " is required")
  return this
}

Adding validate method to validator class

Validator.prototype.validate = function (object) {
  const validationFields = Object.keys(this.rules)
  const errorResponses = {}
  let success = true
  validationFields.forEach(item => {
    const validation = this.rules[item].reduce((acc, e) => {
      if (!e.rule(object[item] || "")) {
        success = false
        acc.push(e.errMsg)
      }
      return acc
    }, [])

    if (validation.length > 0) errorResponses[item] = validation
  })

  return {
    success,
    errors: !success ? { ...errorResponses } : {},
  }
}

Finally your validator.js file will be like this

class Validator {
  rules = {}

  setField(name) {
    this.field = name
    return this
  }

  setRule(...args) {
    if (this.rules[this.field])
      this.rules[this.field].push({ rule: args[0], errMsg: args[1] })
    else this.rules[this.field] = [{ rule: args[0], errMsg: args[1] }]
  }
}

Validator.prototype.forField = function (field) {
  this.setField(field)
  return this
}

const _addRule = (obj, ...args) => {
  if (args.length === 3) {
    obj.setField(args[0])
    args.shift()
  }
  obj.setRule(...args)
  return clone(obj)
}

function clone(obj) {
  return Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj)
  )
}

Validator.prototype.addRule = function (...args) {
  return _addRule(this, ...args)
}

Validator.prototype.required = function () {
  const isEmpty = e => !!e
  const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
  this.setRule(isEmpty, capitalize(this.field) + " is required")
  return this
}

Validator.prototype.validate = function (object) {
  const validationFields = Object.keys(this.rules)
  const errorResponses = {}
  let success = true
  validationFields.forEach(item => {
    const validation = this.rules[item].reduce((acc, e) => {
      if (!e.rule(object[item] || "")) {
        success = false
        acc.push(e.errMsg)
      }
      return acc
    }, [])

    if (validation.length > 0) errorResponses[item] = validation
  })

  return {
    success,
    errors: !success ? { ...errorResponses } : {},
  }
}

Working with Validator class

create a file name it main.js and add few validation functions

const isNumber = e => !isNaN(e)
const isStrType = e => typeof e === "string"
const lengthGtFive = e => e.length > 5
const lengthEqTen = e => e.length === 10

Now add the following code to run our Validator

const formValidator = new Validator()
const nameRules = formValidator
  .forField("name")
  .addRule(lengthGtFive, "Name Should have atleast 6 letters")
  .required()
const phoneNumberRules = formValidator.addRule(
  "mobile",
  isNumber,
  "Mobile number should only have numbers"
)
nameRules.addRule(isStrType, "Name Should be alphabets")
phoneNumberRules.addRule(lengthEqTen, "Mobile number should have 10 numbers")

//Success Case
formValidator.validate({
  name: "PERSON NAME",
  mobile: "1234567890",
})

/*output
{ success: true, errors: {} }
*/

//Negative Case 1
formValidator.validate({
  name: "PERSO",
  mobile: "1234567890",
})

/*output
{
  success: false,
  errors: { name: [ 'Name Should have atleast 6 letters' ] }
}
*/

//Negative Case 2
formValidator.validate({
  name: "PERSON",
  mobile: "jnlfne",
})

/*output
{
  success: false,
  errors: {
    mobile: [
      'Mobile number should only have numbers',
      'Mobile number should have 10 numbers'
    ]
  }
}
*/