wbotelhos/validaty

View on GitHub
src/jquery.validaty.js

Summary

Maintainability
A
1 hr
Test Coverage
/*!
 * jQuery Validaty - A Validation Plugin
 *
 * The MIT License
 *
 * @author  Washington Botelho
 * @doc     wbotelhos.com/validaty
 * @version 0.6.1
 */

;(function($) {
  'use strict';

  var helper  = {};
  var methods = {};

  methods.init = function(settings) {
    return this.each(function() {
      methods.destroy.call(this);

      this.opt = $.extend({}, $.fn.validaty.defaults, settings);

      var self = $(this).addClass('validaty');

      methods._bind.call(this);

      self.data({ settings: this.opt, validaty: true });
    });
  };

  methods.destroy = function() {
    return $(this).each(function() {
      $(this).off('.validaty').removeClass('validaty');
    });
  };

  methods.helper = function(name) {
    return helper;
  };

  methods.valid = function() {
    return $(this).data('valid');
  };

  methods.validate = function(fields) {
    return this.each(function() {
      methods._process.call(this, [].concat.apply([], fields));
    });
  };

  methods.validator = function(name) {
    return $.fn.validaty.defaults.validators[name] || $.error('Validator "' + name + '" not registered!');
  };

  methods._bind = function() {
    this.inputs = methods._fields.call(this);

    $(this).on('submit.validaty', function(evt) {
      methods._process.call(this, this.inputs, evt);
    }.bind(this));

    var that = this;

    for (var i = 0; i < this.inputs.length; i++) {
      var field   = this.inputs[i];
      var actions = helper.getParams(field).actions;

      field.hash = 'validaty-' + this.opt.hash();

      field = $(field);

      for (var index in actions) {
        var binds = actions[index].args.join('.validaty ') + '.validaty';

        field.on(binds, function(evt, forced) {
          if (!forced) {
            methods._process.call(that, [this], evt);
          }
        });
      }
    }
  };

  methods._clear = function(field) {
    $('#' + field.hash).remove();

    $(field).removeClass('invalid valid');
  };

  methods._display = function(result) {
    var message = methods._message.call(this, result);
    var wrapper = message.children('ul');

    for (var i = 0; i < result.fail.length; i++) {
      $('<li />', { text: result.fail[i].message }).appendTo(wrapper);
    }

    message.animate({ opacity: 1 }, function() {
      if (this.opt.onMessage) {
        this.opt.onMessage();
      }
    }.bind(this));
  };

  methods._distinct = function(fields) {
    var names    = [];
    var distinct = [];
    var inputs   = $($(fields).get().reverse());

    for (var i = 0; i < inputs.length; i++) {
      if (helper.isCheckable(inputs[i])) {
        if ($.inArray(inputs[i].name, names) === -1) {
          names.push(inputs[i].name);
          distinct.push(inputs[i]);
        }
      } else {
        distinct.push(inputs[i]);
      }
    }

    return distinct;
  };

  methods._error = function(field, validator, args) {
    var message = validator.message;

    if (typeof validator.message === 'object') {
      if (field.is('input')) {
        message = validator.message[field.attr('type')];
      } else if (field.is('select')) {
        message = validator.message.select;
      } else if (field.is('textarea')) {
        message = validator.message.textarea;
      }
    }

    return methods._format.call(this, message, args);
  };

  methods._fields = function() {
    var self   = $(this);
    var fields = self.is('form') ? $(this).find(':input') : self;

    return fields.not(this.opt.ignore);
  }

  methods._format = function(message, args) {
    message = message || 'Message type missing!';

    var holders = message.match(/{[^}]*}/g);

    if (holders) {
      for (var i = 0; i < holders.length; i++) {
        if (i == args.length) {
          break;
        }

        message = message.replace(holders[i], args[i]);
      }
    }

    return message;
  };

  methods._highlight = function(result) {
    var inputs = result.el;

    if (helper.isCheckable(result.el[0])) {
      inputs = $(this).find('[name="' + result.el.attr('name') + '"]');
    }

    inputs.addClass(result.fail.length ? 'invalid' : 'valid')
  };

  methods._message = function(result) {
    var message = $('<div />', { id: result.el[0].hash, html: '<ul></ul>', 'class': 'validaty-message' });

    if (this.opt.errorTarget) {
      this.opt.errorTarget.call(this, result.el, message);
    } else {
      message.insertAfter(result.el[0]);
    }

    return message;
  };

  methods._process = function(fields, evt) {
    fields = (fields && fields.length && fields) || this.inputs;

    var distinct = methods._distinct.call(this, fields);
    var submit   = evt && evt.type === 'submit';

    for (var i = 0; i < fields.length; i++) {
      methods._clear.call(this, fields[i]);
    }

    for (var i = 0; i < distinct.length; i++) {
      var result = methods._validate.call(this, distinct[i]);

      if (result.fail.length > 0) {
        if (submit) {
          evt.preventDefault();
        }

        methods._display.call(this, result);
      }

      methods._highlight.call(this, result);
    }

    if (this.opt.focus && submit) {
      var forced = true;

      $(this).find('.invalid:visible:' + this.opt.focus).trigger('focus', forced);
    }

    var valid = !$(fields).filter('.invalid').length;

    $(this).data('valid', valid);

    if (valid) {
      this.opt.onValid && this.opt.onValid.call(this, fields, evt);
    } else {
      this.opt.onInvalid && this.opt.onInvalid.call(this, fields, evt);
    }
  };

  methods._validate = function(input) {
    var that        = this;
    var validations = helper.getParams(input).validations;

    input = $(input);

    var ignoreItems = [];
    var ignores     = input.attr('data-validaty-ignore');
    var result      = { el: input, pass: [], fail: [] };

    if (typeof ignores === 'string') {
      ignoreItems = ignores.split(' ');
    }

    for (var i = 0; i < validations.length; i++) {
      var name      = validations[i].name;
      var validator = that.opt.validators[name];

      if (!validator) {
        $.error('Validator "' + name + '" not registered!');
      }

      var args  = validations[i].args;
      var error = methods._error.call(that, input, validator, args);
      var valid = ignoreItems.includes(name) ? true : validator.validate.apply(input, [helper, that].concat(args));

      result[valid ? 'pass' : 'fail'].push({ type: name, message: error });
    }

    return result;
  };

  helper.getParams = function(input) {
    var data   = $(input).attr('data-validaty');
    var params = { validations: [], actions: [] };

    if (data) {
      var items = data.split(/\s+/);

      for (var i = 0; i < items.length; i++) {
        var args     = $(items[i].split(':')).get().reverse();
        var name     = args.pop();
        var isAction = /^on/.test(name);

        args = $(args).get().reverse();

        if (!isAction) {
          for (var j = 0; j < args.length; j++) {
            if (isNaN(args[j])) {
              args[j] = args[j].replace('%20', ' ');
            } else if (name !== 'equal') {
              args[j] = parseInt(args[j]);
            }
          }
        }

        params[isAction ? 'actions' : 'validations'].push({ name: name, args: args });
      }
    }

    return params;
  };

  helper.isCheckable = function(input) {
    return /checkbox|radio/i.test(input.type);
  };

  $.fn.validaty = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error('Method ' + method + ' does not exist!');
    }
  };

  $.fn.validaty.defaults = {
    errorTarget: undefined,
    focus:       'first',
    hash:        function() { return Math.random().toString().substring(2); },
    ignore:      ':submit, :reset, :image, :disabled',
    onInvalid:   undefined,
    onValid:     undefined,
    validators:  {},
  };

  $.validaty = {
    register: function() {
      $.fn.validaty.defaults.validators[arguments[0]] = { message: arguments[1], validate: arguments[2] };

      return this;
    }
  };
})(jQuery);