pristinejs
Advanced tools
Comparing version
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global.Pristine = factory()); | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Pristine = factory()); | ||
}(this, (function () { 'use strict'; | ||
var lang = { | ||
required: "This field is required", | ||
email: "This field requires a valid e-mail address", | ||
number: "This field requires a number", | ||
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: "Input must match the pattern ${1}" | ||
}; | ||
var lang = { | ||
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" | ||
}; | ||
function findAncestor(el, cls) { | ||
while ((el = el.parentElement) && !el.classList.contains(cls)) {} | ||
return el; | ||
} | ||
function findAncestor(el, cls) { | ||
while ((el = el.parentElement) && !el.classList.contains(cls)) {} | ||
return el; | ||
} | ||
function tmpl(o) { | ||
var _arguments = arguments; | ||
function tmpl(o) { | ||
var _arguments = arguments; | ||
return this.replace(/\${([^{}]*)}/g, function (a, b) { | ||
return _arguments[b]; | ||
}); | ||
} | ||
return this.replace(/\${([^{}]*)}/g, function (a, b) { | ||
return _arguments[b]; | ||
}); | ||
} | ||
function groupedElemCount(input) { | ||
return input.pristine.self.form.querySelectorAll('input[name="' + input.getAttribute('name') + '"]:checked').length; | ||
} | ||
function groupedElemCount(input) { | ||
return input.pristine.self.form.querySelectorAll('input[name="' + input.getAttribute('name') + '"]:checked').length; | ||
} | ||
function mergeConfig(obj1, obj2) { | ||
for (var attr in obj2) { | ||
if (!(attr in obj1)) { | ||
obj1[attr] = obj2[attr]; | ||
function mergeConfig(obj1, obj2) { | ||
for (var attr in obj2) { | ||
if (!(attr in obj1)) { | ||
obj1[attr] = obj2[attr]; | ||
} | ||
} | ||
return obj1; | ||
} | ||
return obj1; | ||
} | ||
var defaultConfig = { | ||
classTo: 'form-group', | ||
errorClass: 'has-danger', | ||
successClass: 'has-success', | ||
errorTextParent: 'form-group', | ||
errorTextTag: 'div', | ||
errorTextClass: 'text-help' | ||
}; | ||
function isFunction(obj) { | ||
return !!(obj && obj.constructor && obj.call && obj.apply); | ||
} | ||
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 defaultConfig = { | ||
classTo: 'form-group', | ||
errorClass: 'has-danger', | ||
successClass: 'has-success', | ||
errorTextParent: 'form-group', | ||
errorTextTag: 'div', | ||
errorTextClass: 'text-help' | ||
}; | ||
var validators = {}; | ||
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 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 _ = function _(name, validator) { | ||
validator.name = name; | ||
if (!validator.msg) validator.msg = lang[name]; | ||
if (validator.priority === undefined) validator.priority = 1; | ||
validators[name] = validator; | ||
}; | ||
var validators = {}; | ||
_('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 || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.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); | ||
} }); | ||
var _ = function _(name, validator) { | ||
validator.name = name; | ||
if (!validator.msg) validator.msg = lang[name]; | ||
if (validator.priority === undefined) validator.priority = 1; | ||
validators[name] = validator; | ||
}; | ||
function Pristine(form, config, live) { | ||
_('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); | ||
} }); | ||
var self = this; | ||
function Pristine(form, config, live) { | ||
init(form, config, live); | ||
var self = this; | ||
function init(form, config, live) { | ||
init(form, config, live); | ||
form.setAttribute("novalidate", "true"); | ||
function init(form, config, live) { | ||
self.form = form; | ||
self.config = mergeConfig(config || {}, defaultConfig); | ||
self.live = !(live === false); | ||
self.fields = Array.from(form.querySelectorAll(SELECTOR)).map(function (input) { | ||
form.setAttribute("novalidate", "true"); | ||
var fns = []; | ||
var params = {}; | ||
var messages = {}; | ||
self.form = form; | ||
self.config = mergeConfig(config || {}, defaultConfig); | ||
self.live = !(live === false); | ||
self.fields = Array.from(form.querySelectorAll(SELECTOR)).map(function (input) { | ||
[].forEach.call(input.attributes, function (attr) { | ||
if (/^data-pristine-/.test(attr.name)) { | ||
var name = attr.name.substr(14); | ||
if (name.endsWith('-message')) { | ||
messages[name.slice(0, name.length - 8)] = attr.value; | ||
return; | ||
var fns = []; | ||
var params = {}; | ||
var messages = {}; | ||
[].forEach.call(input.attributes, function (attr) { | ||
if (/^data-pristine-/.test(attr.name)) { | ||
var name = attr.name.substr(14); | ||
if (name.endsWith('-message')) { | ||
messages[name.slice(0, name.length - 8)] = 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); | ||
} | ||
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; | ||
}); | ||
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); | ||
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)); | ||
} | ||
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 = value.split(','); | ||
valueParams.unshift(null); // placeholder for input's value | ||
params[name] = valueParams; | ||
function _addValidatorToField(fns, params, name, value) { | ||
var validator = validators[name]; | ||
if (validator) { | ||
fns.push(validator); | ||
if (value) { | ||
var valueParams = 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 in fields) { | ||
var field = fields[i]; | ||
if (_validateField(field)) { | ||
!silent && _showSuccess(field); | ||
} else { | ||
valid = false; | ||
!silent && _showError(field); | ||
for (var i in fields) { | ||
var field = fields[i]; | ||
if (_validateField(field)) { | ||
!silent && _showSuccess(field); | ||
} else { | ||
valid = false; | ||
!silent && _showError(field); | ||
} | ||
} | ||
} | ||
return valid; | ||
}; | ||
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 }); | ||
/*** | ||
* 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; | ||
} | ||
return erroneousFields; | ||
} | ||
return input.length ? input[0].pristine.errors : input.pristine.errors; | ||
}; | ||
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 in field.validators) { | ||
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; | ||
var error = field.messages[validator.name] || validator.msg; | ||
errors.push(tmpl.apply(error, params)); | ||
if (validator.halt === true) { | ||
break; | ||
/*** | ||
* 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 in field.validators) { | ||
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 (isFunction(validator.msg)) { | ||
errors.push(validator.msg(field.input.value, params)); | ||
} else { | ||
var error = field.messages[validator.name] || validator.msg; | ||
errors.push(tmpl.apply(error, params)); | ||
} | ||
if (validator.halt === true) { | ||
break; | ||
} | ||
} | ||
} | ||
field.errors = errors; | ||
return valid; | ||
} | ||
field.errors = errors; | ||
return valid; | ||
} | ||
/*** | ||
* | ||
* @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"); | ||
} | ||
}; | ||
/*** | ||
* | ||
* @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.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; | ||
/*** | ||
* 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]; | ||
} | ||
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 (errorClassElement) { | ||
errorClassElement.classList.remove(self.config.successClass); | ||
errorClassElement.classList.add(self.config.errorClass); | ||
} | ||
if (errorTextElement) { | ||
errorTextElement.innerHTML = field.errors.join('<br/>'); | ||
errorTextElement.style.display = errorTextElement.pristineDisplay || ''; | ||
} | ||
} | ||
if (errorTextElement) { | ||
errorTextElement.innerHTML = field.errors.join('<br/>'); | ||
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); | ||
}; | ||
/*** | ||
* 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); | ||
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 = ''; | ||
errorTextElement.style.display = 'none'; | ||
function _showSuccess(field) { | ||
var errorClassElement = _removeError(field)[0]; | ||
errorClassElement && errorClassElement.classList.add(self.config.successClass); | ||
} | ||
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 in self.fields) { | ||
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; | ||
}; | ||
return self; | ||
} | ||
/*** | ||
* Resets the errors | ||
* | ||
* @param name => Name of the global validator | ||
* @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.reset = function () { | ||
for (var i in self.fields) { | ||
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); | ||
}); | ||
Pristine.addValidator = function (name, fn, msg, priority, halt) { | ||
_(name, { fn: fn, msg: msg, priority: priority, halt: halt }); | ||
}; | ||
/*** | ||
* 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 Pristine; | ||
self.setGlobalConfig = function (config) { | ||
defaultConfig = config; | ||
}; | ||
return self; | ||
} | ||
/*** | ||
* | ||
* @param name => Name of the global validator | ||
* @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 | ||
*/ | ||
Pristine.addValidator = function (name, fn, msg, priority, halt) { | ||
_(name, { fn: fn, msg: msg, priority: priority, halt: halt }); | ||
}; | ||
return Pristine; | ||
}))); |
@@ -1,1 +0,1 @@ | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):e.Pristine=r()}(this,function(){"use strict";function e(e){var r=arguments;return this.replace(/\${([^{}]*)}/g,function(e,t){return r[t]})}function r(e){return e.pristine.self.form.querySelectorAll('input[name="'+e.getAttribute("name")+'"]:checked').length}function t(r,t,n){function u(e,r,t,n){var i=l[t];if(i&&(e.push(i),n)){var s=n.split(",");s.unshift(null),r[t]=s}}function f(e){if(e.errorElements)return e.errorElements;var r=function(e,r){for(;(e=e.parentElement)&&!e.classList.contains(r););return e}(e.input,m.config.classTo),t=null,n=null;return(t=m.config.classTo===m.config.errorTextParent?r:r.querySelector(m.errorTextParent))&&((n=t.querySelector("."+s))||((n=document.createElement(m.config.errorTextTag)).className=s+" "+m.config.errorTextClass,t.appendChild(n),n.pristineDisplay=n.style.display)),e.errorElements=[r,n]}function c(e){var r=f(e),t=r[0],n=r[1];t&&(t.classList.remove(m.config.successClass),t.classList.add(m.config.errorClass)),n&&(n.innerHTML=e.errors.join("<br/>"),n.style.display=n.pristineDisplay||"")}function p(e){var r=function(e){var r=f(e),t=r[0],n=r[1];return t&&(t.classList.remove(m.config.errorClass),t.classList.remove(m.config.successClass)),n&&(n.innerHTML="",n.style.display="none"),r}(e)[0];r&&r.classList.add(m.config.successClass)}var m=this;return function(e,r,t){e.setAttribute("novalidate","true"),m.form=e,m.config=function(e,r){for(var t in r)t in e||(e[t]=r[t]);return e}(r||{},i),m.live=!(!1===t),m.fields=Array.from(e.querySelectorAll(a)).map(function(e){var r=[],t={},n={};return[].forEach.call(e.attributes,function(e){if(/^data-pristine-/.test(e.name)){var i=e.name.substr(14);if(i.endsWith("-message"))return void(n[i.slice(0,i.length-8)]=e.value);"type"===i&&(i=e.value),u(r,t,i,e.value)}else~o.indexOf(e.name)?u(r,t,e.name,e.value):"type"===e.name&&u(r,t,e.value)}),r.sort(function(e,r){return r.priority-e.priority}),m.live&&e.addEventListener(~["radio","checkbox"].indexOf(e.getAttribute("type"))?"change":"input",function(e){m.validate(e.target)}.bind(m)),e.pristine={input:e,validators:r,params:t,messages:n,self:m}}.bind(m))}(r,t,n),m.validate=function(r,t){t=r&&!0===t||!0===r;var n=m.fields;!0!==r&&!1!==r&&(r instanceof HTMLElement?n=[r.pristine]:(r instanceof NodeList||r instanceof(window.$||Array)||r instanceof Array)&&(n=Array.from(r).map(function(e){return e.pristine})));var i=!0;for(var s in n){var a=n[s];!function(r){var t=[],n=!0;for(var i in r.validators){var s=r.validators[i],a=r.params[s.name]?r.params[s.name]:[];if(a[0]=r.input.value,!s.fn.apply(r.input,a)){n=!1;var o=r.messages[s.name]||s.msg;if(t.push(e.apply(o,a)),!0===s.halt)break}}return r.errors=t,n}(a)?(i=!1,!t&&c(a)):!t&&p(a)}return i},m.getErrors=function(e){if(!e){for(var r=[],t=0;t<m.fields.length;t++){var n=m.fields[t];n.errors.length&&r.push({input:n.input,errors:n.errors})}return r}return e.length?e[0].pristine.errors:e.pristine.errors},m.addValidator=function(e,r,t,n,i){e instanceof HTMLElement?(e.pristine.validators.push({fn:r,msg:t,priority:n,halt:i}),e.pristine.validators.sort(function(e,r){return r.priority-e.priority})):console.warn("The parameter elem must be a dom element")},m.addError=function(e,r){(e=e.length?e[0]:e).pristine.errors.push(r),c(e.pristine)},m.reset=function(){for(var e in m.fields)m.fields[e].errorElements=null;Array.from(m.form.querySelectorAll("."+s)).map(function(e){e.parentNode.removeChild(e)}),Array.from(m.form.querySelectorAll("."+m.config.classTo)).map(function(e){e.classList.remove(m.config.successClass),e.classList.remove(m.config.errorClass)})},m.destroy=function(){m.reset(),m.fields.forEach(function(e){delete e.input.pristine}),m.fields=[]},m.setGlobalConfig=function(e){i=e},m}var n={required:"This field is required",email:"This field requires a valid e-mail address",number:"This field requires a number",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:"Input must match the pattern ${1}"},i={classTo:"form-group",errorClass:"has-danger",successClass:"has-success",errorTextParent:"form-group",errorTextTag:"div",errorTextClass:"text-help"},s="pristine-error",a="input:not([type^=hidden]):not([type^=submit]), select, textarea",o=["required","min","max","minlength","maxlength","pattern"],l={},u=function(e,r){r.name=e,r.msg||(r.msg=n[e]),void 0===r.priority&&(r.priority=1),l[e]=r};return u("text",{fn:function(e){return!0},priority:0}),u("required",{fn:function(e){return"radio"===this.type||"checkbox"===this.type?r(this):void 0!==e&&""!==e},priority:99,halt:!0}),u("email",{fn:function(e){return!e||/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}}),u("number",{fn:function(e){return!e||!isNaN(parseFloat(e))},priority:2}),u("integer",{fn:function(e){return e&&/^\d+$/.test(e)}}),u("minlength",{fn:function(e,r){return!e||e.length>=parseInt(r)}}),u("maxlength",{fn:function(e,r){return!e||e.length<=parseInt(r)}}),u("min",{fn:function(e,t){return!e||("checkbox"===this.type?r(this)>=parseInt(t):parseFloat(e)>=parseFloat(t))}}),u("max",{fn:function(e,t){return!e||("checkbox"===this.type?r(this)<=parseInt(t):parseFloat(e)<=parseFloat(t))}}),u("pattern",{fn:function(e,r){var t=r.match(new RegExp("^/(.*?)/([gimy]*)$"));return!e||new RegExp(t[1],t[2]).test(e)}}),t.addValidator=function(e,r,t,n,i){u(e,{fn:r,msg:t,priority:n,halt:i})},t}); | ||
!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={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"};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={},o=function(r,t){t.name=r,t.msg||(t.msg=e[r]),void 0===t.priority&&(t.priority=1),a[r]=t};function l(e,t,s){var o=this;function l(e,r,t,n){var i=a[t];if(i&&(e.push(i),n)){var s=n.split(",");s.unshift(null),r[t]=s}}function u(e){var t,n=[],i=!0;for(var s in e.validators){var a=e.validators[s],o=e.params[a.name]?e.params[a.name]:[];if(o[0]=e.input.value,!a.fn.apply(e.input,o)){if(i=!1,(t=a.msg)&&t.constructor&&t.call&&t.apply)n.push(a.msg(e.input.value,o));else{var l=e.messages[a.name]||a.msg;n.push(r.apply(l,o))}if(!0===a.halt)break}}return e.errors=n,i}function f(e){if(e.errorElements)return e.errorElements;var r=function(e,r){for(;(e=e.parentElement)&&!e.classList.contains(r););return e}(e.input,o.config.classTo),t=null,n=null;return(t=o.config.classTo===o.config.errorTextParent?r:r.querySelector("."+o.config.errorTextParent))&&((n=t.querySelector(".pristine-error"))||((n=document.createElement(o.config.errorTextTag)).className="pristine-error "+o.config.errorTextClass,t.appendChild(n),n.pristineDisplay=n.style.display)),e.errorElements=[r,n]}function c(e){var r=f(e),t=r[0],n=r[1];t&&(t.classList.remove(o.config.successClass),t.classList.add(o.config.errorClass)),n&&(n.innerHTML=e.errors.join("<br/>"),n.style.display=n.pristineDisplay||"")}function p(e){var r=function(e){var r=f(e),t=r[0],n=r[1];return t&&(t.classList.remove(o.config.errorClass),t.classList.remove(o.config.successClass)),n&&(n.innerHTML="",n.style.display="none"),r}(e)[0];r&&r.classList.add(o.config.successClass)}return function(e,r,t){e.setAttribute("novalidate","true"),o.form=e,o.config=function(e,r){for(var t in r)t in e||(e[t]=r[t]);return e}(r||{},n),o.live=!(!1===t),o.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);if(s.endsWith("-message"))return void(n[s.slice(0,s.length-8)]=e.value);"type"===s&&(s=e.value),l(r,t,s,e.value)}else~i.indexOf(e.name)?l(r,t,e.name,e.value):"type"===e.name&&l(r,t,e.value)})),r.sort((function(e,r){return r.priority-e.priority})),o.live&&e.addEventListener(~["radio","checkbox"].indexOf(e.getAttribute("type"))?"change":"input",function(e){o.validate(e.target)}.bind(o)),e.pristine={input:e,validators:r,params:t,messages:n,self:o}}.bind(o))}(e,t,s),o.validate=function(e,r){r=e&&!0===r||!0===e;var t=o.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}))));var n=!0;for(var i in t){var s=t[i];u(s)?!r&&p(s):(n=!1,!r&&c(s))}return n},o.getErrors=function(e){if(!e){for(var r=[],t=0;t<o.fields.length;t++){var n=o.fields[t];n.errors.length&&r.push({input:n.input,errors:n.errors})}return r}return e.tagName&&"select"===e.tagName.toLowerCase()?e.pristine.errors:e.length?e[0].pristine.errors:e.pristine.errors},o.addValidator=function(e,r,t,n,i){e instanceof HTMLElement?(e.pristine.validators.push({fn:r,msg:t,priority:n,halt:i}),e.pristine.validators.sort((function(e,r){return r.priority-e.priority}))):console.warn("The parameter elem must be a dom element")},o.addError=function(e,r){(e=e.length?e[0]:e).pristine.errors.push(r),c(e.pristine)},o.reset=function(){for(var e in o.fields)o.fields[e].errorElements=null;Array.from(o.form.querySelectorAll(".pristine-error")).map((function(e){e.parentNode.removeChild(e)})),Array.from(o.form.querySelectorAll("."+o.config.classTo)).map((function(e){e.classList.remove(o.config.successClass),e.classList.remove(o.config.errorClass)}))},o.destroy=function(){o.reset(),o.fields.forEach((function(e){delete e.input.pristine})),o.fields=[]},o.setGlobalConfig=function(e){n=e},o}return o("text",{fn:function(e){return!0},priority:0}),o("required",{fn:function(e){return"radio"===this.type||"checkbox"===this.type?t(this):void 0!==e&&""!==e},priority:99,halt:!0}),o("email",{fn:function(e){return!e||s.test(e)}}),o("number",{fn:function(e){return!e||!isNaN(parseFloat(e))},priority:2}),o("integer",{fn:function(e){return!e||/^\d+$/.test(e)}}),o("minlength",{fn:function(e,r){return!e||e.length>=parseInt(r)}}),o("maxlength",{fn:function(e,r){return!e||e.length<=parseInt(r)}}),o("min",{fn:function(e,r){return!e||("checkbox"===this.type?t(this)>=parseInt(r):parseFloat(e)>=parseFloat(r))}}),o("max",{fn:function(e,r){return!e||("checkbox"===this.type?t(this)<=parseInt(r):parseFloat(e)<=parseFloat(r))}}),o("pattern",{fn:function(e,r){var t=r.match(new RegExp("^/(.*?)/([gimy]*)$"));return!e||new RegExp(t[1],t[2]).test(e)}}),l.addValidator=function(e,r,t,n,i){o(e,{fn:r,msg:t,priority:n,halt:i})},l})); |
{ | ||
"name": "pristinejs", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "A tiny vanilla javascript form validation library", | ||
"main": "dist/pristine.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"test": "karma start karma.conf.js --single-run", | ||
"build": "rollup -c" | ||
@@ -31,6 +31,15 @@ }, | ||
"babel-preset-env": "^1.6.1", | ||
"jasmine": "^3.6.1", | ||
"jasmine-core": "^3.6.0", | ||
"karma": "^5.1.1", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-jasmine": "^4.0.1", | ||
"karma-jasmine-html-reporter": "^1.5.4", | ||
"karma-rollup-preprocessor": "^7.0.5", | ||
"karma-spec-reporter": "0.0.32", | ||
"rollup": "^2.26.2", | ||
"rollup-plugin-babel": "^3.0.2", | ||
"rollup-plugin-uglify": "^2.0.1" | ||
"rollup-plugin-terser": "^7.0.0" | ||
}, | ||
"dependencies": {} | ||
} |
@@ -137,3 +137,3 @@ # Pristine - Vanilla javascript form validation library | ||
| `max` | `max="100"` or `data-pristine-max="100"` | | | ||
| `pattern` | `pattern="/[a-z]+$/i"` or `data-pristine-pattern="/[a-z]+$/i"` || | ||
| `pattern` | `pattern="/[a-z]+$/i"` or `data-pristine-pattern="/[a-z]+$/i"`, `\` must be escaped (replace with `\\`) || | ||
@@ -173,3 +173,3 @@ | ||
| `fn`| - | <center>✔</center> | The function that validates the field. Value of the input field gets passed as the first parameter, and the attribute value (split using comma) as the subsequent parameters. For example, for `<input data-pristine-my-validator="10,20,dhaka" value="myValue"/>`, validator function get called like `fn("myValue", 10, 20, "dhaka")`. Inside the function `this` refers to the input element| | ||
| `message`| - | <center>✔</center> | The message to show when the validation fails. It supports simple templating. `${0}` for the input's value, `${1}` and so on are for the attribute values. For the above example, `${0}` will get replaced by `myValue`, `${1}` by `10`, `${2}` by `20`, `${3}` by `dhaka`.| | ||
| `message`| - | <center>✔</center> | The message to show when the validation fails. It supports simple templating. `${0}` for the input's value, `${1}` and so on are for the attribute values. For the above example, `${0}` will get replaced by `myValue`, `${1}` by `10`, `${2}` by `20`, `${3}` by `dhaka`. It can also be a function which should return the error string. The values and inputs are available as function arguments| | ||
| `priority`| 1 | <center>✕</center> | Priority of the validator function. The higher the value, the earlier it gets called when there are multiple validators on one field. | | ||
@@ -176,0 +176,0 @@ | `halt`| `false` | <center>✕</center> | Whether to halt validation on the current field after this validation. When `true` after validating the current validator, rest of the validators are ignored on the current field.| |
import babel from 'rollup-plugin-babel'; | ||
import uglify from 'rollup-plugin-uglify'; | ||
import { terser } from "rollup-plugin-terser"; | ||
@@ -29,6 +29,6 @@ const source = { | ||
}), | ||
uglify() | ||
terser() | ||
] | ||
}; | ||
export default [source, minified]; | ||
export default [source, minified]; |
@@ -5,2 +5,3 @@ export const lang = { | ||
number: "This field requires a number", | ||
integer: "This field requires an integer value", | ||
url: "This field requires a valid website URL", | ||
@@ -12,3 +13,3 @@ tel: "This field requires a valid telephone number", | ||
max: "Maximum value for this field is ${1}", | ||
pattern: "Input must match the pattern ${1}", | ||
pattern: "Please match the requested format", | ||
}; |
import { lang } from './lang'; | ||
import { tmpl, findAncestor, groupedElemCount, mergeConfig } from './utils'; | ||
import { tmpl, findAncestor, groupedElemCount, mergeConfig, isFunction } from './utils'; | ||
@@ -16,2 +16,3 @@ let defaultConfig = { | ||
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,}))$/ | ||
@@ -31,5 +32,5 @@ const validators = {}; | ||
_('required', { fn: function(val){ return (this.type === 'radio' || this.type === 'checkbox') ? groupedElemCount(this) : val !== undefined && val !== ''}, priority: 99, halt: true}); | ||
_('email', { fn: (val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)}); | ||
_('email', { fn: (val) => !val || EMAIL_REGEX.test(val)}); | ||
_('number', { fn: (val) => !val || !isNaN(parseFloat(val)), priority: 2 }); | ||
_('integer', { fn: (val) => val && /^\d+$/.test(val) }); | ||
_('integer', { fn: (val) => !val || /^\d+$/.test(val) }); | ||
_('minlength', { fn: (val, length) => !val || val.length >= parseInt(length) }); | ||
@@ -147,2 +148,5 @@ _('maxlength', { fn: (val, length) => !val || val.length <= parseInt(length) }); | ||
} | ||
if (input.tagName && input.tagName.toLowerCase() === "select"){ | ||
return input.pristine.errors; | ||
} | ||
return input.length ? input[0].pristine.errors : input.pristine.errors; | ||
@@ -167,4 +171,10 @@ }; | ||
valid = false; | ||
let error = field.messages[validator.name] || validator.msg; | ||
errors.push(tmpl.apply(error, params)); | ||
if (isFunction(validator.msg)) { | ||
errors.push(validator.msg(field.input.value, params)) | ||
} else { | ||
let error = field.messages[validator.name] || validator.msg; | ||
errors.push(tmpl.apply(error, params)); | ||
} | ||
if (validator.halt === true){ | ||
@@ -213,3 +223,3 @@ break; | ||
} else { | ||
errorTextParent = errorClassElement.querySelector(self.errorTextParent); | ||
errorTextParent = errorClassElement.querySelector('.' + self.config.errorTextParent); | ||
} | ||
@@ -216,0 +226,0 @@ if (errorTextParent){ |
@@ -22,1 +22,5 @@ export function findAncestor (el, cls) { | ||
} | ||
export function isFunction(obj) { | ||
return !!(obj && obj.constructor && obj.call && obj.apply); | ||
} |
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
55805
31.63%13
44.44%998
42.57%1
-50%14
180%