formvalidatorplus
Version:
This library offers a complete solution for validating, handling, and submitting forms of all complexities. From simple contact forms to intricate multi-step wizards, FormValidatorPlus empowers web developers to streamline the entire process. It handles
1,014 lines (982 loc) • 122 kB
JavaScript
/**
* @name FormValidator
* @description It validates and submits form for more details. It requires
* you to pass jquery as a parameter.
* visit https://github.com/Muhthishimiscoding/FormValidatorPlus
* @version 1.0.5
* @author Muhthishim Malik
* @link https://github.com/Muhthishimiscoding
* @license MIT
*/
(function(e){
if(typeof module === 'object') module.exports = e;
else{
[window.Validator, window.SubmitForm] = e(jQuery);
}
})(function($){
'use strict';
if (typeof $ !== 'function')
throw new Error("FormValidatorPlus works with jquery");
class Validator {
//----------------------Default Data Sets------------------------\\
// Error Message defaults objects
static errorMessages = {
alpha: "This field needs to contain only alphabatic characters i.e A to z.",
alpha_s: "This field can only have only alphabatic characters i.e A to z and space",
alphaNumeric: "This field can only contain alphanumeric characters.",
alphaNumeric_s: "Thie fields can only contain alphanumeric characters and space.",
barcode:"Please enter a valid barcode (UPC-E, UPC-A, EAN, EAN-14, SSCC).",
date: 'Please enter a valid date in the format YYYY-DD-MM.',
dateAll: 'Please enter a valid date',
dateTime: 'Please enter a valid date and time in the format YYYY-DD-MM HH:MM:SS.',
required: 'This field is required.',
email: 'Please provide a valid email address.',
// [Validator.RULE_MATCH]: 'This should match with the {match}.',
min: 'The field should contain a minimum of {min} characters.',
max: 'The field should contain a maximum of {max} characters.',
// [Validator.RULE_SPECIAL]: 'This field must contain a special character such as $, #, %, or @.',
noSpecial: 'This field should not contain any special characters like $, #, &, @, or >.',
space: 'This field must contain at least one space.',
noSpace: 'This field should not contain any spaces.',
lowerCase: 'This field should contain lowercase letters.',
upperCase: 'This field should contain uppercase letters.',
numb: 'This field should contain only digits.',
maxnumb: 'This field should not exceed {max} digits.',
minnumb: 'This field should contain at least {min} digits.',
makeInvalidEmpty: "Please make this field empty.",
tillDate: 'The date of this field should be selected on or before {tillDate}.',
shouldOld: 'Age Requirement: You must be at least {shouldOld} years old to access this feature/content.',
fileSize: 'The maximum allowed file size is {fileSize} and your file size is {fileSize2}.',
fileType: 'The allowed file types are {fileType}',
image: 'The uploaded file is not a valid image. Allowed image types are {image}.',
fileExt: 'The file is not of valid type allowed file types are {fileExt}.',
dimension_equal: 'The uploaded image has a width of {givenWidth}px and height of {givenHeight}px, while the required dimensions are {expectedWidth}x{expectedHeight} pixels.',
dimension_smallest: 'The smallest accepted width and height are {expectedWidth}x{expectedHeight} pixels, but your image is smaller than that, {givenWidth}x{givenHeight}.',
dimension_highest: 'The largest accepted width and height are {expectedWidth}x{expectedHeight} pixels, but your image is larger than that, {givenWidth}x{givenHeight}.',
dimension_width: 'The expected width for this image is {width}px.',
dimension_height: 'The expected height for this image is {height}px.',
dimension_square: 'This image needs to be a square, meaning it should have the same height and width, like 500x500 pixels.',
dimension_square_size: 'This image needs to be square with a width and height of {expectedWidth}x{expectedHeight} pixels.',
dimension_aspectRatio: 'The image must have an aspect ratio of {aspectRatio}.',
detectMultipleSpaces: 'This field has multiple consecutive spaces.',
accept: 'Please check this checkbox.',
range: 'The entered number must be between {num1} to {num2}.',
numb: 'This field needs to be a valid number without any space',
numb_space: 'This field needs to be a valid number.',
numb_space_double: 'This field contains more spaces then it should.',
password: 'This field needs to contain small case, uppercase, special characters i.e $,#,% etc and digits characters and has a length of atleast {password} characters.',
hasLowerCase: 'The field needs to have a lower case character.',
hasUpperCase: 'The field needs to have a upper case character.',
hasSpecial: 'The field needs to have a special character like $, #, @, & etc.',
hasDigit: 'The field needs to have a numeric digit [0-9].',
same: 'This field must match with the {same}.',
inList: 'The field must be of any of these {inList}.',
url: "Please enter a valid HTTP or HTTPS URL.",
url_ftp: "Please enter a valid HTTP or HTTPS or FTP URL.",
zipCode: "Please enter a valid ZIP/Postal code.",
json: "The provided JSON structure is not valid.",
ipv4: "Please enter a valid IPv4 address.",
ipv6: "Please enter a valid IPv6 address.",
isbn10: "Please enter a valid ISBN-10.",
upca: "Please enter a valid UPC-A (Universal Product Code).",
ean: "Please enter a valid EAN-10 or EAN-13 (European Article Number).",
};
/**
* Negative checks alpha numeric
* characters including space
* and period (.).
*/
static REG_SPECIAL = /[^\p{L}0-9\s.]/u;
static defaultMsg = "An unknown error occured";
static classes = ['is-invalid', 'invalid-feedback'];
static imgMimeTypes = [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/bmp',
'image/webp'
];
static changeCssClasses(classes){
if(classes.length != 2)
throw Error(`You should give 2 classes to replace default classes which are ${Validator.classes.join(', ')}.`);
Validator.classes = classes;
}
/**
* @param {object} rules In this object key would be input name
* attribute and value would be either a string or an array.
* An input can have multiple rules.
* Sending rules as a string after rulename you can add any
* rule constraint you want like in min case you need
* to tell how much mimnimum characters are bearable in
* username like 4
* rules : {
* username: 'required|min:4|max:12
* }
* As an array you can either pass pre configured
* static names for rules but remember that they are not
* configured for all types of rules to keep the library
* size smaller. Like image rule can only be send inside
* an array becuase it has an array of possible
* mime types for the images.
* rules :{
* username : [
* Validator.RULE_REQUIRED,
* [
* min,
* 6
* ],
* [
* 'image',
* [
* image/png',
* 'image/jpeg'
* ]
* ]
* ]
* }
* @param {object} errorMessages There are two ways to pass
* your error messages
* as one is by creating sub-object recommended
* when you have multipke rules for a single
* input and the second one is by using concatenated
* name like this if your input field has a name attribute
* "username" and you
* want to just send the error message for min error
* you can do it like this "username_min":"message"
* Example of first method
* errorMessages : {
* username : {
* min : "message",
* max : "message"
* },
* otherinput : {
* min : "message"
* }
* }
* Example of second way It is recommended for when input has
* only one rule but if you
* want you can use it with as many rules as you want.
*
* errorMessages : {
* username_min : "message"
* username_max : "Here max is rule name underscore is
* just for seperation and you
* can have as many underscore as you want in name attribute.",
* otherinput_min : "message"
* }
* Some error messages names are not same as
* functions they are listed here
* so if you want to pass your custom
* error messages for that rules you
* can do it easily.
*
* 1) alpha and alphaNumeric with spaces:
*
* If you want to allow space inside alpha and alphaNumeric
* fields then you can pass it's error
* messages key appended with
* _s.
* like alpha_s, and alphanumeric_s
*
* 2) url function :
*
* for url function if you want to allow ftp urls
* it error message should has a key url_ftp.
* and pass rules like this {input : "url:1"},
*
* 3) Password :
* There is no error message for password itself
* it uses these functions to validate password
* i) hasLowerCase
* ii) hasUpperCase
* iii) hasDigit
* iv) min
* v) hasSpecial
* So in order to use password you need to pass error
* messages with these functions name like
* {hasLowerCase: "Your password should have a lowrcase character."}
* If you don't want to pass this many 5 error messages
* then you can just pass a single one with password key
* like this:
* First way :
* {
* input : {
* password : "Msg"
* }
* }
* Second way :
* {
* input_password : "Msg"
* }
* here input is name attribute of your password input and password
* is rule name which is appended with "_".
*
* Last Image rule dimension error messsages settings.
* Dimension rule has many sub attributes like width, height.
* Way of giving error for this
* First way
* like this
* {
* input : {
* dimension : {
* width : "The width should be 80 px",
* height : "The height should be 90px."
* }
* }
* }
* Second way
* {
* input_dimesnion_width : "The width should be 80 px",
* input_dimension_height : "The height should be 90px."
*
* }
* @param {FormData | object | null} [dataToValidate=null] data
* which
* should be validated
*/
constructor(rules, errorMessages = {}, dataToValidate = null) {
this.customMessages = errorMessages;
this.rules = rules;
this.data = dataToValidate;
this.errors = {};
this.dateFormat = {};
/**
* Conditional errors
*/
this.conerrors = {};
}
/**
* Register a New Conditional Errors object with the given inputName and ruleName
* @param {string} inputName
* @param {string} ruleName
* You can access your conditional error object like this
* @code this.conerrors[inputName][ruleName].get(key)
* it is an object
* which contain condional errors for inputs
*/
regConErrors(inputName, ruleName) {
if (this.conerrors?.[inputName]?.[ruleName] == undefined) {
if (this.conerrors?.[inputName] == undefined) {
this.conerrors[inputName] = {};
}
this.conerrors[inputName][ruleName] = new Map();
}
return { inp: inputName, ruleName };
}
/**
* Sets the static property of
* REG_SPECIAL
* @param {RegExp} regex
*/
static setRegexSpecial(regex) {
Validator.REG_SPECIAL = regex;
}
/**
* @param {object} rule
*/
setRules(rules) {
this.rules = rules;
return this;
}
/**
* @param {object} customErrorMessages
* @returns {Validator}
*/
setErrorMessages(customErrorMessages) {
if (this.customMessages) {
this.customMessages = { ...this.customMessages, ...customErrorMessages };
} else {
this.customMessages = customErrorMessages;
}
return this;
}
static setImgMimeTypes(imgMimeTypes) {
Validator.imgMimeTypes = imgMimeTypes;
}
static get RULE_REQUIRED() {
return 'required';
}
/**
* Resolve howManyYears and give a calculated date
* @param {Number | Date | String | null} howManyYears
* @returns {Date}
*/
static getDate(howManyYears) {
let pastDate;
if (typeof howManyYears === 'number') {
pastDate = new Date();
pastDate.setFullYear(pastDate.getFullYear() - howManyYears);
pastDate.setHours(0, 0, 0, 0);
} else if (typeof howManyYears === 'string') {
pastDate = new Date(howManyYears);
} else if (howManyYears instanceof Date) {
pastDate = howManyYears;
} else {
//pass current date
pastDate = new Date();
}
return pastDate;
}
/**------------------EXTENDERS FOR INPUTS--------------------*/
/**
* Sets the max attribute for your input element date like if you want the user to
* select only between 2005 16 october it can do this for you.
* @param {string|HTMLInputElement} selector
* @param {null|Date|Number} [toDate=null] In number case it should be years count
*/
static setMaxdate(selector, toDate = null) {
let input = SubmitForm.selectElem(selector, HTMLInputElement, true);
let max = Validator.getDate(toDate).toISOString();
if(input.attr('type') ==='date'){
input[0].max = max.split('T')[0];
}else{
input[0].max = max.slice(0, max.lastIndexOf(':'));
}
}
/**
* Set the field a valid number
* @param {string|HTMLElement} selector
* @param {false|Number} [setMax=false] The max number which is allowed
* as input
*/
static setNum(selector, setMax = false) {
let input = SubmitForm.selectElem(selector, HTMLElement, true);
input.on('input', function () {
let s = input.val();
input.val(s.replace(/\D/g, ''))
if (setMax && s > setMax) {
input.val(s.slice(0, s.length - 1));
}
});
}
/**
* Set the Input as a formatted phone number input
* @param {string|HTMLInputElement|HTMLTextAreaElement} selector - Selector or
* element to format.
* @param {boolean} [allowCode=false] - Whether to allow country codes.
* @param {number} [numCount=10] - Maximum number of digits allowed in
* the phone number.
* @param {number[]} putSpaces - Array of positions where spaces should be added.
*/
static setPhone(selector, allowCode = false, numCount = 10, putSpaces = [2, 5]) {
let input = SubmitForm.selectElem(selector, HTMLElement, true);
input.on('input', function (e) {
let val = '', newVal = '', changePlus = false;
if (!allowCode)
// \D would match with any non-digit characters
val = input.val().replace(/\D/g, '');
else {
numCount = (numCount === 10) ? 13 : numCount;
val = input.val().replaceAll(/[^0-9+]/g, '');
if (!val.includes('+')) {
newVal = '+';
changePlus = true;
// return;
}
}
for (let i = 0; i < Math.min(val.length, numCount); i++) {
if (allowCode && newVal.includes('+') && val[i] == '+') continue;
newVal += val[i];
if (putSpaces.includes(i))
newVal += ' ';
}
//removes the extra spaces from the end of inputs
if (!changePlus && newVal === "+") newVal = '';
input.val(newVal.trimEnd());
});
}
/**
* @param {Date} date Date instansce
* @returns {string} formated string of date and time
*/
formatDateTimeWithAMPM(date) {
const months = [
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
];
let hours = date.getHours(),
minutes = date.getMinutes(),
ampm = hours >= 12 ? 'pm' : 'am';
// Convert hours to 12-hour format
hours = hours % 12;
hours = hours ? hours : 12; // 12 should be displayed as 12:00 PM, not 00:00 AM
// Add leading zeros to minutes if needed
minutes = minutes < 10 ? '0' + minutes : minutes;
const formattedDateTime = `${date.getDate()} ${months[date.getMonth()]},${date.getFullYear()} by ${hours}:${minutes} ${ampm}`;
return formattedDateTime;
}
/**
* Set up live form validation based on provided
* rules and custom error messages.
* @param {object} obj maybe just rules or an obect containing rules and
* errorMsgs
* @property {object} rules - Rules for validation.
* @property {object|undefined} [errorMsgs=undefined] - Custom error messages
* for validation rules.
* @property {function|null} [callback=null] - Callback function to
* execute on validation errors. Default is null if you don't provide any
* function it would use showErrors function. If you provide a function
* liveverifier would pass
* 3 parameters inside your callback
* i) First would be key which is name of the current input you
* pass inside rule
* ii) Second would be errors object
* which would contain errors for all inputs.
* iii) Third would be rules itself well it looks
* like why to pass rules when I gave it. Well
* rules would help you to verify that
* a particular input name has rules object
* but errors object doesn't have it's name
* so you can understand that it is a valid
* field.
* @throws {TypeError} - If callBack is not a function.
*/
static liveVerify(obj) {
// If object doesn't contain rule property it means the whole object is rule
let rules = obj?.rules || obj;
let verify = new Validator(rules, (obj?.errorMsgs || {})),
callback = obj?.callback || verify.showErrors.bind(verify),
conThrottle = obj?.conThrottle || 500,
throttle = obj?.throttle || 300;
if (typeof callback !== 'function') throw new TypeError("callback paratemer needs to be a function");
let debouncedEvent = verify.debounce(async function (input, rule, key, callback) {
// Debouncer would make this available here
let i = await this.runRules(this.getDataFromInp(input), key, rule);
if (i === 2) {
/**
* Verifier did not found any error
* inside this field.
*/
if (this.errors.hasOwnProperty(key)) {
delete this.errors[key];
}
}
callback(key, this.rules, this.errors, this);
}, throttle),
blurEvent = verify.debounce(function (input, key) {
/**
* On file case making val null because
* required function would select file by
* itself.
*/
let val = (input.type === 'file') ? null : input.value;
if (this.required(val, null, key) == 0) {
this.putError(key, 'required');
callback(key, this.rules, this.errors, this);
};
}, throttle);
for (const key in verify.rules) {
if (verify.rules.hasOwnProperty(key)) {
let input = $(`[name="${key}"]`),
rule = verify.resolveRule(verify.rules[key]),
bool = rule.includes('required'),
i = verify.runConrules(key, rule, conThrottle, callback);
if (i > -1) {
// Means an input can't have more than one conditional rules
continue;
}
if (bool) {
input.on('blur focus', () => {
blurEvent(input[0], key);
});
}
input.on('input', () => debouncedEvent(input[0], rule, key, callback));
}
}
}
/**
* Get the conditional rule and it's index inside in an array
* @param {Array} rule
* @returns {Array} Contains either one or 2 elements
*/
getConrule(rule) {
let allConRules = ['any_of', 'only_any_of'];
// Checking the rule array for any conditional rules
for (let i = 0; i < rule.length; i++) {
if (typeof rule[i] === 'object') {
for (const v of allConRules) {
if (rule[i].hasOwnProperty(v)) {
return [i, v];
}
}
}
}
return [-1];
}
/**
* Run a function with the given time
* @param {Function} callBack
* @param {Number} delay
* @param {object} options
* @param {...} args callBack parameters
* @returns {Function} Returns an async function which accepts args
*/
debounce(callBack, delay) {
let lastTimeExe = Date.now(), now, context = this, timeOut, timeSinceLastExe;
return async function (...args) {
now = Date.now();
timeSinceLastExe = now - lastTimeExe;
clearTimeout(timeOut);
timeOut = setTimeout(async () => {
await callBack.apply(context, args)
lastTimeExe = Date.now();
}, delay - timeSinceLastExe);
};
}
/**
* Verifies rules inside conditional rule
* @param {string} skey Sub Input name attribute from the loop which you run
* on input event
* @param {object} resolvedRules An object which contains resolved rules
* @param {string} key The main Input name attr where conditional rule is given
* @param {string} r Name of the rule like 'any_of' or 'only_any_of'
* @param {object} addError Which you get by regisitering conditional error
* for an input
* @param {Function} callback A function which would be used to show errors
* @param {boolean|undefined} makeInvalidEmpty A special parameter for
* 'only_any_of' rule
*/
async #verifyConRule(skey, resolvedRules, key, r, addError, callback, makeInvalidEmpty) {
let s = [], j;
//Running the loop on every input to make sure that input is valid or not
for (const nK in resolvedRules) {
if (resolvedRules.hasOwnProperty(nK)) {
j = await this.verify(this.getDataByKey(nK, true), nK, resolvedRules[nK], addError);
if (j === 2) {
if (r == 'only_any_of') {
s.push(nK);
} else {
// We are making s here equal to j because then we can check it is number or an array
s = j;
/**
* Verifier did not found any error
* inside this field.
*/
this.deleteErrors(resolvedRules, key, r);
break;
}
}
}
}
if (r === 'only_any_of') {
let n = this.only_any_of_errs(key, s, resolvedRules, makeInvalidEmpty, skey);
if (n == 1) {
this.deleteErrors(resolvedRules, key, r);
} else if (n == 3) {
// Here one field would be valid and other fields needs to be empty
delete this.errors[s[0]];
}
}
else if (this.conerrors[key][r].size > 0) {
this.errors[skey] = this.getUserError(skey, r) || this.conerrors[key][r].get(skey);
}
// callback(null, verify.errors, verify.rules, verify);
callback(null, resolvedRules);
}
/**
* Add conditional error messages in conerror
* @param {string} key Input name attribute
* @param {object} rules
* @param {Number} throttlingTime Time f
* @param {Function} callBack1
* @returns
*/
runConrules(key, rules, throttlingTime, callBack1) {
let [i, r] = this.getConrule(rules);
if (i > -1) {
let rule = (r == 'only_any_of') ? rules[i][r]['fields'] : rules[i][r],
ruleObj = this.regConErrors(key, r),
resolvedRules = {},
callBack = this.debounce(this.#verifyConRule, throttlingTime),
bool = rules[i][r]?.makeInvalidEmpty;
for (const skey in rule) {
if (rule.hasOwnProperty(skey)) {
resolvedRules[skey] = this.resolveRule(rule[skey]);
let e = $(`[name="${skey}"]`);
e.on('input', () => callBack(skey, resolvedRules, key, r, ruleObj, callBack1, bool));
}
}
return i;
}
}
deleteErrors(rule, key, r) {
this.conerrors[key][r].clear();
for (const n in rule) {
if (rule.hasOwnProperty(n)) {
delete this.errors[n];
}
}
}
//--------------------Returns file size in human readable format-------------------\\
static getfileSize(size) {
if (size < 1024) {
return size + ' bytes';
} else if (size < 1024 * 1024) {
const sizeInKB = size / 1024;
return sizeInKB % 1 === 0 ? sizeInKB + ' kb' : sizeInKB.toFixed(2) + ' kb';
} else if (size < 1024 * 1024 * 1024) {
const sizeInMB = size / (1024 * 1024);
return sizeInMB % 1 === 0 ? sizeInMB + ' mb' : sizeInMB.toFixed(2) + ' mb';
} else if (size < 1024 * 1024 * 1024 * 1024) {
const sizeInGB = size / (1024 * 1024 * 1024);
return sizeInGB % 1 === 0 ? sizeInGB + ' gb' : sizeInGB.toFixed(2) + ' gb';
} else {
const sizeInTB = size / (1024 * 1024 * 1024 * 1024);
return sizeInTB % 1 === 0 ? sizeInTB + ' TB' : sizeInTB.toFixed(2) + ' TB';
}
}
//------------------------Validator Extender-----------------------------\\
/**
* Add a function inside the validator
* @param {Function} callback
* @param {string} ruleName
* @param {string} errorMessage
* @returns {this}
* @throws TypeError
*/
addFunc(callback, ruleName, errorMessage) {
if (typeof callback === "function") {
if (typeof ruleName !== 'string' || typeof errorMessage !== 'string') throw new TypeError("You must provide ruleName and errorMessage for your function", callback);
if (typeof Validator.prototype[ruleName] === 'function' || typeof Validator.errorMessages[ruleName] == 'string') throw new TypeError("A rule or error message with this name already exists kindly rename your rule.");
Validator.prototype[ruleName] = callback;
Validator.errorMessages[ruleName] = errorMessage;
} else {
throw new Error(`${ruleName} does not refer to a function`);
}
}
/**
* @param {Function|object} callback If you pass a
* function here then next parameter till errorMessage
* must not be null, Every function you pass must
* should either return 1 or 0 or 2 (check details down below).
*
* In object case you can pass as many function as you
* want inisde that object
* like this
* {
* ruleName : {
* callback : function(value, extras, key, addError){
* return value.trim().length;
* },
* msg : "This field can't be empty"
* }
* }
*
* Parametrs Defination of callback:
* i) value:
* here value would be the input value where your applying rule
* ii) extras :
* extras would be null, object , string, bool
* basically extras are when you pass something
* with your rule as a constraint like this
* "min:6" here 6 is a extra
* dimension : {
* width : 80px,
* height : 100px
* }
* here whole object of dimension is extra
* like this {width : 80px, height : 100px}
*
* iii) key:
* would be name attribute of your input
*
* iv) addError:
* This is a boolean or an object which controls where the error should be added. If
* you are not adding your own error then you don't need to worry about it.
*
* 4) Return Values defination:
*
* i) 1:
* When your function would return one it means current rule
* didn't found any error.
*
* ii) 2:
* When your function would return 2 it means it has added it
* error in the errors object and after this the validator
* won't run any rule on your input because input already
* has an error. You can use putError function to put
* any errors inside error object.
* @see putError
*
* iii) 0 or false
* It means you rule has found error and then no other rule
* would apply on your input. The main difference b/w 2 and 0
* is that in 0 case verifier would add its error in
* errors object by itself and in 2 case you need
* to add your own error.
*
* @param {string|null} [ruleName=null] Rule name of your
* custom rule like required, email
* @param {string|null} errorMessage Error message for your rule
* Validator or null which is default
*/
static extend(callback, ruleName = null, errorMessage = null) {
if (typeof callback === 'function') {
Validator.prototype.addFunc(callback, ruleName, errorMessage);
} else {
for (const key in callback) {
if (callback.hasOwnProperty(key)) {
Validator.prototype.addFunc(callback[key].callback, key, callback[key].msg);
}
}
}
}
//--------------------WORK WITH ERROR MESSAEGS Data----------------------\\
getUserError(key, ruleName) {
return this.customMessages?.[key]?.[ruleName] || this.customMessages?.[`${key}_${ruleName}`];
}
getError(key, ruleName) {
return this.getUserError(key, ruleName) || Validator.errorMessages?.[ruleName] || Validator.defaultMsg;
}
/**
* @param {string} key Name input for
* @param {string} ruleConstraint real rule name
* of dimension like width, height, square etc.
* Way of giving error for this
* like this
* First way
* {
* input : {
* dimension : {
* width : "The width should be 80 px",
* height : "The height should be 90px."
* }
* }
* }
* Second way
* {
* input_dimesnion_width : "The width should be 80 px",
* input_dimension_height : "The height should be 90px."
*
* }
*/
getDimensionError(key, ruleConstraint, replaceMents = {}) {
let ruleName = 'dimension';
return this.customMessages?.[key]?.[ruleName]?.[ruleConstraint] || this.customMessages?.[`${key}_${ruleName}_${ruleConstraint}`] || Validator.strReplace(Validator.errorMessages[`${ruleName}_${ruleConstraint}`], replaceMents);
}
resolveDimensionErrors(key, errorSubName, array, width, height, addError) {
this.addErrorConditionally(key, this.getDimensionError(key, errorSubName, { givenWidth: width, givenHeight: height, expectedWidth: array[0], expectedHeight: array[1] }), addError)
// this.errors[key] = this.getDimensionError(key, errorSubName, { givenWidth: width, givenHeight: height, expectedWidth: array[0], expectedHeight: array[1] });
}
/**
* Return error as an string
* @param {string} k Input attribute
* @param {string} r Name of rule
* @param {string|null|object} e
* @returns {string}
*/
rE(attribute, ruleName, extras) {
let message = this.getError(attribute, ruleName);
if (extras) {
let msg = extras;
switch (ruleName) {
case 'tillDate':
if (typeof extras === 'number') extras = -extras;
msg = this.formatDateTimeWithAMPM(Validator.getDate(extras));
break;
case 'shouldOld':
if (extras instanceof Date) msg = extras.getFullYear();
break;
case 'image':
case 'fileType':
case 'inList':
case 'fileExt':
/**
* This time extras are array
* @var extras {array}
*/
msg = extras.join(', ');
break;
}
message = message.replace(`{${ruleName}}`, msg);
}
return message;
}
//--------------------------Adding errors-------------------------------\\
/**
*
* @param {string} attribute Input name attr
* @param {string} ruleName The rule which caused the error
* @param {array|string|null} extras any data related to this rule to
* show in error msg
*/
addError(k, msg) {
this.errors[k] = msg || Validator.defaultMsg;
return 2;
}
/**
* Add errors in errors object with replacements
* @param {string} key Name of the input you want to add error.
* @param {string} ruleName Name of your rule.
* @param {object|null} [replaceMents=null] is the parameter of strReplace
* @param {boolean|object} [addError=true] If it is true then it adds error
* in errors object and if it is an object then it adds error in conerrors
* which is a conditional error object and it's error messages are not shown
* on user interface until user add it's errors in errors object of
* Validator.
* @param {boolean} [curlyBraces=true] is the parameter of strReplace
* @returns {2}
* @see strReplace
*/
putError(key, ruleName, replaceMents = null, addError = true, curlyBraces = true) {
let msg;
if (!replaceMents) msg = this.getError(key, ruleName);
else msg = Validator.strReplace(this.getError(key, ruleName), replaceMents, curlyBraces);
return this.addErrorConditionally(key, msg, addError);
}
/**
* Handles adding error conditionally for conditional rules like any_of etc
* @param {string} key Input name attribute
* @param {string} errorMsg
* @param {object|true} addError
* @returns
*/
addErrorConditionally(key, errorMsg, addError) {
if (addError === true) {
this.errors[key] = errorMsg;
} else {
this.conerrors[addError.inp][addError.ruleName].set(key, errorMsg);
}
return 2;
}
/**
* Gets all errors from error object
* @returns {object}
*/
getErrors() {
return this.errors;
}
/**
* A string replacer for adding errors
* @param {string} str
* @param {object} replaceMents key should be placeholder/search Value and
* value should be replacing string
* @param {boolean} [curlyBraces=true] if you pass a placeholder
* in error message like this {min} then in replacements you just
* need to pass min it would automatically make it {min}
* if it false then it won't do this. So curlyBraces means true
* means to add the curly braces around your placeholder like
* you have an error message like this
* max : "This field should contain {max} characters."
* So here max is inside curlybraces {}.
* to replace this with help of this function
* you can easily pass it like this
* Validator.strReplace(Validator.errorMessages['max'], {max : 90})
* no need to pass like this {['{max}'] : 90}
*/
static strReplace(str, replaceMents, curlyBraces = true) {
for (const key in replaceMents) {
if (replaceMents.hasOwnProperty(key))
if (curlyBraces)
str = str.replace(`{${key}}`, replaceMents[key]);
else
str = str.replace(key, replaceMents[key]);
}
return str;
}
//---------------------SHOWING ERRORS---------------------\\
addOrRemoveClass(key, message, addClass = false) {
let elem = $(`[name="${key}"]`);
elem.removeClass(Validator.classes[0]);
elem.next().filter(`.${Validator.classes[1]}`)?.remove();
if (addClass) {
elem.addClass(Validator.classes[0]);
elem.after(`<div class="${Validator.classes[1]}">${message}</div>`);
}
}
baseShowErrors(key) {
if (this.errors.hasOwnProperty(key)) {
/**
* This field is not valid because errors object
* contains it keys.
*/
this.addOrRemoveClass(key, this.errors[key], true);
}
else {
/**
* It means it is a valid field
*/
this.addOrRemoveClass(key);
}
}
showErrors(key = null, rules) {
if (key) this.baseShowErrors(key);
else {
for (const key in rules) {
this.baseShowErrors(key);
}
}
}
showConErr(rule) {
for (const key in rule) {
if (rule.hasOwnProperty(key)) {
this.baseShowErrors(key);
}
}
}
//----------------Validators which run rules-----------------------\\
/**
* @param {object} obj can either only contain datatoValidate
* or can also have rules and custom error messages etc.
* @property {object|FormData} data |
* @param {object} rules
* @param {object | null} errorMsgs
* @param {Function|null} [callback=null] if you give give callback as a function
* then it must accept a single parameter an object of errors keys would be
* input names and values would be error messages. If it is null then it would
* automatically show errors.
*/
static async verifyData(obj, formValidator = null) {
let verify;
if (obj.hasOwnProperty('rules')) {
/**
* If rule is passed inside obj paramter
* then we need to get both rules and errorMessages from here
* otherwise we would insert data inside formValidator parameter.
*/
verify = new Validator(obj.rules, (obj?.errorMsgs || {}), obj.data);
} else if (formValidator instanceof Validator) {
verify = formValidator
verify.data = obj.data;
}
let callback = obj?.callback || verify.showErrors.bind(verify);
if (verify.data instanceof FormData) {
for (const key in verify.rules) {
await verify.verify(verify.data.getAll(key), key, verify.resolveRule(verify.rules[key]));
}
}
else if (typeof verify.data === 'object') {
for (const key in verify.rules) {
await verify.verify(verify.data[key], key, verify.resolveRule(verify.rules[key]));
}
}
let bool = verify.isValid();
if (!bool) {
if (typeof callback === 'function') {
callback(null, verify.rules, verify.errors, verify);
} else {
throw new TypeError("The given callback needs to be a function.");
}
}
return bool;
}
/**
* Is validation passes or fails
* @returns {boolean} true on validation passes and false on validation fails
*/
isValid() {
return Object.keys(this.errors).length === 0;
}
/**
* Verifies the data
* @param {FormDataEntryValue|string} value
* @param {string} key Name of the input where to apply rule
* @param {boolean|Array} [rules] Array of rules to verify
* @param {boolean|string} [addError=true] For conditional rules
* @returns {Promise<2|1>} 1 when error is found and 2 when no
* error was found
*/
async verify(value, key, rule, addError = true, runTillEnd = false) {
if (typeof value === 'undefin