Validation

by reesewill

HTML

<form method="post" novalidate>
  <p>
    <label>Name:
      <input type="text" name="name" data-rules="required">
    </label>
    <span class="error" aria-hidden="true" data-rule="required" data-for="name">This field is required</span>
  </p>
  <p>
    <label>Email:
      <input type="email" name="email" data-rules="required|email">
    </label>
    <span class="error" aria-hidden="true" data-rule="required" data-for="email">This field is required</span>
    <span class="error" aria-hidden="true" data-rule="email" data-for="email">Enter a valid email address</span>
  </p>
  <p>
    <label>Email: (Again)
      <input type="email" name="email2" data-rules="required|email|isTheSame" data-same="email">
    </label>
    <span class="error" aria-hidden="true" data-rule="required" data-for="email2">This field is required</span>
    <span class="error" aria-hidden="true" data-rule="email" data-for="email2">Enter a valid email address</span>
    <span class="error" aria-hidden="true" data-rule="isTheSame" data-for="email2">Not the same as other input</span>
  </p>
  <p>
    <label>Age:
      <input type="number" name="age" data-rules="required">
    </label>
    <span class="error" aria-hidden="true" data-rule="required" data-for="age">This field is required</span>
  </p>
  <p>
    <input type="submit" value="Submit">
  </p>
</form>

SCSS

.error {
  color: red;
  display: block;
  
  &[aria-hidden="true"] {
    display: none;
  }
}

JavaScript

function UndefinedRuleException(rule) {
	this.message = "The specified rule '" + rule + "' doesn't exist."
}

function Rule(name, condition, test) {
	this.name = name;
	this.condition = condition;
  this.test = test || ((value) => { return this.condition.test(value) });
}

// check that input is same as other input
function isTheSame(value, input) {
	var sameName = input.getAttribute('data-same');
  var sameInput = document.querySelector('[name="' + sameName + '"]');
  var valid = true;
  
  if (input.value && input.value != sameInput.value) {
		valid = false;
  }
  
  return valid;
}

var rules = {};
rules.required = new Rule('required', /.+/);
rules.email = new Rule('email', /^(.+@.+)?$/);
rules.isTheSame = new Rule('isTheSame', null, isTheSame);

function Validator(input /*, rules...*/) {
  this.input = input;
  this.rules = [];
  this.errorElements = {};
  
  var rulesList = this.input.getAttribute('data-rules').split('|');
  
  rulesList.forEach((rule) => {
  	if(rules[rule]) {
    	this.rules.push(rules[rule]);
      this.errorElements[rule] = document.querySelector('.error[data-rule="' + rule + '"][data-for="' + this.input.name + '"]');
    } else {
    	throw new UndefinedRuleException(rule);
    }
  });
  
  this.input.addEventListener('blur', () => { this.test() });
}
Validator.prototype.test = function() {
	var valid = true;
	this.rules.forEach((rule) => {
  	var test = true;
  	if (!rule.test(this.input.value, this.input)) {
    	test = false;
    	valid = false;
    }
    
    this.errorElements[rule.name].setAttribute('aria-hidden', test);
  });
  
  return valid;
};

Validator.init = function() {
	var validators = [];
	document.querySelectorAll('[data-rules]').forEach(function(el) {
		var validator = new Validator(el);
		el.Validator = validator;
    validators.push(validator);
  });
  
  document.querySelector('form').addEventListener('submit', (e) => {
  	var valid = true;
    validators.forEach((validator) => {
    	if (!validator.test()) {
    ...