Skip to main content

This is a nifty little JavaScript validator object, inspired by "JS Patterns." It offers a convenient method to extend the validation options as well.

var Validator = {

  validationRules: {

    // A few to get started.
    required: {
      validate: function (value) {
        return value.length !== 0;
      },

      errorMessage: 'This field cannot be blank.'
    },

    number: {
      validate: function (value) {
        return (typeof value === 'number') && !isNaN(value); // kinda weird. Needed more than isNaN. Make better, Jeff.
      },

      errorMessage: 'Must be a number.'
    },

    email: {
      validate: function (value) {
        var exp = /^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$/;
        return exp.test(value);
      },

      errorMessage: 'Must use a valid email address.'
    },

    min: {
      validate: function (value, min) {
        return value.length >= min;
      },

      errorMessage: 'Not enough characters'
    },

    max: {
      validate: function (value, min, max) {
        return value.length <= max;
      },

      errorMessage: 'Too many characters.'
    },

    sameAs: {
      validate: function (value, limit, propName, sameAs, origValue) {
        return value === Validator.data[origValue];
      },

      errorMessage: 'Values must be the same'
    }

  },

  // Will contain array of error messages (should there be any)
  errorMessages: [],

  // To be set by the user.
  config: {},

  min: '',

  max: '',

  sameAs: '',

  // The object of data we're testing
  data: {},

  validate: function (data) {
    var rule, checker, goodToGo, toString = Object.prototype.toString,
      prop, j, len, sameAs, isSameRuleSet;

    // Empty error errorMessages
    this.errorMessages = [];

    // Expose data to Validator object.
    this.data = data;

    // Determines if a min|max rule has been applied,
    // If so, it splits it.

    function reviseIfLimit(rule) {
      var split;

      // Check if rule is min|max_number
      if (/\d$/.test(rule)) {
        split = rule.split('_');
        rule = split[0];
        if (rule === 'min'.toLowerCase()) {
          Validator.min = split[1];
        } else {
          Validator.max = split[1];
        }
      }

      return rule;
    }

    function isSameChecker(rule) {
      var split;

      // what if verification (sameAs_<field>)
      if (/^sameAs/i.test(rule)) {
        split = rule.split('_');
        rule = split[0];
        Validator.sameAs = split[1];

        if (!Validator.validationRules[rule]) {
          throw new Error(rule + ': Validation parameter is unknown.');
        }
      }

      return rule;
    }

    // Filter through the form data object
    for (prop in data) {
      // prop = field name (firstName)
      // data[prop] = field value (Jeffrey)
      if (data.hasOwnProperty(prop)) {
        // Like, "number" or "required" (Could be array of checks)
        if (!this.config[prop]) continue;
        rule = this.config[prop];

        // Did the user pass an array of checks?
        if (toString.call(rule) === '[object Array]') {
          len = rule.length;
          checker = [];

          // Then filter through array, and check to see if we have a rule for the check.
          while (len--) {
            // Check if the rule is "min" or "max" followed by a limit (int).
            rule[len] = reviseIfLimit(rule[len]);
            rule[len] = isSameChecker(rule[len]);
            checker.push(this.validationRules[rule[len]]);
          }
        } else {
          // no array. just a single value to validate.
          // Check if the rule has a limit (min | max_INT)
          rule = reviseIfLimit(rule);
          rule = isSameChecker(rule);
          checker = this.validationRules[rule];

          if (!checker) {
            throw new Error(rule + ': Validation parameter is unknown.');
          }
        }

        // Now call the validate methods of all the items in the array
        if (toString.call(checker) === '[object Array]') {
          for (j = 0; j < checker.length; j++) {
            goodToGo = callValidator.call(this, checker[j]);
          }
        } else {
          goodToGo = callValidator.call(this, checker);
        }
      }
    } // end for

    function callValidator(checker) {
      goodToGo = checker.validate(data[prop], this.min, this.max, prop, this.sameAs); // value, property name, what it's being compared to
      if (!goodToGo) {
        this.errorMessages.push(prop + ': ' + checker.errorMessage);
      }
    }

  },

  // Very simple, but helpful.
  hasErrors: function () {
    return this.errorMessages.length !== 0;
  },

  // Another little helper method to populate a ul element with the list of errors (if any).
  populateList: function (results) {
    var len = Validator.errorMessages.length,
      li;
    frag = document.createDocumentFragment();

    if (!results) {
      throw new Error('populateList requires an argument that specifies the container element for the error list.');
    }

    if (this.hasErrors) {
      while (len--) {
        li = document.createElement('li');
        li.appendChild(document.createTextNode(Validator.errorMessages[len]));
        frag.appendChild(li);
      }
      results.appendChild(frag);
    }
  }

};