vanilla-validation
Version:
Vanilla JavaScript validation rules
668 lines (562 loc) • 22.4 kB
JavaScript
const moment = require('moment')
////////////////////////////////////////////////////
// JS OBJECT CONTAINING ALL VALIDATION RULES
////////////////////////////////////////////////////
const validationRules = {
// validation input has a value
vrule_required: function (elm, customMessage) {
let value = elm.value;
let val = value.replace("£", "")
if (val && val != "Please select...")
return true;
else {
if (customMessage === undefined || customMessage === null) {
return { message: "This question is required" };
} else {
return { message: customMessage };
}
}
},
// input requires a default value of 0 if nothing set
vrule_requiredZero: function (elm) {
if (elm.value) { return true; }
else {
// if currency input format '0'
if(elm.classList.contains("currency"))
elm.value = "£0"
else
elm.value = "0"
// return true to pass validation
return true
}
},
// validation input has a value
vrule_requiredIf: function (elm, validator) {
// element to validate against
let element = validator[0];
// element values to look for
let valRules = validator[1].split("||")
let isRequired = false
for (let i = 0; i < valRules.length; i++) {
if (element.value == valRules[i]) {
isRequired = true
}
}
if (isRequired) {
let val = elm.value;
if (val)
return true;
else
return { message: "This question is required" };
} else {
return true
}
},
// ensure checkbox is ticked
vrule_checkboxTrue: function (fieldset, customMessage) {
let checkbox = fieldset.querySelector("input[type='checkbox']");
if (checkbox.checked)
return true
else {
if (customMessage === undefined || customMessage === null) {
return { message: "Please tick this box to continue" };
} else {
return { message: customMessage };
}
}
},
// validation at least one button selected
vrule_btn_required: function (fieldset, customMessage) {
let buttons = fieldset.querySelectorAll("input[type='radio'], input[type='checkbox']")
let btnSelected = false;
Array.prototype.filter.call(buttons, function (button) {
if (button.checked)
btnSelected = true;
});
if (btnSelected)
return true;
else {
if (customMessage === undefined || customMessage === null) {
return { message: "Please select an option" };
} else {
return { message: customMessage };
}
}
},
// text input (letters and limited special characters)
vrule_textInput: function (elm) {
let val = elm.value;
var regex = /^[a-zA-Z'\-\s]+$/;
if (regex.test(val) || val == "")
return true;
else
return { message: "Please only enter letters" };
},
// number input (numbers only)
vrule_numberInput: function (elm) {
let val = elm.value;
var regex = /^\d+$/;
if (regex.test(val))
return true;
else
return { message: "Please only enter numbers" };
},
// validation input length is at least (dynamic)
vrule_minLength: function (elm, min, customMessage) {
let val = elm.value;
if (val.length >= min || val == "")
return true;
else {
if (customMessage === undefined || customMessage === null) {
return { message: "This field requires at least " + min + " characters" };
} else {
return { message: customMessage };
}
}
},
// validation input length is at least (dynamic)
vrule_maxLength: function (elm, max, customMessage) {
let val = elm.value;
if (val.length <= max || val == "")
return true;
else {
if (customMessage === undefined || customMessage === null) {
return { message: "This field can not contain more than " + max + " characters" };
} else {
return { message: customMessage };
}
}
},
// minimum value of input
vrule_minValue: function (elm, min, customMessage) {
let val = elm.value;
val = val.replace("£", "").replace(/,/g, "")
if (val >= min || val == "")
return true;
else {
console.log('custom message:', customMessage)
if (customMessage === undefined || customMessage === null) {
return { message: "This field requires minimum value of " + min };
} else {
return { message: customMessage };
}
}
},
// maximum value of input
vrule_maxValue: function (elm, min, customMessage) {
let val = elm.value;
val = val.replace("£", "").replace(/,/g, "")
if (val <= min || val == "")
return true;
else {
if (customMessage === undefined || customMessage === null) {
return { message: "This field requires maximum value of " + min };
} else {
return { message: customMessage };
}
}
},
// valid date format (e.g. month cant be 15)
vrule_date: function (date) {
let validDate = moment(date, "YYYY/MM/DD", true).isValid();
let nullDate = (date === null || date === undefined || date === '')
// if date is valid or empty
if (validDate || nullDate)
return true;
else {
let invalidType = moment(date, "YYYY/MM/DD", true).invalidAt();
if (invalidType == 1) { // invalid month
return {
message: "Please enter a valid date <br/>The <strong>month</strong> you entered is invalid",
type: "month"
};
} else if (invalidType == 2) { // invalid day
return {
message: "Please enter a valid date <br/>The <strong>day</strong> you entered is invalid",
type: "day"
};
} else // invalid general
return { message: "Please enter a valid date" };
}
},
// validate date required
vrule_date_required: function (date, customMessage) {
if (date)
return true;
else
if (customMessage === undefined || customMessage === null) {
return { message: "This question is required" };
} else {
return { message: customMessage };
}
},
// validate that input date is not before X date
vrule_notBeforeX: function(elm, dateCheck){
var date = moment(elm.value, "YYYY/MM/DD");
var dateX = moment(dateCheck, "YYYY/MM/DD");
var difference = date.diff(dateX, "years");
if (difference > 0)
return true
else
return { message: "The date you entered can not be before: " + dateCheck };
},
// age range (between 21-65)
vrule_ageRange: function (date, ageRange, customMessage) {
let start = ageRange[0]
let end = ageRange[1]
let today = moment()
let dob = moment(date, "YYYY/MM/DD")
let age = today.diff(dob, "years")
let nullDate = (date === null || date === undefined || date === '')
if ((age >= start && age <= end) || nullDate)
return true
else
if (customMessage === undefined || customMessage === null) {
return { message: "You must be aged between " + start + " and " + end };
} else {
return { message: customMessage };
}
},
// check age is at least 18
vrule_minAge: function (date, minAge) {
let birthday = moment(date, "DD MM YYYY")
let today = moment()
let age = today.diff(birthday, "years")
let nullDate = (date === null || date === undefined || date === '')
if (age >= minAge || nullDate)
return true;
else
return { message: "You must be at least " + minAge };
},
// valid mobile number
vrule_mobileNumber: function (elm) {
var phonenumber = elm.value;
var starts07 = phonenumber.substring(0, 2) == "07";
var mobileLength = (phonenumber.length == 11);
var containsLetters = /[a-zA-Z]/.test(phonenumber);
var containsSpaces = /\s/.test(phonenumber);
var onlyDigits = /^\d+$/.test(phonenumber);
if (onlyDigits && starts07 && mobileLength && !containsLetters && !containsSpaces || phonenumber == "")
return true
else if (containsSpaces)
return { message: "Please remove spaces from your phone number" };
else if (!onlyDigits && !containsLetters)
return { message: "Please remove any non-numerical characters from your phone number" };
else
return { message: "Please enter a valid mobile number" };
},
// valid home number
vrule_homeNumber: function (elm) {
var phonenumber = elm.value,
homeNumber = /^\(?0( *\d\)?){9,10}$/.test(phonenumber),
containsSpaces = /\s/.test(phonenumber);
if ((homeNumber || phonenumber == "") && !containsSpaces)
return true;
else if (containsSpaces)
return { message: "Please remove spaces from your phone number" };
else
return { message: "Please provide a valid home phone number" };
},
// valid home or mobile number
vrule_anyPhone: function (elm) {
var phonenumber = elm.value;
if (phonenumber == undefined)
phonenumber = elm[0].value;
var containsSpaces = /\s/.test(phonenumber),
homeNumberTest =
/^\(?0( *\d\)?){9,10}$/.test(phonenumber) &&
(phonenumber.substring(0, 2) == "01" || phonenumber.substring(0, 2) == "02"),
mobileNumberTest =
phonenumber.substring(0, 2) == "07" && phonenumber.length == 11 ||
phonenumber.substring(0, 4) == "+447" && phonenumber.length == 13 ||
phonenumber.substring(0, 3) == "447" && phonenumber.length == 12;
if ((homeNumberTest || mobileNumberTest) && !containsSpaces)
return true;
else
return { message: "Please provide a valid mobile or home phone number" };
},
// either a mobile or homephone number provided
vrule_mobileOrHomePhone: function (val, elm) {
var mainVal = val.value,
queryElmVal = $(elm[0]).val()
if (queryElmVal != "" || mainVal != "") {
if (queryElmVal != ""){
if (this.vrule_anyPhone($(elm[0]))){
$(elm[0]).siblings(".error-message").parent().removeClass("error").addClass("good");
return true;
} else {
return { message: "Please enter a valid mobile or home phone number" };
}
} else if (mainVal != ""){
if(this.vrule_anyPhone($(val))){
$(elm[0]).siblings(".error-message").parent().removeClass("error").addClass("good");
return true;
} else {
return { message: "Please enter a valid mobile or home phone number" };
}
}
} else {
return { message: "Please enter a mobile or home phone number" };
}
},
// valid email address
vrule_email: function (elm) {
var emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (emailRegex.test(elm.value))
return true;
else
return { message: "Please enter a valid email address" };
},
// currency format valid
vrule_currency: function (elm) {
let val = elm.value;
var regex = /^[\d£.,]+$/;
if (val) {
if (regex.test(val))
return true;
else
return { message: "This input can only contain numbers, a pound sign and commas e.g. £1,000" };
} else {
return true
}
},
// postcode valid
vrule_postcode: function (elm) {
var postcode = elm.value
let regex = /[A-Z]{1,2}[0-9][0-9A-Z]?\s?[0-9][A-Z]{2}/i;
// min length 5, max length 8, matches postcode regex
if (postcode.length >= 5 && postcode.length <= 8 && regex.test(postcode))
return true;
else
return { message: "Please enter a valid postcode" };
}
};
// end validation rules object
// VALIDATION FUNCTION
// function will get validation rules from html, run validation and either pass or fail
function validate(question, event) {
// debugging
if (debuggingActive) {
validationDebudding(question, event);
}
// turn off validation
if (!validationActive) {
return true;
}
// get validation rules from html
var fn = question.getAttribute("data-validation");
// split rules into array
fn = fn.split(", ");
// loop through array of validation rules and run each rule
for (var i = 0; i < fn.length; i++) {
// set empty paramater variable
var params = "";
// set vRule variable (the validation rule to be executed)
var vRule = fn[i];
// check if vRule has any parameters to use
if (vRule.indexOf("(") !== -1) {
// get parameters
params = getParameters(vRule);
// split vRule to allow me to remove brackets
vRule = vRule.split("(");
// get vRule function name (without any brackets)
vRule = vRule[0];
}
// validation based on specific vRule
if (validationRules[vRule](question, params) !== true) {
// VALIDATION FAILED
// add 'error' class to input parent
modifyClassesOnValidation(false, question);
// set error message
setError(false, vRule, question, params);
// return false to stop further validation
return false;
} else {
// VALIDATION PASSED
// add 'good' class to input parent
modifyClassesOnValidation(true, question);
// clear error messagse
setError(true, vRule, question);
}
}
// end validation
}
// add class function
function addClass(question, theClass) {
if (question.length) {
if (question[0].classList.contains("question")) {
question[0].classList.add(theClass);
} else {
// select
question[0].parentNode.parentNode.classList.add(theClass);
}
} else {
if (question.classList.contains("question")) {
question.classList.add(theClass);
} else {
question.parentNode.classList.add(theClass);
}
}
}
// remove class function
function removeClass(question, theClass) {
if (question.length) {
if (question[0].classList.contains("question")) {
question[0].classList.remove(theClass);
} else {
// select
question[0].parentNode.parentNode.classList.remove(theClass);
}
} else {
if (question.classList.contains("question")) {
question.classList.remove(theClass);
} else {
question.parentNode.classList.remove(theClass);
}
}
}
// get validation function parameters from html
function getParameters(rule) {
// split validation rule at bracket
var p = rule.split("(");
// get parameter from bracket
p = p[1];
// remove trailing bracket from parameter variable
p = p.replace(")", "");
// split paramters
p = p.split(",");
// return parameters
return p;
}
// using add / remove class functions, update classes based on validation
function modifyClassesOnValidation(isValid, question) {
if (isValid === false) {
// add classes to parent
addClass(question, "error");
removeClass(question, "good");
} else {
// add classes to parent
removeClass(question, "error");
addClass(question, "good");
}
}
// set error messages using validation rule 'message' property
function setError(isValid, vRule, question, params) {
var errorMessage = "",
errorMessageElm;
if (!isValid) {
// get required error message from validation rule
if (vRule == "vrule_required" || vRule == "vrule_btn_required" || vRule == "vrule_checkboxTrue" || vRule == "vrule_requiredIf")
errorMessage = question.getAttribute('data-val-message')
// if no error message set, fallback to validation error message
else
errorMessage = validationRules[vRule](question, params).message;
// get error-message element
if (question.tagName == "INPUT") { //question.matches("input") <- not valid in IE
if (!question.parentNode.classList.contains("question")) { // if a date input
errorMessageElm = question.parentNode.parentNode.querySelector(".error-message");
} else { // if standard input
errorMessageElm = question.parentNode.querySelector(".error-message");
}
} else if (question.tagName == "SELECT") { // question.matches("select") <- not valid in IE
errorMessageElm = question.parentNode.parentNode.querySelector(".error-message");
} else if (question.tagName == "FIELDSET") { // question.matches("fieldset") <- not valid in IE
errorMessageElm = question.querySelector(".error-message");
} else {
errorMessageElm = question.parentNode.querySelector(".error-message");
}
// set error message text
errorMessageElm.innerHTML = errorMessage;
}
}
/**
* Get all siblings of an element
* @param {Node} elem The element
* @return {Array} The siblings
*/
var getSiblings = function (elem, cssSelector) {
var siblings = [];
var sibling = elem.parentNode.firstChild;
for (; sibling; sibling = sibling.nextSibling) {
// if no cssSelector filter
if (sibling.nodeType === 1 && sibling !== elem && !cssSelector) {
siblings.push(sibling);
// use css selector
} else if (sibling.nodeType === 1 && sibling !== elem && (sibling.localName == cssSelector || sibling.classList.contains(cssSelector) || sibling.id == cssSelector || sibling.type == cssSelector)) {
siblings.push(sibling);
}
}
return siblings;
};
// function used if validation debugging active
function validationDebudding(question, event) {
// get validation rules from html
var fn = question.getAttribute("data-validation");
var questionValue = question.value;
// get question text
var questionBeingValidated;
if (question.tagName == "FIELDSET") { // if fieldset question // question.matches("fieldset") <- not valid in IE
// get question text from legend
questionBeingValidated = question.querySelector("legend").textContent;
// question value from selected button
if (fn == "vrule_checkboxTrue")
questionValue = question.querySelector("input[type=checkbox]").checked; //questionValue = "N/A buttons"
else
questionValue = question.querySelector("input[type=hidden]").value; //questionValue = "N/A buttons"
} else { // if regular input question
// get input siblings
var inputSiblings = getSiblings(question);
// find label element
Array.prototype.filter.call(inputSiblings, function (elm) {
// get question text from label
if (elm.tagName == "LABEL") // elm.matches("label") <- not valid in IE
questionBeingValidated = elm.textContent;
});
}
// show name of question being validated
console.error("question being validated: " + questionBeingValidated);
// show validation trigger method
console.log("validation trigger: " + event);
// show full string taken from HTML
console.warn("validation rules:");
console.log(fn);
// show value submitted
console.warn("input value:");
console.log(questionValue);
// split rules into array
fn = fn.split(", ");
// loop through array of validation rules and run each rule
for (var i = 0; i < fn.length; i++) {
// set empty paramater variable
var params = "";
// set vRule variable (the validation rule to be executed)
var vRule = fn[i];
// show which rule is being validated
console.warn("validating: " + vRule);
// check if vRule has any parameters to use
if (vRule.indexOf("(") !== -1) {
// get parameters
params = getParameters(vRule);
// split vRule to allow me to remove brackets
vRule = vRule.split("(");
// get vRule function name (without any brackets)
vRule = vRule[0];
}
// validation based on specific vRule
// if validation doesnt return 'true' set 'isValid' variable to false
if (validationRules[vRule](question, params) !== true) {
// validation failed, stop further validation
console.log("result: FAIL");
console.log(""); // space
console.log(""); // space
return false
} else {
// validation passed
console.log("result: PASS");
}
}
console.log(""); // space
console.log(""); // space
// end validation debugging
}
module.exports = validationRules