From 8c1a3ea4e20501ed94c194d152376e8cef0238e2 Mon Sep 17 00:00:00 2001 From: Nicolas Cusan Date: Mon, 8 Feb 2021 17:07:24 +0100 Subject: [PATCH] Checkout validator lib --- dist/pristine.js | 662 +++++++++++++++++++++-------------------- dist/pristine.min.js | 2 +- package-lock.json | 2 +- src/pristine.js | 694 ++++++++++++++++++++++++------------------- 4 files changed, 733 insertions(+), 627 deletions(-) diff --git a/dist/pristine.js b/dist/pristine.js index e873ea7..eb3478f 100644 --- a/dist/pristine.js +++ b/dist/pristine.js @@ -17,7 +17,8 @@ min: "Minimum value for this field is ${1}", max: "Maximum value for this field is ${1}", pattern: "Please match the requested format", - equals: "The two fields do not match" + equals: "The two fields do not match", + default: "Please enter a correct value" } }; @@ -48,349 +49,370 @@ } var defaultConfig = { - classTo: 'form-group', - errorClass: 'has-danger', - successClass: 'has-success', - errorTextParent: 'form-group', - errorTextTag: 'div', - errorTextClass: 'text-help' + classTo: "form-group", + errorClass: "has-danger", + successClass: "has-success", + errorTextParent: "form-group", + errorTextTag: "div", + errorTextClass: "text-help" }; - var PRISTINE_ERROR = 'pristine-error'; + var PRISTINE_ERROR = "pristine-error"; var SELECTOR = "input:not([type^=hidden]):not([type^=submit]), select, textarea"; - var ALLOWED_ATTRIBUTES = ["required", "min", "max", 'minlength', 'maxlength', 'pattern']; + var ALLOWED_ATTRIBUTES = ["required", "min", "max", "minlength", "maxlength", "pattern"]; var EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\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,}))$/; var MESSAGE_REGEX = /-message(?:-([a-z]{2}(?:_[A-Z]{2})?))?/; // matches, -message, -message-en, -message-en_US - var currentLocale = 'en'; + var currentLocale = "en"; var validators = {}; var _ = function _(name, validator) { - validator.name = name; - if (validator.priority === undefined) validator.priority = 1; - validators[name] = validator; + validator.name = name; + if (validator.priority === undefined) validator.priority = 1; + validators[name] = validator; }; - _('text', { fn: function fn(val) { - return true; - }, priority: 0 }); - _('required', { fn: function fn(val) { - return this.type === 'radio' || this.type === 'checkbox' ? groupedElemCount(this) : val !== undefined && val !== ''; - }, priority: 99, halt: true }); - _('email', { fn: function fn(val) { - return !val || EMAIL_REGEX.test(val); - } }); - _('number', { fn: function fn(val) { - return !val || !isNaN(parseFloat(val)); - }, priority: 2 }); - _('integer', { fn: function fn(val) { - return !val || /^\d+$/.test(val); - } }); - _('minlength', { fn: function fn(val, length) { - return !val || val.length >= parseInt(length); - } }); - _('maxlength', { fn: function fn(val, length) { - return !val || val.length <= parseInt(length); - } }); - _('min', { fn: function fn(val, limit) { - return !val || (this.type === 'checkbox' ? groupedElemCount(this) >= parseInt(limit) : parseFloat(val) >= parseFloat(limit)); - } }); - _('max', { fn: function fn(val, limit) { - return !val || (this.type === 'checkbox' ? groupedElemCount(this) <= parseInt(limit) : parseFloat(val) <= parseFloat(limit)); - } }); - _('pattern', { fn: function fn(val, pattern) { - var m = pattern.match(new RegExp('^/(.*?)/([gimy]*)$'));return !val || new RegExp(m[1], m[2]).test(val); - } }); - _('equals', { fn: function fn(val, otherFieldSelector) { - var other = document.querySelector(otherFieldSelector);return other && (!val && !other.value || other.value === val); - } }); + _("text", { fn: function fn(val) { + return true; + }, priority: 0 }); + _("required", { + fn: function fn(val) { + return this.type === "radio" || this.type === "checkbox" ? groupedElemCount(this) : val !== undefined && val !== ""; + }, + priority: 99, + halt: true + }); + _("email", { fn: function fn(val) { + return !val || EMAIL_REGEX.test(val); + } }); + _("number", { fn: function fn(val) { + return !val || !isNaN(parseFloat(val)); + }, priority: 2 }); + _("integer", { fn: function fn(val) { + return !val || /^\d+$/.test(val); + } }); + _("minlength", { fn: function fn(val, length) { + return !val || val.length >= parseInt(length); + } }); + _("maxlength", { fn: function fn(val, length) { + return !val || val.length <= parseInt(length); + } }); + _("min", { + fn: function fn(val, limit) { + return !val || (this.type === "checkbox" ? groupedElemCount(this) >= parseInt(limit) : parseFloat(val) >= parseFloat(limit)); + } + }); + _("max", { + fn: function fn(val, limit) { + return !val || (this.type === "checkbox" ? groupedElemCount(this) <= parseInt(limit) : parseFloat(val) <= parseFloat(limit)); + } + }); + _("pattern", { + fn: function fn(val, pattern) { + var m = pattern.match(new RegExp("^/(.*?)/([gimy]*)$")); + return !val || new RegExp(m[1], m[2]).test(val); + } + }); + _("equals", { + fn: function fn(val, otherFieldSelector) { + var other = document.querySelector(otherFieldSelector); + return other && (!val && !other.value || other.value === val); + } + }); + + console.log(_); function Pristine(form, config, live) { - - var self = this; - - init(form, config, live); - - function init(form, config, live) { - - form.setAttribute("novalidate", "true"); - - self.form = form; - self.config = mergeConfig(config || {}, defaultConfig); - self.live = !(live === false); - self.fields = Array.from(form.querySelectorAll(SELECTOR)).map(function (input) { - - var fns = []; - var params = {}; - var messages = {}; - - [].forEach.call(input.attributes, function (attr) { - if (/^data-pristine-/.test(attr.name)) { - var name = attr.name.substr(14); - var messageMatch = name.match(MESSAGE_REGEX); - if (messageMatch !== null) { - var locale = messageMatch[1] === undefined ? 'en' : messageMatch[1]; - if (!messages.hasOwnProperty(locale)) messages[locale] = {}; - messages[locale][name.slice(0, name.length - messageMatch[0].length)] = attr.value; - return; - } - if (name === 'type') name = attr.value; - _addValidatorToField(fns, params, name, attr.value); - } else if (~ALLOWED_ATTRIBUTES.indexOf(attr.name)) { - _addValidatorToField(fns, params, attr.name, attr.value); - } else if (attr.name === 'type') { - _addValidatorToField(fns, params, attr.value); - } - }); - - fns.sort(function (a, b) { - return b.priority - a.priority; - }); - - self.live && input.addEventListener(!~['radio', 'checkbox'].indexOf(input.getAttribute('type')) ? 'input' : 'change', function (e) { - self.validate(e.target); - }.bind(self)); - - return input.pristine = { input: input, validators: fns, params: params, messages: messages, self: self }; - }.bind(self)); - } - - function _addValidatorToField(fns, params, name, value) { - var validator = validators[name]; - if (validator) { - fns.push(validator); - if (value) { - var valueParams = name === "pattern" ? [value] : value.split(','); - valueParams.unshift(null); // placeholder for input's value - params[name] = valueParams; - } + var self = this; + + init(form, config, live); + + function init(form, config, live) { + form.setAttribute("novalidate", "true"); + + self.form = form; + self.config = mergeConfig(config || {}, defaultConfig); + self.live = !(live === false); + self.fields = Array.from(form.querySelectorAll(SELECTOR)).map(function (input) { + var fns = []; + var params = {}; + var messages = {}; + + [].forEach.call(input.attributes, function (attr) { + if (/^data-pristine-/.test(attr.name)) { + var name = attr.name.substr(14); + var messageMatch = name.match(MESSAGE_REGEX); + if (messageMatch !== null) { + var locale = messageMatch[1] === undefined ? "en" : messageMatch[1]; + if (!messages.hasOwnProperty(locale)) messages[locale] = {}; + messages[locale][name.slice(0, name.length - messageMatch[0].length)] = attr.value; + return; + } + if (name === "type") name = attr.value; + _addValidatorToField(fns, params, name, attr.value); + } else if (~ALLOWED_ATTRIBUTES.indexOf(attr.name)) { + _addValidatorToField(fns, params, attr.name, attr.value); + } else if (attr.name === "type") { + _addValidatorToField(fns, params, attr.value); } + }); + + fns.sort(function (a, b) { + return b.priority - a.priority; + }); + + self.live && input.addEventListener(!~["radio", "checkbox"].indexOf(input.getAttribute("type")) ? "input" : "change", function (e) { + self.validate(e.target); + }.bind(self)); + + return input.pristine = { + input: input, + validators: fns, + params: params, + messages: messages, + self: self + }; + }.bind(self)); + } + + function _addValidatorToField(fns, params, name, value) { + var validator = validators[name]; + if (validator) { + fns.push(validator); + if (value) { + var valueParams = name === "pattern" ? [value] : value.split(","); + valueParams.unshift(null); // placeholder for input's value + params[name] = valueParams; + } + } + } + + /*** + * Checks whether the form/input elements are valid + * @param input => input element(s) or a jquery selector, null for full form validation + * @param silent => do not show error messages, just return true/false + * @returns {boolean} return true when valid false otherwise + */ + self.validate = function (input, silent) { + silent = input && silent === true || input === true; + var fields = self.fields; + if (input !== true && input !== false) { + if (input instanceof HTMLElement) { + fields = [input.pristine]; + } else if (input instanceof NodeList || input instanceof (window.$ || Array) || input instanceof Array) { + fields = Array.from(input).map(function (el) { + return el.pristine; + }); + } } - /*** - * Checks whether the form/input elements are valid - * @param input => input element(s) or a jquery selector, null for full form validation - * @param silent => do not show error messages, just return true/false - * @returns {boolean} return true when valid false otherwise - */ - self.validate = function (input, silent) { - silent = input && silent === true || input === true; - var fields = self.fields; - if (input !== true && input !== false) { - if (input instanceof HTMLElement) { - fields = [input.pristine]; - } else if (input instanceof NodeList || input instanceof (window.$ || Array) || input instanceof Array) { - fields = Array.from(input).map(function (el) { - return el.pristine; - }); - } - } - - var valid = true; + var valid = true; - for (var i = 0; fields[i]; i++) { - var field = fields[i]; - if (_validateField(field)) { - !silent && _showSuccess(field); - } else { - valid = false; - !silent && _showError(field); - } - } - return valid; - }; - - /*** - * Get errors of a specific field or the whole form - * @param input - * @returns {Array|*} - */ - self.getErrors = function (input) { - if (!input) { - var erroneousFields = []; - for (var i = 0; i < self.fields.length; i++) { - var field = self.fields[i]; - if (field.errors.length) { - erroneousFields.push({ input: field.input, errors: field.errors }); - } - } - return erroneousFields; - } - if (input.tagName && input.tagName.toLowerCase() === "select") { - return input.pristine.errors; - } - return input.length ? input[0].pristine.errors : input.pristine.errors; - }; - - /*** - * Validates a single field, all validator functions are called and error messages are generated - * when a validator fails - * @param field - * @returns {boolean} - * @private - */ - function _validateField(field) { - var errors = []; - var valid = true; - for (var i = 0; field.validators[i]; i++) { - var validator = field.validators[i]; - var params = field.params[validator.name] ? field.params[validator.name] : []; - params[0] = field.input.value; - if (!validator.fn.apply(field.input, params)) { - valid = false; - - if (typeof validator.msg === "function") { - errors.push(validator.msg(field.input.value, params)); - } else if (typeof validator.msg === "string") { - errors.push(tmpl.apply(validator.msg, params)); - } else if (validator.msg === Object(validator.msg) && validator.msg[currentLocale]) { - // typeof generates unnecessary babel code - errors.push(tmpl.apply(validator.msg[currentLocale], params)); - } else if (field.messages[currentLocale] && field.messages[currentLocale][validator.name]) { - errors.push(tmpl.apply(field.messages[currentLocale][validator.name], params)); - } else if (lang[currentLocale] && lang[currentLocale][validator.name]) { - errors.push(tmpl.apply(lang[currentLocale][validator.name], params)); - } - - if (validator.halt === true) { - break; - } - } - } - field.errors = errors; - return valid; + for (var i = 0; fields[i]; i++) { + var field = fields[i]; + if (_validateField(field)) { + !silent && _showSuccess(field); + } else { + valid = false; + !silent && _showError(field); + } } - - /*** - * Add a validator to a specific dom element in a form - * @param elem => The dom element where the validator is applied to - * @param fn => validator function - * @param msg => message to show when validation fails. Supports templating. ${0} for the input's value, ${1} and - * so on are for the attribute values - * @param priority => priority of the validator function, higher valued function gets called first. - * @param halt => whether validation should stop for this field after current validation function - */ - self.addValidator = function (elem, fn, msg, priority, halt) { - if (elem instanceof HTMLElement) { - elem.pristine.validators.push({ fn: fn, msg: msg, priority: priority, halt: halt }); - elem.pristine.validators.sort(function (a, b) { - return b.priority - a.priority; - }); - } else { - console.warn("The parameter elem must be a dom element"); + return valid; + }; + + /*** + * Get errors of a specific field or the whole form + * @param input + * @returns {Array|*} + */ + self.getErrors = function (input) { + if (!input) { + var erroneousFields = []; + for (var i = 0; i < self.fields.length; i++) { + var field = self.fields[i]; + if (field.errors.length) { + erroneousFields.push({ input: field.input, errors: field.errors }); } - }; - - /*** - * An utility function that returns a 2-element array, first one is the element where error/success class is - * applied. 2nd one is the element where error message is displayed. 2nd element is created if doesn't exist and cached. - * @param field - * @returns {*} - * @private - */ - function _getErrorElements(field) { - if (field.errorElements) { - return field.errorElements; - } - var errorClassElement = findAncestor(field.input, self.config.classTo); - var errorTextParent = null, - errorTextElement = null; - if (self.config.classTo === self.config.errorTextParent) { - errorTextParent = errorClassElement; + } + return erroneousFields; + } + if (input.tagName && input.tagName.toLowerCase() === "select") { + return input.pristine.errors; + } + return input.length ? input[0].pristine.errors : input.pristine.errors; + }; + + /*** + * Validates a single field, all validator functions are called and error messages are generated + * when a validator fails + * @param field + * @returns {boolean} + * @private + */ + function _validateField(field) { + var errors = []; + var valid = true; + for (var i = 0; field.validators[i]; i++) { + var validator = field.validators[i]; + var params = field.params[validator.name] ? field.params[validator.name] : []; + params[0] = field.input.value; + if (!validator.fn.apply(field.input, params)) { + valid = false; + + if (typeof validator.msg === "function") { + errors.push(validator.msg(field.input.value, params)); + } else if (typeof validator.msg === "string") { + errors.push(tmpl.apply(validator.msg, params)); + } else if (validator.msg === Object(validator.msg) && validator.msg[currentLocale]) { + // typeof generates unnecessary babel code + errors.push(tmpl.apply(validator.msg[currentLocale], params)); + } else if (field.messages[currentLocale] && field.messages[currentLocale][validator.name]) { + errors.push(tmpl.apply(field.messages[currentLocale][validator.name], params)); + } else if (lang[currentLocale] && lang[currentLocale][validator.name]) { + errors.push(tmpl.apply(lang[currentLocale][validator.name], params)); } else { - errorTextParent = errorClassElement.querySelector('.' + self.config.errorTextParent); + errors.push(tmpl.apply(lang[currentLocale].default, params)); } - if (errorTextParent) { - errorTextElement = errorTextParent.querySelector('.' + PRISTINE_ERROR); - if (!errorTextElement) { - errorTextElement = document.createElement(self.config.errorTextTag); - errorTextElement.className = PRISTINE_ERROR + ' ' + self.config.errorTextClass; - errorTextParent.appendChild(errorTextElement); - errorTextElement.pristineDisplay = errorTextElement.style.display; - } + + if (validator.halt === true) { + break; } - return field.errorElements = [errorClassElement, errorTextElement]; + } + } + field.errors = errors; + return valid; + } + + /*** + * Add a validator to a specific dom element in a form + * @param elem => The dom element where the validator is applied to + * @param fn => validator function + * @param msg => message to show when validation fails. Supports templating. ${0} for the input's value, ${1} and + * so on are for the attribute values + * @param priority => priority of the validator function, higher valued function gets called first. + * @param halt => whether validation should stop for this field after current validation function + */ + self.addValidator = function (elem, fn, msg, priority, halt) { + if (elem instanceof HTMLElement) { + elem.pristine.validators.push({ fn: fn, msg: msg, priority: priority, halt: halt }); + elem.pristine.validators.sort(function (a, b) { + return b.priority - a.priority; + }); + } else { + console.warn("The parameter elem must be a dom element"); + } + }; + + /*** + * An utility function that returns a 2-element array, first one is the element where error/success class is + * applied. 2nd one is the element where error message is displayed. 2nd element is created if doesn't exist and cached. + * @param field + * @returns {*} + * @private + */ + function _getErrorElements(field) { + if (field.errorElements) { + return field.errorElements; } + var errorClassElement = findAncestor(field.input, self.config.classTo); + var errorTextParent = null, + errorTextElement = null; + if (self.config.classTo === self.config.errorTextParent) { + errorTextParent = errorClassElement; + } else { + errorTextParent = errorClassElement.querySelector("." + self.config.errorTextParent); + } + if (errorTextParent) { + errorTextElement = errorTextParent.querySelector("." + PRISTINE_ERROR); + if (!errorTextElement) { + errorTextElement = document.createElement(self.config.errorTextTag); + errorTextElement.className = PRISTINE_ERROR + " " + self.config.errorTextClass; + errorTextParent.appendChild(errorTextElement); + errorTextElement.pristineDisplay = errorTextElement.style.display; + } + } + return field.errorElements = [errorClassElement, errorTextElement]; + } - function _showError(field) { - var errorElements = _getErrorElements(field); - var errorClassElement = errorElements[0], - errorTextElement = errorElements[1]; + function _showError(field) { + var errorElements = _getErrorElements(field); + var errorClassElement = errorElements[0], + errorTextElement = errorElements[1]; - if (errorClassElement) { - errorClassElement.classList.remove(self.config.successClass); - errorClassElement.classList.add(self.config.errorClass); - } - if (errorTextElement) { - errorTextElement.innerHTML = field.errors.join('
'); - errorTextElement.style.display = errorTextElement.pristineDisplay || ''; - } + if (errorClassElement) { + errorClassElement.classList.remove(self.config.successClass); + errorClassElement.classList.add(self.config.errorClass); } - - /*** - * Adds error to a specific field - * @param input - * @param error - */ - self.addError = function (input, error) { - input = input.length ? input[0] : input; - input.pristine.errors.push(error); - _showError(input.pristine); - }; - - function _removeError(field) { - var errorElements = _getErrorElements(field); - var errorClassElement = errorElements[0], - errorTextElement = errorElements[1]; - if (errorClassElement) { - // IE > 9 doesn't support multiple class removal - errorClassElement.classList.remove(self.config.errorClass); - errorClassElement.classList.remove(self.config.successClass); - } - if (errorTextElement) { - errorTextElement.innerHTML = ''; - errorTextElement.style.display = 'none'; - } - return errorElements; + if (errorTextElement) { + errorTextElement.innerHTML = field.errors.join("
"); + errorTextElement.style.display = errorTextElement.pristineDisplay || ""; } - - function _showSuccess(field) { - var errorClassElement = _removeError(field)[0]; - errorClassElement && errorClassElement.classList.add(self.config.successClass); + } + + /*** + * Adds error to a specific field + * @param input + * @param error + */ + self.addError = function (input, error) { + input = input.length ? input[0] : input; + input.pristine.errors.push(error); + _showError(input.pristine); + }; + + function _removeError(field) { + var errorElements = _getErrorElements(field); + var errorClassElement = errorElements[0], + errorTextElement = errorElements[1]; + if (errorClassElement) { + // IE > 9 doesn't support multiple class removal + errorClassElement.classList.remove(self.config.errorClass); + errorClassElement.classList.remove(self.config.successClass); } + if (errorTextElement) { + errorTextElement.innerHTML = ""; + errorTextElement.style.display = "none"; + } + return errorElements; + } + + function _showSuccess(field) { + var errorClassElement = _removeError(field)[0]; + errorClassElement && errorClassElement.classList.add(self.config.successClass); + } + + /*** + * Resets the errors + */ + self.reset = function () { + for (var i = 0; self.fields[i]; i++) { + self.fields[i].errorElements = null; + } + Array.from(self.form.querySelectorAll("." + PRISTINE_ERROR)).map(function (elem) { + elem.parentNode.removeChild(elem); + }); + Array.from(self.form.querySelectorAll("." + self.config.classTo)).map(function (elem) { + elem.classList.remove(self.config.successClass); + elem.classList.remove(self.config.errorClass); + }); + }; + + /*** + * Resets the errors and deletes all pristine fields + */ + self.destroy = function () { + self.reset(); + self.fields.forEach(function (field) { + delete field.input.pristine; + }); + self.fields = []; + }; - /*** - * Resets the errors - */ - self.reset = function () { - for (var i = 0; self.fields[i]; i++) { - self.fields[i].errorElements = null; - } - Array.from(self.form.querySelectorAll('.' + PRISTINE_ERROR)).map(function (elem) { - elem.parentNode.removeChild(elem); - }); - Array.from(self.form.querySelectorAll('.' + self.config.classTo)).map(function (elem) { - elem.classList.remove(self.config.successClass); - elem.classList.remove(self.config.errorClass); - }); - }; - - /*** - * Resets the errors and deletes all pristine fields - */ - self.destroy = function () { - self.reset(); - self.fields.forEach(function (field) { - delete field.input.pristine; - }); - self.fields = []; - }; - - self.setGlobalConfig = function (config) { - defaultConfig = config; - }; + self.setGlobalConfig = function (config) { + defaultConfig = config; + }; - return self; + return self; } /*** @@ -403,19 +425,19 @@ * @param halt => whether validation should stop for this field after current validation function */ Pristine.addValidator = function (name, fn, msg, priority, halt) { - _(name, { fn: fn, msg: msg, priority: priority, halt: halt }); + _(name, { fn: fn, msg: msg, priority: priority, halt: halt }); }; Pristine.addMessages = function (locale, messages) { - var langObj = lang.hasOwnProperty(locale) ? lang[locale] : lang[locale] = {}; + var langObj = lang.hasOwnProperty(locale) ? lang[locale] : lang[locale] = {}; - Object.keys(messages).forEach(function (key, index) { - langObj[key] = messages[key]; - }); + Object.keys(messages).forEach(function (key, index) { + langObj[key] = messages[key]; + }); }; Pristine.setLocale = function (locale) { - currentLocale = locale; + currentLocale = locale; }; return Pristine; diff --git a/dist/pristine.min.js b/dist/pristine.min.js index 75030ff..8c2656d 100644 --- a/dist/pristine.min.js +++ b/dist/pristine.min.js @@ -1 +1 @@ -!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(e="undefined"!=typeof globalThis?globalThis:e||self).Pristine=r()}(this,(function(){"use strict";var e={en:{required:"This field is required",email:"This field requires a valid e-mail address",number:"This field requires a number",integer:"This field requires an integer value",url:"This field requires a valid website URL",tel:"This field requires a valid telephone number",maxlength:"This fields length must be < ${1}",minlength:"This fields length must be > ${1}",min:"Minimum value for this field is ${1}",max:"Maximum value for this field is ${1}",pattern:"Please match the requested format",equals:"The two fields do not match"}};function r(e){var r=arguments;return this.replace(/\${([^{}]*)}/g,(function(e,t){return r[t]}))}function t(e){return e.pristine.self.form.querySelectorAll('input[name="'+e.getAttribute("name")+'"]:checked').length}var n={classTo:"form-group",errorClass:"has-danger",successClass:"has-success",errorTextParent:"form-group",errorTextTag:"div",errorTextClass:"text-help"},i=["required","min","max","minlength","maxlength","pattern"],s=/^(([^<>()\[\]\\.,;:\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,}))$/,a=/-message(?:-([a-z]{2}(?:_[A-Z]{2})?))?/,o="en",l={},u=function(e,r){r.name=e,void 0===r.priority&&(r.priority=1),l[e]=r};function f(t,s,u){var f=this;function c(e,r,t,n){var i=l[t];if(i&&(e.push(i),n)){var s="pattern"===t?[n]:n.split(",");s.unshift(null),r[t]=s}}function p(t){for(var n=[],i=!0,s=0;t.validators[s];s++){var a=t.validators[s],l=t.params[a.name]?t.params[a.name]:[];if(l[0]=t.input.value,!a.fn.apply(t.input,l)&&(i=!1,"function"==typeof a.msg?n.push(a.msg(t.input.value,l)):"string"==typeof a.msg?n.push(r.apply(a.msg,l)):a.msg===Object(a.msg)&&a.msg[o]?n.push(r.apply(a.msg[o],l)):t.messages[o]&&t.messages[o][a.name]?n.push(r.apply(t.messages[o][a.name],l)):e[o]&&e[o][a.name]&&n.push(r.apply(e[o][a.name],l)),!0===a.halt))break}return t.errors=n,i}function m(e){if(e.errorElements)return e.errorElements;var r=function(e,r){for(;(e=e.parentElement)&&!e.classList.contains(r););return e}(e.input,f.config.classTo),t=null,n=null;return(t=f.config.classTo===f.config.errorTextParent?r:r.querySelector("."+f.config.errorTextParent))&&((n=t.querySelector(".pristine-error"))||((n=document.createElement(f.config.errorTextTag)).className="pristine-error "+f.config.errorTextClass,t.appendChild(n),n.pristineDisplay=n.style.display)),e.errorElements=[r,n]}function d(e){var r=m(e),t=r[0],n=r[1];t&&(t.classList.remove(f.config.successClass),t.classList.add(f.config.errorClass)),n&&(n.innerHTML=e.errors.join("
"),n.style.display=n.pristineDisplay||"")}function h(e){var r=function(e){var r=m(e),t=r[0],n=r[1];return t&&(t.classList.remove(f.config.errorClass),t.classList.remove(f.config.successClass)),n&&(n.innerHTML="",n.style.display="none"),r}(e)[0];r&&r.classList.add(f.config.successClass)}return function(e,r,t){e.setAttribute("novalidate","true"),f.form=e,f.config=function(e,r){for(var t in r)t in e||(e[t]=r[t]);return e}(r||{},n),f.live=!(!1===t),f.fields=Array.from(e.querySelectorAll("input:not([type^=hidden]):not([type^=submit]), select, textarea")).map(function(e){var r=[],t={},n={};return[].forEach.call(e.attributes,(function(e){if(/^data-pristine-/.test(e.name)){var s=e.name.substr(14),o=s.match(a);if(null!==o){var l=void 0===o[1]?"en":o[1];return n.hasOwnProperty(l)||(n[l]={}),void(n[l][s.slice(0,s.length-o[0].length)]=e.value)}"type"===s&&(s=e.value),c(r,t,s,e.value)}else~i.indexOf(e.name)?c(r,t,e.name,e.value):"type"===e.name&&c(r,t,e.value)})),r.sort((function(e,r){return r.priority-e.priority})),f.live&&e.addEventListener(~["radio","checkbox"].indexOf(e.getAttribute("type"))?"change":"input",function(e){f.validate(e.target)}.bind(f)),e.pristine={input:e,validators:r,params:t,messages:n,self:f}}.bind(f))}(t,s,u),f.validate=function(e,r){r=e&&!0===r||!0===e;var t=f.fields;!0!==e&&!1!==e&&(e instanceof HTMLElement?t=[e.pristine]:(e instanceof NodeList||e instanceof(window.$||Array)||e instanceof Array)&&(t=Array.from(e).map((function(e){return e.pristine}))));for(var n=!0,i=0;t[i];i++){var s=t[i];p(s)?!r&&h(s):(n=!1,!r&&d(s))}return n},f.getErrors=function(e){if(!e){for(var r=[],t=0;t=parseInt(r)}}),u("maxlength",{fn:function(e,r){return!e||e.length<=parseInt(r)}}),u("min",{fn:function(e,r){return!e||("checkbox"===this.type?t(this)>=parseInt(r):parseFloat(e)>=parseFloat(r))}}),u("max",{fn:function(e,r){return!e||("checkbox"===this.type?t(this)<=parseInt(r):parseFloat(e)<=parseFloat(r))}}),u("pattern",{fn:function(e,r){var t=r.match(new RegExp("^/(.*?)/([gimy]*)$"));return!e||new RegExp(t[1],t[2]).test(e)}}),u("equals",{fn:function(e,r){var t=document.querySelector(r);return t&&(!e&&!t.value||t.value===e)}}),f.addValidator=function(e,r,t,n,i){u(e,{fn:r,msg:t,priority:n,halt:i})},f.addMessages=function(r,t){var n=e.hasOwnProperty(r)?e[r]:e[r]={};Object.keys(t).forEach((function(e,r){n[e]=t[e]}))},f.setLocale=function(e){o=e},f})); +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(e="undefined"!=typeof globalThis?globalThis:e||self).Pristine=r()}(this,(function(){"use strict";var e={en:{required:"This field is required",email:"This field requires a valid e-mail address",number:"This field requires a number",integer:"This field requires an integer value",url:"This field requires a valid website URL",tel:"This field requires a valid telephone number",maxlength:"This fields length must be < ${1}",minlength:"This fields length must be > ${1}",min:"Minimum value for this field is ${1}",max:"Maximum value for this field is ${1}",pattern:"Please match the requested format",equals:"The two fields do not match",default:"Please enter a correct value"}};function r(e){var r=arguments;return this.replace(/\${([^{}]*)}/g,(function(e,t){return r[t]}))}function t(e){return e.pristine.self.form.querySelectorAll('input[name="'+e.getAttribute("name")+'"]:checked').length}var n={classTo:"form-group",errorClass:"has-danger",successClass:"has-success",errorTextParent:"form-group",errorTextTag:"div",errorTextClass:"text-help"},i=["required","min","max","minlength","maxlength","pattern"],s=/^(([^<>()\[\]\\.,;:\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,}))$/,a=/-message(?:-([a-z]{2}(?:_[A-Z]{2})?))?/,o="en",l={},u=function(e,r){r.name=e,void 0===r.priority&&(r.priority=1),l[e]=r};function f(t,s,u){var f=this;function c(e,r,t,n){var i=l[t];if(i&&(e.push(i),n)){var s="pattern"===t?[n]:n.split(",");s.unshift(null),r[t]=s}}function p(t){for(var n=[],i=!0,s=0;t.validators[s];s++){var a=t.validators[s],l=t.params[a.name]?t.params[a.name]:[];if(l[0]=t.input.value,!a.fn.apply(t.input,l)&&(i=!1,"function"==typeof a.msg?n.push(a.msg(t.input.value,l)):"string"==typeof a.msg?n.push(r.apply(a.msg,l)):a.msg===Object(a.msg)&&a.msg[o]?n.push(r.apply(a.msg[o],l)):t.messages[o]&&t.messages[o][a.name]?n.push(r.apply(t.messages[o][a.name],l)):e[o]&&e[o][a.name]?n.push(r.apply(e[o][a.name],l)):n.push(r.apply(e[o].default,l)),!0===a.halt))break}return t.errors=n,i}function m(e){if(e.errorElements)return e.errorElements;var r=function(e,r){for(;(e=e.parentElement)&&!e.classList.contains(r););return e}(e.input,f.config.classTo),t=null,n=null;return(t=f.config.classTo===f.config.errorTextParent?r:r.querySelector("."+f.config.errorTextParent))&&((n=t.querySelector(".pristine-error"))||((n=document.createElement(f.config.errorTextTag)).className="pristine-error "+f.config.errorTextClass,t.appendChild(n),n.pristineDisplay=n.style.display)),e.errorElements=[r,n]}function d(e){var r=m(e),t=r[0],n=r[1];t&&(t.classList.remove(f.config.successClass),t.classList.add(f.config.errorClass)),n&&(n.innerHTML=e.errors.join("
"),n.style.display=n.pristineDisplay||"")}function h(e){var r=function(e){var r=m(e),t=r[0],n=r[1];return t&&(t.classList.remove(f.config.errorClass),t.classList.remove(f.config.successClass)),n&&(n.innerHTML="",n.style.display="none"),r}(e)[0];r&&r.classList.add(f.config.successClass)}return function(e,r,t){e.setAttribute("novalidate","true"),f.form=e,f.config=function(e,r){for(var t in r)t in e||(e[t]=r[t]);return e}(r||{},n),f.live=!(!1===t),f.fields=Array.from(e.querySelectorAll("input:not([type^=hidden]):not([type^=submit]), select, textarea")).map(function(e){var r=[],t={},n={};return[].forEach.call(e.attributes,(function(e){if(/^data-pristine-/.test(e.name)){var s=e.name.substr(14),o=s.match(a);if(null!==o){var l=void 0===o[1]?"en":o[1];return n.hasOwnProperty(l)||(n[l]={}),void(n[l][s.slice(0,s.length-o[0].length)]=e.value)}"type"===s&&(s=e.value),c(r,t,s,e.value)}else~i.indexOf(e.name)?c(r,t,e.name,e.value):"type"===e.name&&c(r,t,e.value)})),r.sort((function(e,r){return r.priority-e.priority})),f.live&&e.addEventListener(~["radio","checkbox"].indexOf(e.getAttribute("type"))?"change":"input",function(e){f.validate(e.target)}.bind(f)),e.pristine={input:e,validators:r,params:t,messages:n,self:f}}.bind(f))}(t,s,u),f.validate=function(e,r){r=e&&!0===r||!0===e;var t=f.fields;!0!==e&&!1!==e&&(e instanceof HTMLElement?t=[e.pristine]:(e instanceof NodeList||e instanceof(window.$||Array)||e instanceof Array)&&(t=Array.from(e).map((function(e){return e.pristine}))));for(var n=!0,i=0;t[i];i++){var s=t[i];p(s)?!r&&h(s):(n=!1,!r&&d(s))}return n},f.getErrors=function(e){if(!e){for(var r=[],t=0;t=parseInt(r)}}),u("maxlength",{fn:function(e,r){return!e||e.length<=parseInt(r)}}),u("min",{fn:function(e,r){return!e||("checkbox"===this.type?t(this)>=parseInt(r):parseFloat(e)>=parseFloat(r))}}),u("max",{fn:function(e,r){return!e||("checkbox"===this.type?t(this)<=parseInt(r):parseFloat(e)<=parseFloat(r))}}),u("pattern",{fn:function(e,r){var t=r.match(new RegExp("^/(.*?)/([gimy]*)$"));return!e||new RegExp(t[1],t[2]).test(e)}}),u("equals",{fn:function(e,r){var t=document.querySelector(r);return t&&(!e&&!t.value||t.value===e)}}),console.log(u),f.addValidator=function(e,r,t,n,i){u(e,{fn:r,msg:t,priority:n,halt:i})},f.addMessages=function(r,t){var n=e.hasOwnProperty(r)?e[r]:e[r]={};Object.keys(t).forEach((function(e,r){n[e]=t[e]}))},f.setLocale=function(e){o=e},f})); diff --git a/package-lock.json b/package-lock.json index 13709d2..1da603b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pristinejs", - "version": "0.1.8", + "version": "0.1.9", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/pristine.js b/src/pristine.js index 6fd296a..916f604 100755 --- a/src/pristine.js +++ b/src/pristine.js @@ -1,325 +1,407 @@ -import { lang } from './lang'; -import { tmpl, findAncestor, groupedElemCount, mergeConfig } from './utils'; +import { lang } from "./lang"; +import { tmpl, findAncestor, groupedElemCount, mergeConfig } from "./utils"; let defaultConfig = { - classTo: 'form-group', - errorClass: 'has-danger', - successClass: 'has-success', - errorTextParent: 'form-group', - errorTextTag: 'div', - errorTextClass: 'text-help' + classTo: "form-group", + errorClass: "has-danger", + successClass: "has-success", + errorTextParent: "form-group", + errorTextTag: "div", + errorTextClass: "text-help", }; -const PRISTINE_ERROR = 'pristine-error'; -const SELECTOR = "input:not([type^=hidden]):not([type^=submit]), select, textarea"; -const ALLOWED_ATTRIBUTES = ["required", "min", "max", 'minlength', 'maxlength', 'pattern']; -const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\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,}))$/ +const PRISTINE_ERROR = "pristine-error"; +const SELECTOR = + "input:not([type^=hidden]):not([type^=submit]), select, textarea"; +const ALLOWED_ATTRIBUTES = [ + "required", + "min", + "max", + "minlength", + "maxlength", + "pattern", +]; +const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\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,}))$/; const MESSAGE_REGEX = /-message(?:-([a-z]{2}(?:_[A-Z]{2})?))?/; // matches, -message, -message-en, -message-en_US -let currentLocale = 'en'; +let currentLocale = "en"; const validators = {}; const _ = function (name, validator) { - validator.name = name; - if (validator.priority === undefined) - validator.priority = 1; - validators[name] = validator; + validator.name = name; + if (validator.priority === undefined) validator.priority = 1; + validators[name] = validator; }; -_('text', { fn: (val) => true, priority: 0}); -_('required', { fn: function(val){ return (this.type === 'radio' || this.type === 'checkbox') ? groupedElemCount(this) : val !== undefined && val !== ''}, priority: 99, halt: true}); -_('email', { fn: (val) => !val || EMAIL_REGEX.test(val)}); -_('number', { fn: (val) => !val || !isNaN(parseFloat(val)), priority: 2 }); -_('integer', { fn: (val) => !val || /^\d+$/.test(val) }); -_('minlength', { fn: (val, length) => !val || val.length >= parseInt(length) }); -_('maxlength', { fn: (val, length) => !val || val.length <= parseInt(length) }); -_('min', { fn: function(val, limit){ return !val || (this.type === 'checkbox' ? groupedElemCount(this) >= parseInt(limit) : parseFloat(val) >= parseFloat(limit)); } }); -_('max', { fn: function(val, limit){ return !val || (this.type === 'checkbox' ? groupedElemCount(this) <= parseInt(limit) : parseFloat(val) <= parseFloat(limit)); } }); -_('pattern', { fn: (val, pattern) => { let m = pattern.match(new RegExp('^/(.*?)/([gimy]*)$')); return !val || (new RegExp(m[1], m[2])).test(val);} }); -_('equals', { fn: (val, otherFieldSelector) => { let other = document.querySelector(otherFieldSelector); return (other) && ((!val && !other.value) || (other.value === val)); } }); - -export default function Pristine(form, config, live){ - - let self = this; - - init(form, config, live); - - function init(form, config, live){ - - form.setAttribute("novalidate", "true"); - - self.form = form; - self.config = mergeConfig(config || {}, defaultConfig); - self.live = !(live === false); - self.fields = Array.from(form.querySelectorAll(SELECTOR)).map(function (input) { - - let fns = []; - let params = {}; - let messages = {}; - - [].forEach.call(input.attributes, function (attr) { - if (/^data-pristine-/.test(attr.name)) { - let name = attr.name.substr(14); - let messageMatch = name.match(MESSAGE_REGEX); - if (messageMatch !== null){ - let locale = messageMatch[1] === undefined ? 'en' : messageMatch[1]; - if (!messages.hasOwnProperty(locale)) - messages[locale] = {}; - messages[locale][name.slice(0, name.length - messageMatch[0].length)] = attr.value; - return; - } - if (name === 'type') name = attr.value; - _addValidatorToField(fns, params, name, attr.value); - } else if (~ALLOWED_ATTRIBUTES.indexOf(attr.name)){ - _addValidatorToField(fns, params, attr.name, attr.value); - } else if (attr.name === 'type'){ - _addValidatorToField(fns, params, attr.value); - } - }); - - fns.sort( (a, b) => b.priority - a.priority); - - self.live && input.addEventListener((!~['radio', 'checkbox'].indexOf(input.getAttribute('type')) ? 'input':'change'), function(e) { - self.validate(e.target); - }.bind(self)); - - return input.pristine = {input, validators: fns, params, messages, self}; - - }.bind(self)); - } - - function _addValidatorToField(fns, params, name, value) { - let validator = validators[name]; - if (validator) { - fns.push(validator); - if (value) { - let valueParams = (name === "pattern" ? [value]: value.split(',')); - valueParams.unshift(null); // placeholder for input's value - params[name] = valueParams; +_("text", { fn: (val) => true, priority: 0 }); +_("required", { + fn: function (val) { + return this.type === "radio" || this.type === "checkbox" + ? groupedElemCount(this) + : val !== undefined && val !== ""; + }, + priority: 99, + halt: true, +}); +_("email", { fn: (val) => !val || EMAIL_REGEX.test(val) }); +_("number", { fn: (val) => !val || !isNaN(parseFloat(val)), priority: 2 }); +_("integer", { fn: (val) => !val || /^\d+$/.test(val) }); +_("minlength", { fn: (val, length) => !val || val.length >= parseInt(length) }); +_("maxlength", { fn: (val, length) => !val || val.length <= parseInt(length) }); +_("min", { + fn: function (val, limit) { + return ( + !val || + (this.type === "checkbox" + ? groupedElemCount(this) >= parseInt(limit) + : parseFloat(val) >= parseFloat(limit)) + ); + }, +}); +_("max", { + fn: function (val, limit) { + return ( + !val || + (this.type === "checkbox" + ? groupedElemCount(this) <= parseInt(limit) + : parseFloat(val) <= parseFloat(limit)) + ); + }, +}); +_("pattern", { + fn: (val, pattern) => { + let m = pattern.match(new RegExp("^/(.*?)/([gimy]*)$")); + return !val || new RegExp(m[1], m[2]).test(val); + }, +}); +_("equals", { + fn: (val, otherFieldSelector) => { + let other = document.querySelector(otherFieldSelector); + return other && ((!val && !other.value) || other.value === val); + }, +}); + +console.log(_); + +export default function Pristine(form, config, live) { + let self = this; + + init(form, config, live); + + function init(form, config, live) { + form.setAttribute("novalidate", "true"); + + self.form = form; + self.config = mergeConfig(config || {}, defaultConfig); + self.live = !(live === false); + self.fields = Array.from(form.querySelectorAll(SELECTOR)).map( + function (input) { + let fns = []; + let params = {}; + let messages = {}; + + [].forEach.call(input.attributes, function (attr) { + if (/^data-pristine-/.test(attr.name)) { + let name = attr.name.substr(14); + let messageMatch = name.match(MESSAGE_REGEX); + if (messageMatch !== null) { + let locale = + messageMatch[1] === undefined ? "en" : messageMatch[1]; + if (!messages.hasOwnProperty(locale)) messages[locale] = {}; + messages[locale][ + name.slice(0, name.length - messageMatch[0].length) + ] = attr.value; + return; } - } - } + if (name === "type") name = attr.value; + _addValidatorToField(fns, params, name, attr.value); + } else if (~ALLOWED_ATTRIBUTES.indexOf(attr.name)) { + _addValidatorToField(fns, params, attr.name, attr.value); + } else if (attr.name === "type") { + _addValidatorToField(fns, params, attr.value); + } + }); - /*** - * Checks whether the form/input elements are valid - * @param input => input element(s) or a jquery selector, null for full form validation - * @param silent => do not show error messages, just return true/false - * @returns {boolean} return true when valid false otherwise - */ - self.validate = function(input, silent){ - silent = (input && silent === true) || input === true; - let fields = self.fields; - if (input !== true && input !== false){ - if (input instanceof HTMLElement) { - fields = [input.pristine]; - } else if (input instanceof NodeList || input instanceof (window.$ || Array) || input instanceof Array){ - fields = Array.from(input).map(el => el.pristine); - } - } + fns.sort((a, b) => b.priority - a.priority); + + self.live && + input.addEventListener( + !~["radio", "checkbox"].indexOf(input.getAttribute("type")) + ? "input" + : "change", + function (e) { + self.validate(e.target); + }.bind(self) + ); + + return (input.pristine = { + input, + validators: fns, + params, + messages, + self, + }); + }.bind(self) + ); + } + + function _addValidatorToField(fns, params, name, value) { + let validator = validators[name]; + if (validator) { + fns.push(validator); + if (value) { + let valueParams = name === "pattern" ? [value] : value.split(","); + valueParams.unshift(null); // placeholder for input's value + params[name] = valueParams; + } + } + } + + /*** + * Checks whether the form/input elements are valid + * @param input => input element(s) or a jquery selector, null for full form validation + * @param silent => do not show error messages, just return true/false + * @returns {boolean} return true when valid false otherwise + */ + self.validate = function (input, silent) { + silent = (input && silent === true) || input === true; + let fields = self.fields; + if (input !== true && input !== false) { + if (input instanceof HTMLElement) { + fields = [input.pristine]; + } else if ( + input instanceof NodeList || + input instanceof (window.$ || Array) || + input instanceof Array + ) { + fields = Array.from(input).map((el) => el.pristine); + } + } - let valid = true; + let valid = true; - for(let i = 0; fields[i]; i++) { - let field = fields[i]; - if (_validateField(field)){ - !silent && _showSuccess(field); - } else { - valid = false; - !silent && _showError(field); - } - } - return valid; - }; - - /*** - * Get errors of a specific field or the whole form - * @param input - * @returns {Array|*} - */ - self.getErrors = function(input) { - if (!input){ - let erroneousFields = []; - for(let i=0; i The dom element where the validator is applied to - * @param fn => validator function - * @param msg => message to show when validation fails. Supports templating. ${0} for the input's value, ${1} and - * so on are for the attribute values - * @param priority => priority of the validator function, higher valued function gets called first. - * @param halt => whether validation should stop for this field after current validation function - */ - self.addValidator = function(elem, fn, msg, priority, halt){ - if (elem instanceof HTMLElement){ - elem.pristine.validators.push({fn, msg, priority, halt}); - elem.pristine.validators.sort( (a, b) => b.priority - a.priority); - } else { - console.warn("The parameter elem must be a dom element"); - } - }; - - /*** - * An utility function that returns a 2-element array, first one is the element where error/success class is - * applied. 2nd one is the element where error message is displayed. 2nd element is created if doesn't exist and cached. - * @param field - * @returns {*} - * @private - */ - function _getErrorElements(field) { - if (field.errorElements){ - return field.errorElements; + return valid; + }; + + /*** + * Get errors of a specific field or the whole form + * @param input + * @returns {Array|*} + */ + self.getErrors = function (input) { + if (!input) { + let erroneousFields = []; + for (let i = 0; i < self.fields.length; i++) { + let field = self.fields[i]; + if (field.errors.length) { + erroneousFields.push({ input: field.input, errors: field.errors }); } - let errorClassElement = findAncestor(field.input, self.config.classTo); - let errorTextParent = null, errorTextElement = null; - if (self.config.classTo === self.config.errorTextParent){ - errorTextParent = errorClassElement; + } + return erroneousFields; + } + if (input.tagName && input.tagName.toLowerCase() === "select") { + return input.pristine.errors; + } + return input.length ? input[0].pristine.errors : input.pristine.errors; + }; + + /*** + * Validates a single field, all validator functions are called and error messages are generated + * when a validator fails + * @param field + * @returns {boolean} + * @private + */ + function _validateField(field) { + let errors = []; + let valid = true; + for (let i = 0; field.validators[i]; i++) { + let validator = field.validators[i]; + let params = field.params[validator.name] + ? field.params[validator.name] + : []; + params[0] = field.input.value; + if (!validator.fn.apply(field.input, params)) { + valid = false; + + if (typeof validator.msg === "function") { + errors.push(validator.msg(field.input.value, params)); + } else if (typeof validator.msg === "string") { + errors.push(tmpl.apply(validator.msg, params)); + } else if ( + validator.msg === Object(validator.msg) && + validator.msg[currentLocale] + ) { + // typeof generates unnecessary babel code + errors.push(tmpl.apply(validator.msg[currentLocale], params)); + } else if ( + field.messages[currentLocale] && + field.messages[currentLocale][validator.name] + ) { + errors.push( + tmpl.apply(field.messages[currentLocale][validator.name], params) + ); + } else if (lang[currentLocale] && lang[currentLocale][validator.name]) { + errors.push(tmpl.apply(lang[currentLocale][validator.name], params)); } else { - errorTextParent = errorClassElement.querySelector('.' + self.config.errorTextParent); - } - if (errorTextParent){ - errorTextElement = errorTextParent.querySelector('.' + PRISTINE_ERROR); - if (!errorTextElement){ - errorTextElement = document.createElement(self.config.errorTextTag); - errorTextElement.className = PRISTINE_ERROR + ' ' + self.config.errorTextClass; - errorTextParent.appendChild(errorTextElement); - errorTextElement.pristineDisplay = errorTextElement.style.display; - } + errors.push(tmpl.apply(lang[currentLocale].default, params)); } - return field.errorElements = [errorClassElement, errorTextElement] - } - - function _showError(field){ - let errorElements = _getErrorElements(field); - let errorClassElement = errorElements[0], errorTextElement = errorElements[1]; - if(errorClassElement){ - errorClassElement.classList.remove(self.config.successClass); - errorClassElement.classList.add(self.config.errorClass); - } - if (errorTextElement){ - errorTextElement.innerHTML = field.errors.join('
'); - errorTextElement.style.display = errorTextElement.pristineDisplay || ''; + if (validator.halt === true) { + break; } + } } - - /*** - * Adds error to a specific field - * @param input - * @param error - */ - self.addError = function(input, error) { - input = input.length ? input[0] : input; - input.pristine.errors.push(error); - _showError(input.pristine); - }; - - function _removeError(field){ - let errorElements = _getErrorElements(field); - let errorClassElement = errorElements[0], errorTextElement = errorElements[1]; - if (errorClassElement){ - // IE > 9 doesn't support multiple class removal - errorClassElement.classList.remove(self.config.errorClass); - errorClassElement.classList.remove(self.config.successClass); - } - if (errorTextElement){ - errorTextElement.innerHTML = ''; - errorTextElement.style.display = 'none'; - } - return errorElements; + field.errors = errors; + return valid; + } + + /*** + * Add a validator to a specific dom element in a form + * @param elem => The dom element where the validator is applied to + * @param fn => validator function + * @param msg => message to show when validation fails. Supports templating. ${0} for the input's value, ${1} and + * so on are for the attribute values + * @param priority => priority of the validator function, higher valued function gets called first. + * @param halt => whether validation should stop for this field after current validation function + */ + self.addValidator = function (elem, fn, msg, priority, halt) { + if (elem instanceof HTMLElement) { + elem.pristine.validators.push({ fn, msg, priority, halt }); + elem.pristine.validators.sort((a, b) => b.priority - a.priority); + } else { + console.warn("The parameter elem must be a dom element"); } - - function _showSuccess(field){ - let errorClassElement = _removeError(field)[0]; - errorClassElement && errorClassElement.classList.add(self.config.successClass); + }; + + /*** + * An utility function that returns a 2-element array, first one is the element where error/success class is + * applied. 2nd one is the element where error message is displayed. 2nd element is created if doesn't exist and cached. + * @param field + * @returns {*} + * @private + */ + function _getErrorElements(field) { + if (field.errorElements) { + return field.errorElements; } + let errorClassElement = findAncestor(field.input, self.config.classTo); + let errorTextParent = null, + errorTextElement = null; + if (self.config.classTo === self.config.errorTextParent) { + errorTextParent = errorClassElement; + } else { + errorTextParent = errorClassElement.querySelector( + "." + self.config.errorTextParent + ); + } + if (errorTextParent) { + errorTextElement = errorTextParent.querySelector("." + PRISTINE_ERROR); + if (!errorTextElement) { + errorTextElement = document.createElement(self.config.errorTextTag); + errorTextElement.className = + PRISTINE_ERROR + " " + self.config.errorTextClass; + errorTextParent.appendChild(errorTextElement); + errorTextElement.pristineDisplay = errorTextElement.style.display; + } + } + return (field.errorElements = [errorClassElement, errorTextElement]); + } - /*** - * Resets the errors - */ - self.reset = function () { - for(let i = 0; self.fields[i]; i++) { - self.fields[i].errorElements = null; - } - Array.from(self.form.querySelectorAll('.' + PRISTINE_ERROR)).map(function (elem) { - elem.parentNode.removeChild(elem); - }); - Array.from(self.form.querySelectorAll('.' + self.config.classTo)).map(function (elem) { - elem.classList.remove(self.config.successClass); - elem.classList.remove(self.config.errorClass); - }); - - }; - - /*** - * Resets the errors and deletes all pristine fields - */ - self.destroy = function(){ - self.reset(); - self.fields.forEach(function (field) { - delete field.input.pristine; - }); - self.fields = []; - }; + function _showError(field) { + let errorElements = _getErrorElements(field); + let errorClassElement = errorElements[0], + errorTextElement = errorElements[1]; - self.setGlobalConfig = function (config) { - defaultConfig = config; - }; + if (errorClassElement) { + errorClassElement.classList.remove(self.config.successClass); + errorClassElement.classList.add(self.config.errorClass); + } + if (errorTextElement) { + errorTextElement.innerHTML = field.errors.join("
"); + errorTextElement.style.display = errorTextElement.pristineDisplay || ""; + } + } + + /*** + * Adds error to a specific field + * @param input + * @param error + */ + self.addError = function (input, error) { + input = input.length ? input[0] : input; + input.pristine.errors.push(error); + _showError(input.pristine); + }; + + function _removeError(field) { + let errorElements = _getErrorElements(field); + let errorClassElement = errorElements[0], + errorTextElement = errorElements[1]; + if (errorClassElement) { + // IE > 9 doesn't support multiple class removal + errorClassElement.classList.remove(self.config.errorClass); + errorClassElement.classList.remove(self.config.successClass); + } + if (errorTextElement) { + errorTextElement.innerHTML = ""; + errorTextElement.style.display = "none"; + } + return errorElements; + } + + function _showSuccess(field) { + let errorClassElement = _removeError(field)[0]; + errorClassElement && + errorClassElement.classList.add(self.config.successClass); + } + + /*** + * Resets the errors + */ + self.reset = function () { + for (let i = 0; self.fields[i]; i++) { + self.fields[i].errorElements = null; + } + Array.from(self.form.querySelectorAll("." + PRISTINE_ERROR)).map(function ( + elem + ) { + elem.parentNode.removeChild(elem); + }); + Array.from(self.form.querySelectorAll("." + self.config.classTo)).map( + function (elem) { + elem.classList.remove(self.config.successClass); + elem.classList.remove(self.config.errorClass); + } + ); + }; + + /*** + * Resets the errors and deletes all pristine fields + */ + self.destroy = function () { + self.reset(); + self.fields.forEach(function (field) { + delete field.input.pristine; + }); + self.fields = []; + }; - return self; + self.setGlobalConfig = function (config) { + defaultConfig = config; + }; + return self; + return {}; } /*** @@ -331,18 +413,20 @@ export default function Pristine(form, config, live){ * @param priority => priority of the validator function, higher valued function gets called first. * @param halt => whether validation should stop for this field after current validation function */ -Pristine.addValidator = function(name, fn, msg, priority, halt){ - _(name, {fn, msg, priority, halt}); +Pristine.addValidator = function (name, fn, msg, priority, halt) { + _(name, { fn, msg, priority, halt }); }; -Pristine.addMessages = function(locale, messages){ - let langObj = lang.hasOwnProperty(locale) ? lang[locale] : lang[locale] = {}; +Pristine.addMessages = function (locale, messages) { + let langObj = lang.hasOwnProperty(locale) + ? lang[locale] + : (lang[locale] = {}); - Object.keys(messages).forEach(function(key, index) { - langObj[key] = messages[key]; - }); -} + Object.keys(messages).forEach(function (key, index) { + langObj[key] = messages[key]; + }); +}; -Pristine.setLocale = function (locale){ - currentLocale = locale; -} +Pristine.setLocale = function (locale) { + currentLocale = locale; +};