UNPKG

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
/** * @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