Comparing version 0.1.4 to 0.2.0
@@ -6,6 +6,7 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vivalid = f()}})(function(){var define,module,exports;return (require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
validInputTagNames: ['input', 'textarea', 'select'], | ||
keyStrokedInputTypes: ['text', 'email', 'password', 'search'], | ||
keyStrokedInputTypes: ['text', 'email', 'password', 'search', 'hidden'], | ||
ERROR: { | ||
mandatorySuccessFailure: 'passing callbacks for onValidationSuccess and onValidationFailure is mandatory' | ||
} | ||
mandatorySuccessFailure: 'passing callbacks for onValidationSuccess and onValidationFailure is mandatory', | ||
errorInCallback: 'callback failed: ' | ||
} | ||
}; | ||
@@ -16,3 +17,3 @@ | ||
function toArray(arrayLike){ | ||
function toArray(arrayLike) { | ||
return Array.prototype.slice.call(arrayLike); | ||
@@ -22,3 +23,3 @@ } | ||
function ready(fn) { | ||
if (document.readyState != 'loading'){ | ||
if (document.readyState != 'loading') { | ||
fn(); | ||
@@ -30,10 +31,10 @@ } else { | ||
function getElementsByTagNames(tagsArray,obj) { | ||
function getElementsByTagNames(tagsArray, obj) { | ||
if (!obj) obj = document; | ||
var results= []; | ||
var i=0; | ||
for (;i<tagsArray.length;i++) { | ||
var results = []; | ||
var i = 0; | ||
for (; i < tagsArray.length; i++) { | ||
var tags = obj.getElementsByTagName(tagsArray[i]); | ||
var j=0; | ||
for (;j<tags.length;j++) { | ||
var j = 0; | ||
for (; j < tags.length; j++) { | ||
results.push(tags[j]); | ||
@@ -48,5 +49,5 @@ } | ||
// Get closest match | ||
for ( ; elem && elem !== document; elem = elem.parentNode ) { | ||
for (; elem && elem !== document; elem = elem.parentNode) { | ||
if (hasDataSet(elem,attr)) { | ||
if (hasDataSet(elem, attr)) { | ||
return elem; | ||
@@ -61,5 +62,5 @@ } | ||
return toArray(elem.getElementsByTagName('*')) | ||
.filter(function(el){ | ||
if (hasDataSet(el,attr)) return true; | ||
}); | ||
.filter(function(el) { | ||
if (hasDataSet(el, attr)) return true; | ||
}); | ||
} | ||
@@ -75,3 +76,3 @@ | ||
function getDataSet_unsupported(node, attr) { | ||
if (node.nodeType !== Node.ELEMENT_NODE ) return false; | ||
if (node.nodeType !== Node.ELEMENT_NODE) return false; | ||
@@ -82,3 +83,3 @@ return node.getAttribute('data-' + toDashed(attr)); | ||
function getDataSet(node, attr) { | ||
if (node.nodeType !== Node.ELEMENT_NODE ) return false; | ||
if (node.nodeType !== Node.ELEMENT_NODE) return false; | ||
@@ -88,3 +89,3 @@ return node.dataset[attr]; | ||
function hasDataSet(node, attr){ | ||
function hasDataSet(node, attr) { | ||
return (node.nodeType === Node.ELEMENT_NODE && node.hasAttribute('data-' + toDashed(attr))); | ||
@@ -99,4 +100,26 @@ } | ||
// from http://jaketrent.com/post/addremove-classes-raw-javascript/ | ||
// used instead of classList because of lacking browser support | ||
function hasClass(el, className) { | ||
if (el.classList) | ||
return el.classList.contains(className) | ||
else | ||
return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')) | ||
} | ||
function addClass(el, className) { | ||
if (el.classList) | ||
el.classList.add(className) | ||
else if (!hasClass(el, className)) el.className += " " + className | ||
} | ||
function removeClass(el, className) { | ||
if (el.classList) | ||
el.classList.remove(className) | ||
else if (hasClass(el, className)) { | ||
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)') | ||
el.className = el.className.replace(reg, ' ') | ||
} | ||
} | ||
module.exports = { | ||
@@ -109,7 +132,6 @@ getDataSet: isDataSetSupport ? getDataSet : getDataSet_unsupported, | ||
ready: ready, | ||
toArray: toArray | ||
toArray: toArray, | ||
addClass: addClass, | ||
removeClass: removeClass | ||
}; | ||
},{}],3:[function(require,module,exports){ | ||
@@ -124,2 +146,3 @@ var Input = require('./input'); | ||
var callbacks = {}; // custom user defined callbacks | ||
var groupNameToVivalidGroup = {}; | ||
@@ -134,3 +157,3 @@ /** | ||
*/ | ||
function addCallback(name,fn) { | ||
function addCallback(name, fn) { | ||
if (typeof fn !== 'function') throw 'error while trying to add a custom callback: argument must be a function'; | ||
@@ -148,13 +171,14 @@ if (callbacks[name]) throw 'error while trying to add a custom callback: ' + name + ' already exists'; | ||
*/ | ||
function initGroup(groupElem){ | ||
function initGroup(groupElem) { | ||
$$.ready(registerGroupFromDataAttribtues); | ||
function registerGroupFromDataAttribtues(){ | ||
function registerGroupFromDataAttribtues() { | ||
inputElems = $$.getElementsByTagNames(validInputTagNames,groupElem) | ||
.filter(function(el){ | ||
return $$.hasDataSet(el,'vivalidTuples'); | ||
}); | ||
inputElems = $$.getElementsByTagNames(validInputTagNames, groupElem) | ||
.filter(function(el) { | ||
return $$.hasDataSet(el, 'vivalidTuples'); | ||
}); | ||
createGroupFromDataAttribtues(groupElem,inputElems); | ||
var vivalidGroup = createGroupFromDataAttribtues(groupElem, inputElems); | ||
addToGroupNameDictionairy(groupElem, vivalidGroup); | ||
@@ -174,3 +198,3 @@ } | ||
function registerAllFromDataAttribtues(){ | ||
function registerAllFromDataAttribtues() { | ||
@@ -184,17 +208,17 @@ var _nextGroupId = 1; | ||
$$.getElementsByTagNames(validInputTagNames) | ||
.filter(function(el){ | ||
return $$.hasDataSet(el,'vivalidTuples'); | ||
}) | ||
.forEach(function(el){ | ||
addGroupInputs($$.getClosestParentByAttribute(el,'vivalidGroup'),el); | ||
}); | ||
.filter(function(el) { | ||
return $$.hasDataSet(el, 'vivalidTuples'); | ||
}) | ||
.forEach(function(el) { | ||
addGroupInputs($$.getClosestParentByAttribute(el, 'vivalidGroup'), el); | ||
}); | ||
for (var groupId in groupIdToInputs){ | ||
createGroupFromDataAttribtues(groupIdToGroup[groupId], groupIdToInputs[groupId]); | ||
for (var groupId in groupIdToInputs) { | ||
var vivalidGroup = createGroupFromDataAttribtues(groupIdToGroup[groupId], groupIdToInputs[groupId]); | ||
addToGroupNameDictionairy(groupIdToGroup[groupId], vivalidGroup); | ||
} | ||
function addGroupInputs(group, input) { | ||
function addGroupInputs(group,input){ | ||
if (!group){ | ||
if (!group) { | ||
throw 'an input validation is missing a group, input id: ' + input.id; | ||
@@ -205,3 +229,3 @@ } | ||
if(!groupIdToInputs[group._groupId]){ | ||
if (!groupIdToInputs[group._groupId]) { | ||
groupIdToInputs[group._groupId] = []; | ||
@@ -219,55 +243,94 @@ groupIdToGroup[group._groupId] = group; | ||
/** | ||
* Allow's an application to reset the validations state and event listeners of a group | ||
* @memberof! vivalid.htmlInterface | ||
* @function | ||
* @example vivalid.htmlInterface.resetGroup('contactGroup'); | ||
* @param {string} groupName | ||
*/ | ||
function resetGroup(groupName) { | ||
var vivalidGroup = groupNameToVivalidGroup[groupName]; | ||
if (vivalidGroup) { | ||
vivalidGroup.reset(); | ||
} else { | ||
console.log('could not find group named ' + groupName); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function createGroupFromDataAttribtues(groupElem,inputElems){ | ||
function addToGroupNameDictionairy(groupElem, vivalidGroup) { | ||
groupName = $$.getDataSet(groupElem, 'vivalidGroup'); | ||
groupNameToVivalidGroup[groupName] = vivalidGroup; | ||
} | ||
function createGroupFromDataAttribtues(groupElem, inputElems) { | ||
var inputs = inputElems.map(vivalidInputFromElem); | ||
var onValidation = [null,null]; | ||
var pendingUi = [null,null]; | ||
var onValidation = [null, null]; | ||
var pendingUi = [null, null]; | ||
var groupStatesChanged; | ||
var groupPendingChanged; | ||
var onBeforeValidation; | ||
var onAfterValidation; | ||
if ($$.hasDataSet(groupElem,'vivalidOnValidation')){ | ||
onValidation = JSON.parse($$.getDataSet(groupElem,'vivalidOnValidation')); | ||
if ($$.hasDataSet(groupElem, 'vivalidOnValidation')) { | ||
onValidation = JSON.parse($$.getDataSet(groupElem, 'vivalidOnValidation')); | ||
if (!Array.isArray(onValidation) || onValidation.length !== 2) throw 'data-vivalid-on-validation value should be an array of size 2'; | ||
} | ||
if ($$.hasDataSet(groupElem,'vivalidPendingUi')){ | ||
pendingUi = JSON.parse($$.getDataSet(groupElem,'vivalidPendingUi')); | ||
if ($$.hasDataSet(groupElem, 'vivalidPendingUi')) { | ||
pendingUi = JSON.parse($$.getDataSet(groupElem, 'vivalidPendingUi')); | ||
if (!Array.isArray(pendingUi) || pendingUi.length !== 2) throw 'data-vivalid-pending-ui value should be an array of size 2'; | ||
} | ||
if ($$.hasDataSet(groupElem,'vivalidStatesChanged')){ | ||
groupStatesChanged = $$.getDataSet(groupElem,'vivalidStatesChanged'); | ||
if ($$.hasDataSet(groupElem, 'vivalidStatesChanged')) { | ||
groupStatesChanged = $$.getDataSet(groupElem, 'vivalidStatesChanged'); | ||
} | ||
if ($$.hasDataSet(groupElem,'vivalidPendingChanged')){ | ||
groupPendingChanged = $$.getDataSet(groupElem,'vivalidPendingChanged'); | ||
if ($$.hasDataSet(groupElem, 'vivalidPendingChanged')) { | ||
groupPendingChanged = $$.getDataSet(groupElem, 'vivalidPendingChanged'); | ||
} | ||
new InputGroup(inputs, | ||
$$.getChildrenByAttribute(groupElem,'vivalidSubmit'), | ||
callbacks[onValidation[0]], | ||
callbacks[onValidation[1]], | ||
callbacks[pendingUi[0]], | ||
callbacks[pendingUi[1]], | ||
groupStatesChanged, | ||
groupPendingChanged); | ||
if ($$.hasDataSet(groupElem, 'vivalidBeforeValidation')) { | ||
onBeforeValidation = $$.getDataSet(groupElem, 'vivalidBeforeValidation'); | ||
} | ||
if ($$.hasDataSet(groupElem, 'vivalidAfterValidation')) { | ||
onAfterValidation = $$.getDataSet(groupElem, 'vivalidAfterValidation'); | ||
} | ||
return new InputGroup(inputs, | ||
$$.getChildrenByAttribute(groupElem, 'vivalidSubmit'), | ||
callbacks[onValidation[0]], | ||
callbacks[onValidation[1]], | ||
callbacks[pendingUi[0]], | ||
callbacks[pendingUi[1]], | ||
groupStatesChanged, | ||
groupPendingChanged, | ||
callbacks[onBeforeValidation], | ||
callbacks[onAfterValidation], | ||
$$.getChildrenByAttribute(groupElem, 'vivalidReset') | ||
); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function vivalidInputFromElem(el){ | ||
var tuplesArray = JSON.parse($$.getDataSet(el,'vivalidTuples')); | ||
function vivalidInputFromElem(el) { | ||
var tuplesArray = JSON.parse($$.getDataSet(el, 'vivalidTuples')); | ||
var onInputValidationResult; | ||
if ($$.hasDataSet(el,'vivalidResult')){ | ||
onInputValidationResult = $$.getDataSet(el,'vivalidResult'); | ||
if ($$.hasDataSet(el, 'vivalidResult')) { | ||
onInputValidationResult = $$.getDataSet(el, 'vivalidResult'); | ||
} | ||
return new Input(el,tuplesArray,callbacks[onInputValidationResult]); | ||
var isBlurOnly = $$.hasDataSet(el, 'vivalidBlurOnly'); | ||
return new Input(el, tuplesArray, callbacks[onInputValidationResult], isBlurOnly); | ||
} | ||
/** | ||
@@ -281,6 +344,6 @@ * The interface to use when using data attributes to define Inputs And Groups. | ||
initAll: initAll, | ||
initGroup: initGroup | ||
initGroup: initGroup, | ||
resetGroup: resetGroup | ||
}; | ||
},{"./constants":1,"./dom-helpers":2,"./input":5,"./input-group":4}],4:[function(require,module,exports){ | ||
},{"./constants":1,"./dom-helpers":2,"./input":6,"./input-group":4}],4:[function(require,module,exports){ | ||
var Input = require('./input'); | ||
@@ -303,126 +366,137 @@ var stateEnum = require('./state-enum'); | ||
* @param {function} [groupPendingChanged] | ||
* @param {function} [onBeforeValidation] Signature of {@link _internal.onBeforeValidation onBeforeValidation}. A function to be called before triggering any of the input's validators | ||
* @param {function} [onAfterValidation] Signature of {@link _internal.onAfterValidation onAfterValidation}. A function to be called after triggering all of the input's validators | ||
* @param {HTMLElement[]} [resetElems] an array of elements that should trigger the group's validation _reset. | ||
*/ | ||
function InputGroup(inputsArray,submitElems,onValidationSuccess,onValidationFailure,pendingUiStart,pendingUiStop, groupStatesChanged, groupPendingChanged){ | ||
function InputGroup(inputsArray, submitElems, onValidationSuccess, onValidationFailure, pendingUiStart, pendingUiStop, groupStatesChanged, groupPendingChanged, onBeforeValidation, onAfterValidation, resetElems) { | ||
if(!onValidationSuccess || !onValidationFailure) throw ERROR.mandatorySuccessFailure; | ||
if (!onValidationSuccess || !onValidationFailure) throw ERROR.mandatorySuccessFailure; | ||
this.inputs = []; | ||
this.inputElems = []; | ||
this.submitElems = []; | ||
this._inputs = []; | ||
this._inputElems = []; | ||
this._submitElems = []; | ||
this._resetElems = []; | ||
this._onValidationSuccess = onValidationSuccess; | ||
this._onValidationFailure = onValidationFailure; | ||
this._pendingUiStart = pendingUiStart; | ||
this._pendingUiStop = pendingUiStop; | ||
this._groupStatesChanged = groupStatesChanged; | ||
this._onBeforeValidation = onBeforeValidation; | ||
this._onAfterValidation = onAfterValidation; | ||
this.onValidationSuccess = onValidationSuccess; | ||
this.onValidationFailure = onValidationFailure; | ||
this.pendingUiStart = pendingUiStart; | ||
this.pendingUiStop = pendingUiStop; | ||
this.groupStatesChanged = groupStatesChanged; | ||
this._groupPendingChangedListeners = []; | ||
this._groupPendingChangedListeners.push( | ||
function(_isPending) { | ||
if (!_isPending) { | ||
if (this._isPendingUiStartRun) { | ||
this.groupPendingChangedListeners = []; | ||
this.groupPendingChangedListeners.push( | ||
function(isPending){ | ||
if(!isPending){ | ||
if(this.isPendingUiStartRun){ | ||
this._pendingUiStop.call(this._pendingUiLastSubmitElem, this._inputElems, this._submitElems, this._resetElems); | ||
this.pendingUiStop.call(this.pendingUiLastSubmitElem,this.inputElems,this.submitElems); | ||
this._getOnSubmit.call(this).call(this._pendingUiLastSubmitElem); | ||
this.getOnSubmit.call(this).call(this.pendingUiLastSubmitElem); | ||
this.isPendingUiStartRun = false; | ||
this.pendingUiLastSubmitElem = {}; | ||
this._isPendingUiStartRun = false; | ||
this._pendingUiLastSubmitElem = {}; | ||
} | ||
} | ||
}.bind(this) | ||
}.bind(this) | ||
); | ||
if (groupPendingChanged) | ||
this.groupPendingChangedListeners.push(groupPendingChanged); | ||
if (groupPendingChanged) | ||
this._groupPendingChangedListeners.push(groupPendingChanged); | ||
this.stateCounters = {}; | ||
this.stateCounters[stateEnum.invalid] = 0; | ||
this.stateCounters[stateEnum.pending] = 0; | ||
this.stateCounters[stateEnum.valid] = 0; | ||
this._stateCounters = {}; | ||
this._stateCounters[stateEnum.invalid] = 0; | ||
this._stateCounters[stateEnum.pending] = 0; | ||
this._stateCounters[stateEnum.valid] = 0; | ||
this.isPendingChangeTrueRun = false; | ||
this._isPendingChangeTrueRun = false; | ||
this.isPendingUiStartRun = false; | ||
this._isPendingUiStartRun = false; | ||
this.pendingUiLastSubmitElem = {}; | ||
this._pendingUiLastSubmitElem = {}; | ||
this.inputs = inputsArray.map(function(input){ | ||
this._inputs = inputsArray.map(function(input) { | ||
input.setGroup(this); | ||
return input; | ||
},this); | ||
}, this); | ||
this.inputElems = inputsArray | ||
.map(function(input){ | ||
return input.el; | ||
}); | ||
this._inputElems = inputsArray | ||
.map(function(input) { | ||
return input.getDomElement(); | ||
}); | ||
this._stateCounters[stateEnum.valid] = inputsArray.length; | ||
this.stateCounters[stateEnum.valid] = inputsArray.length; | ||
this._submitElems = Array.prototype.slice.call(submitElems); | ||
this.submitElems = Array.prototype.slice.call((submitElems)); | ||
this._submitElems.forEach(function(submit) { | ||
submit.addEventListener('click', this._getOnSubmit.call(this)); | ||
}, this); | ||
this.submitElems.forEach(function(submit){ | ||
submit.addEventListener('click',this.getOnSubmit.call(this)); | ||
},this); | ||
if (resetElems) { | ||
this._resetElems = Array.prototype.slice.call(resetElems); | ||
this._resetElems.forEach(function(submit) { | ||
submit.addEventListener('click', this.reset.bind(this)); | ||
}, this); | ||
} | ||
} | ||
InputGroup.prototype = (function(){ | ||
InputGroup.prototype = (function() { | ||
return { | ||
isValid: isValid, | ||
isPending: isPending, | ||
getOnSubmit: getOnSubmit, | ||
triggerInputsValidation: triggerInputsValidation, | ||
updateGroupListeners: updateGroupListeners, | ||
updateGroupStates: updateGroupStates, | ||
reset: reset, | ||
getOnBeforeValidation: getOnBeforeValidation, | ||
getOnAfterValidation: getOnAfterValidation, | ||
_isValid: _isValid, | ||
_isPending: _isPending, | ||
_getOnSubmit: _getOnSubmit, | ||
_triggerInputsValidation: _triggerInputsValidation | ||
}; | ||
function isValid(){ | ||
this.triggerInputsValidation(); | ||
function _isValid() { | ||
this._triggerInputsValidation(); | ||
return (this.stateCounters[stateEnum.invalid] === 0 && | ||
this.stateCounters[stateEnum.pending] === 0); | ||
return (this._stateCounters[stateEnum.invalid] === 0 && | ||
this._stateCounters[stateEnum.pending] === 0); | ||
} | ||
function isPending(){ | ||
this.triggerInputsValidation(); | ||
function _isPending() { | ||
this._triggerInputsValidation(); | ||
return (this.stateCounters[stateEnum.invalid] === 0 && | ||
this.stateCounters[stateEnum.pending] > 0); | ||
return (this._stateCounters[stateEnum.invalid] === 0 && | ||
this._stateCounters[stateEnum.pending] > 0); | ||
} | ||
function getOnSubmit() { | ||
function _getOnSubmit() { | ||
var self = this; | ||
return function (e) { | ||
if(e) e.preventDefault(); | ||
return function(e) { | ||
if (e) e.preventDefault(); | ||
if(self.isPending()){ | ||
self.pendingUiStart.call(this,self.inputElems,self.submitElems); | ||
self.isPendingUiStartRun = true; | ||
self.pendingUiLastSubmitElem = this; | ||
if (self._isPending()) { | ||
self._pendingUiStart.call(this, self._inputElems, self._submitElems, self._resetElems); | ||
self._isPendingUiStartRun = true; | ||
self._pendingUiLastSubmitElem = this; | ||
} else if (!self._isValid()) { | ||
self._onValidationFailure.call(this, | ||
self._stateCounters[stateEnum.invalid], | ||
self._stateCounters[stateEnum.pending], | ||
self._stateCounters[stateEnum.valid]); | ||
} else { | ||
self._onValidationSuccess.call(this); | ||
} | ||
else if(!self.isValid()){ | ||
self.onValidationFailure.call(this, | ||
self.stateCounters[stateEnum.invalid], | ||
self.stateCounters[stateEnum.pending], | ||
self.stateCounters[stateEnum.valid]); | ||
} | ||
else { | ||
self.onValidationSuccess.call(this); | ||
} | ||
if (DEBUG){ | ||
if (DEBUG) { | ||
console.debug('cuurent states:'); | ||
console.debug("invalid: " + self.stateCounters[stateEnum.invalid]); | ||
console.debug("pending: " + self.stateCounters[stateEnum.pending]); | ||
console.debug("valid: " + self.stateCounters[stateEnum.valid]); | ||
console.debug("invalid: " + self._stateCounters[stateEnum.invalid]); | ||
console.debug("pending: " + self._stateCounters[stateEnum.pending]); | ||
console.debug("valid: " + self._stateCounters[stateEnum.valid]); | ||
} | ||
@@ -434,4 +508,4 @@ | ||
function triggerInputsValidation(){ | ||
this.inputs.forEach(function(input){ | ||
function _triggerInputsValidation() { | ||
this._inputs.forEach(function(input) { | ||
input.triggerValidation(); | ||
@@ -441,29 +515,47 @@ }); | ||
function updateGroupStates(fromInputState, toInputState){ | ||
function getOnBeforeValidation(){ | ||
return this._onBeforeValidation; | ||
} | ||
function getOnAfterValidation(){ | ||
return this._onAfterValidation; | ||
} | ||
function updateGroupStates(fromInputState, toInputState) { | ||
if (fromInputState.stateEnum === toInputState.stateEnum) return; | ||
this.stateCounters[fromInputState.stateEnum]--; | ||
this.stateCounters[toInputState.stateEnum]++; | ||
this._stateCounters[fromInputState.stateEnum]--; | ||
this._stateCounters[toInputState.stateEnum]++; | ||
} | ||
function updateGroupListeners(){ | ||
if (this.groupStatesChanged) this.groupStatesChanged(); | ||
function updateGroupListeners() { | ||
if (this._groupStatesChanged) this._groupStatesChanged(); | ||
// run both internal and user groupPendingChange functions | ||
this.groupPendingChangedListeners.forEach(function(listener){ | ||
this._groupPendingChangedListeners.forEach(function(listener) { | ||
if (!this.isPendingChangeTrueRun && this.stateCounters[stateEnum.invalid] === 0 && this.stateCounters[stateEnum.pending] > 0) | ||
{ | ||
listener(true); | ||
this.isPendingChangeTrueRun = true; | ||
} | ||
else if (this.isPendingChangeTrueRun && this.stateCounters[stateEnum.pending] === 0) | ||
{ | ||
listener(false); | ||
this.isPendingChangeTrueRun = false; | ||
} | ||
},this); | ||
if (!this._isPendingChangeTrueRun && this._stateCounters[stateEnum.invalid] === 0 && this._stateCounters[stateEnum.pending] > 0) { | ||
listener(true); | ||
this._isPendingChangeTrueRun = true; | ||
} else if (this._isPendingChangeTrueRun && this._stateCounters[stateEnum.pending] === 0) { | ||
listener(false); | ||
this._isPendingChangeTrueRun = false; | ||
} | ||
}, this); | ||
} | ||
function reset(e) { | ||
if (e && e.preventDefault) e.preventDefault(); | ||
this._inputs.forEach(function(input) { | ||
input.reset(); | ||
}); | ||
this._stateCounters[stateEnum.invalid] = 0; | ||
this._stateCounters[stateEnum.pending] = 0; | ||
this._stateCounters[stateEnum.valid] = this._inputs.length; | ||
} | ||
})(); | ||
@@ -503,2 +595,3 @@ | ||
* @param {HTMLElement[]} submitElems the group's submit elements | ||
* @param {HTMLElement[]} resetElems the group's _reset elements | ||
*/ | ||
@@ -512,9 +605,40 @@ | ||
* @param {HTMLElement[]} submitElems the group's submit elements | ||
* @param {HTMLElement[]} resetElems the group's _reset elements | ||
*/ | ||
},{"./constants":1,"./input":5,"./state-enum":6}],5:[function(require,module,exports){ | ||
/** A function to be called before triggering any of the input's validators | ||
* @name onBeforeValidation | ||
* @function | ||
* @memberof! _internal | ||
* @param {HTMLElement} el the input's DOM object. | ||
*/ | ||
/** A function to be called after triggering all of the input's validators | ||
* @name onAfterValidation | ||
* @function | ||
* @memberof! _internal | ||
* @param {HTMLElement} el the input's DOM object. | ||
*/ | ||
},{"./constants":1,"./input":6,"./state-enum":7}],5:[function(require,module,exports){ | ||
var ValidationState = require('./validation-state'); | ||
var stateEnum = require('./state-enum'); | ||
function InputState() { | ||
this.isNoneChecked = false; | ||
this.validationState = new ValidationState('', stateEnum.valid); | ||
this.validationCycle = 0; | ||
this.isChanged = false; | ||
this.activeEventType = ''; | ||
} | ||
module.exports = InputState; | ||
},{"./state-enum":7,"./validation-state":8}],6:[function(require,module,exports){ | ||
var validatorRepo = require('./validator-repo'); | ||
var stateEnum = require('./state-enum'); | ||
var ValidationState = require('./validation-state'); | ||
var InputState = require('./input-state'); | ||
var constants = require('./constants'); | ||
var $$ = require('./dom-helpers'); | ||
@@ -532,42 +656,33 @@ var validInputTagNames = constants.validInputTagNames; | ||
* @param {function} [onInputValidationResult] Signature of {@link _internal.onInputValidationResult onInputValidationResult}. A function to handle an input state or message change. If not passed, {@link _internal.defaultOnInputValidationResult defaultOnInputValidationResult} will be used. | ||
* @param {boolean} isBlurOnly if true, doesn't not trigger validation on 'input' or 'change' events. | ||
*/ | ||
function Input(el, validatorsNameOptionsTuples, onInputValidationResult){ | ||
function Input(el, validatorsNameOptionsTuples, onInputValidationResult, isBlurOnly) { | ||
if (validInputTagNames.indexOf(el.nodeName.toLowerCase()) === -1){ | ||
if (validInputTagNames.indexOf(el.nodeName.toLowerCase()) === -1) { | ||
throw 'only operates on the following html tags: ' + validInputTagNames.toString(); | ||
} | ||
this.group = {}; | ||
this._el = el; | ||
this._validatorsNameOptionsTuples = validatorsNameOptionsTuples; | ||
this._onInputValidationResult = onInputValidationResult || defaultOnInputValidationResult; | ||
this._isBlurOnly = isBlurOnly; | ||
this._validators = buildValidators(); | ||
this._inputState = new InputState(); | ||
this._elName = el.nodeName.toLowerCase(); | ||
this._elType = el.type; | ||
this._isKeyed = (this._elName === 'textarea' || keyStrokedInputTypes.indexOf(this._elType) > -1); | ||
this._runValidatorsBounded = this._runValidators.bind(this); | ||
this.el = el; | ||
this.validators = buildValidators(); | ||
this.onInputValidationResult = onInputValidationResult || defaultOnInputValidationResult; | ||
this.isNoneChecked = false; | ||
this._initListeners(); | ||
this.validationState = new ValidationState('', stateEnum.valid); | ||
this.validationCycle = 0; | ||
this.isChanged = false; | ||
this.elName = el.nodeName.toLowerCase(); | ||
this.elType = el.type; | ||
this.isKeyed = (this.elName === 'textarea' || keyStrokedInputTypes.indexOf(this.elType) > -1); | ||
this.activeEventType = ''; | ||
this._runValidatorsBounded = this.runValidators.bind(this); | ||
this.initListeners(); | ||
function buildValidators(){ | ||
function buildValidators() { | ||
var result = []; | ||
validatorsNameOptionsTuples.forEach(function(validatorsNameOptionsTuple){ | ||
validatorsNameOptionsTuples.forEach(function(validatorsNameOptionsTuple) { | ||
var validatorName = validatorsNameOptionsTuple[0]; | ||
var validatorOptions = validatorsNameOptionsTuple[1]; | ||
result.push( | ||
{ | ||
name: validatorName, | ||
run: validatorRepo.build(validatorName,validatorOptions) | ||
} | ||
); | ||
result.push({ | ||
name: validatorName, | ||
run: validatorRepo.build(validatorName, validatorOptions) | ||
}); | ||
@@ -579,3 +694,2 @@ }); | ||
/** The default {@link _internal.onInputValidationResult onInputValidationResult} used when {@link vivalid.Input} is initiated without a 3rd parameter | ||
@@ -586,3 +700,3 @@ * @name defaultOnInputValidationResult | ||
*/ | ||
function defaultOnInputValidationResult(el,validationsResult,validatorName,stateEnum) { | ||
function defaultOnInputValidationResult(el, validationsResult, validatorName, stateEnum) { | ||
@@ -592,29 +706,28 @@ var errorDiv; | ||
// for radio buttons and checkboxes: get the last element in group by name | ||
if ((el.nodeName.toLowerCase() === 'input' && (el.type === 'radio' || el.type === 'checkbox' ))){ | ||
if ((el.nodeName.toLowerCase() === 'input' && (el.type === 'radio' || el.type === 'checkbox'))) { | ||
var getAllByName = el.parentNode.querySelectorAll('input[name="'+el.name+'"]'); | ||
var getAllByName = el.parentNode.querySelectorAll('input[name="' + el.name + '"]'); | ||
el = getAllByName.item(getAllByName.length-1); | ||
el = getAllByName.item(getAllByName.length - 1); | ||
} | ||
if(validationsResult.stateEnum === stateEnum.invalid){ | ||
if (validationsResult.stateEnum === stateEnum.invalid) { | ||
errorDiv = getExistingErrorDiv(el); | ||
if(errorDiv) { | ||
if (errorDiv) { | ||
errorDiv.textContent = validationsResult.message; | ||
} else { | ||
appendNewErrorDiv(el, validationsResult.message); | ||
} | ||
else { | ||
appendNewErrorDiv(el,validationsResult.message); | ||
} | ||
el.style.borderStyle = "solid"; | ||
el.style.borderColor = "#ff0000"; | ||
} | ||
else { | ||
$$.addClass(el, "vivalid-error-input"); | ||
} else { | ||
errorDiv = getExistingErrorDiv(el); | ||
if(errorDiv) { | ||
if (errorDiv) { | ||
errorDiv.parentNode.removeChild(errorDiv); | ||
el.style.borderStyle = ""; | ||
el.style.borderColor = ""; | ||
$$.removeClass(el, "vivalid-error-input"); | ||
} | ||
@@ -624,4 +737,4 @@ } | ||
function getExistingErrorDiv(el) { | ||
if (el.nextSibling.className === "vivalid-error") { | ||
return el.nextSibling; | ||
if (el.nextElementSibling && el.nextElementSibling.className === "vivalid-error") { | ||
return el.nextElementSibling; | ||
} | ||
@@ -631,9 +744,9 @@ | ||
function appendNewErrorDiv(el,message) { | ||
errorDiv = document.createElement("DIV"); | ||
function appendNewErrorDiv(el, message) { | ||
errorDiv = document.createElement("DIV"); | ||
errorDiv.className = "vivalid-error"; | ||
errorDiv.style.color = "#ff0000"; | ||
var t = document.createTextNode(validationsResult.message); | ||
errorDiv.appendChild(t); | ||
el.parentNode.insertBefore(errorDiv, el.nextSibling); | ||
var t = document.createTextNode(validationsResult.message); | ||
errorDiv.appendChild(t); | ||
el.parentNode.insertBefore(errorDiv, el.nextElementSibling); | ||
} | ||
@@ -648,37 +761,36 @@ | ||
return { | ||
reBindCheckedElement: reBindCheckedElement, | ||
triggerValidation: triggerValidation, | ||
runValidators: runValidators, | ||
changeEventType: changeEventType, | ||
initListeners: initListeners, | ||
setGroup: setGroup, | ||
addChangeListener: addChangeListener, | ||
addEventType: addEventType, | ||
removeActiveEventType: removeActiveEventType, | ||
getUpdateInputValidationResultAsync: getUpdateInputValidationResultAsync, | ||
updateInputValidationResult: updateInputValidationResult | ||
reset: reset, | ||
getDomElement: getDomElement, | ||
_reBindCheckedElement: _reBindCheckedElement, | ||
_runValidators: _runValidators, | ||
_changeEventType: _changeEventType, | ||
_initListeners: _initListeners, | ||
_addChangeListener: _addChangeListener, | ||
_addEventType: _addEventType, | ||
_removeActiveEventType: _removeActiveEventType, | ||
_getUpdateInputValidationResultAsync: _getUpdateInputValidationResultAsync, | ||
_updateInputValidationResult: _updateInputValidationResult | ||
}; | ||
// public | ||
function _reBindCheckedElement() { | ||
function reBindCheckedElement(){ | ||
// reBind only radio and checkbox buttons | ||
if (!(this.el.nodeName.toLowerCase() === 'input' && (this.el.type === 'radio' || this.el.type === 'checkbox' ))){ | ||
if (!(this._el.nodeName.toLowerCase() === 'input' && (this._el.type === 'radio' || this._el.type === 'checkbox'))) { | ||
return; | ||
} | ||
var checkedElement = document.querySelector('input[name="'+this.el.name+'"]:checked'); | ||
if (checkedElement){ | ||
this.el = checkedElement; | ||
this.isNoneChecked = false; | ||
var checkedElement = document.querySelector('input[name="' + this._el.name + '"]:checked'); | ||
if (checkedElement) { | ||
this._el = checkedElement; | ||
this._inputState.isNoneChecked = false; | ||
} else { | ||
this._inputState.isNoneChecked = true; | ||
} | ||
else{ | ||
this.isNoneChecked = true; | ||
} | ||
} | ||
function triggerValidation(){ | ||
if (this.validationCycle === 0 || this.isChanged) { | ||
function triggerValidation() { | ||
if (this._inputState.validationCycle === 0 || this._inputState.isChanged) { | ||
this._runValidatorsBounded(); | ||
@@ -688,45 +800,53 @@ } | ||
function changeEventType(eventType) { | ||
if (!this.isKeyed) return; | ||
if (eventType === this.activeEventType) return; | ||
this.removeActiveEventType(); | ||
this.addEventType(eventType); | ||
function _changeEventType(eventType) { | ||
if (!this._isKeyed) return; | ||
if (eventType === this._inputState.activeEventType) return; | ||
this._removeActiveEventType(); | ||
this._addEventType(eventType); | ||
} | ||
function setGroup(value) { | ||
this.group = value; | ||
this._group = value; | ||
} | ||
function initListeners() { | ||
function getDomElement(){ | ||
return this._el; | ||
} | ||
this.addChangeListener(); | ||
if (this.isKeyed){ | ||
this.addEventType('blur'); | ||
function _initListeners() { | ||
this._addChangeListener(); | ||
if (this._isKeyed) { | ||
this._addEventType('blur'); | ||
} else { | ||
this._addEventType('change'); | ||
} | ||
else { | ||
this.addEventType('change'); | ||
} | ||
} | ||
function runValidators(event, fromIndex){ | ||
function _runValidators(event, fromIndex) { | ||
this.validationCycle++; | ||
this.reBindCheckedElement(); | ||
this._inputState.validationCycle++; | ||
this._reBindCheckedElement(); | ||
if (typeof this._group.getOnBeforeValidation() === 'function') { | ||
this._group.getOnBeforeValidation()(this._el); | ||
} | ||
var validationsResult, validatorName; | ||
var i = fromIndex || 0; | ||
for (; i < this.validators.length; i++){ | ||
var validator = this.validators[i]; | ||
var elementValue = this.isNoneChecked ? '' : this.el.value; | ||
for (; i < this._validators.length; i++) { | ||
var validator = this._validators[i]; | ||
var elementValue = this._inputState.isNoneChecked ? '' : this._el.value; | ||
// if async, then return a pending enum with empty message and call the callback with result once ready | ||
var validatorResult = validator.run(elementValue, this.getUpdateInputValidationResultAsync(validator.name, i, this.validationCycle)); | ||
if (validatorResult.stateEnum !== stateEnum.valid) | ||
{ | ||
validationsResult = validatorResult; | ||
validatorName = validator.name; | ||
this.changeEventType('input'); //TODO: call only once? | ||
break; | ||
var validatorResult = validator.run(elementValue, this._getUpdateInputValidationResultAsync(validator.name, i, this._inputState.validationCycle)); | ||
if (validatorResult.stateEnum !== stateEnum.valid) { | ||
validationsResult = validatorResult; | ||
validatorName = validator.name; | ||
if (!this._isBlurOnly) { | ||
this._changeEventType('input'); //TODO: call only once? | ||
} | ||
break; | ||
} | ||
@@ -736,64 +856,78 @@ } | ||
validationsResult = validationsResult || new ValidationState('', stateEnum.valid); | ||
this.updateInputValidationResult(validationsResult,validatorName); | ||
this._updateInputValidationResult(validationsResult, validatorName); | ||
// new... | ||
this.isChanged = false; // TODO: move to top of function | ||
this._inputState.isChanged = false; // TODO: move to top of function? | ||
if (typeof this._group.getOnAfterValidation() === 'function') { | ||
this._group.getOnAfterValidation()(this._el); | ||
} | ||
} | ||
// private | ||
function reset() { | ||
this._removeActiveEventType(); | ||
this._initListeners(); | ||
this._inputState = new InputState(); | ||
this._onInputValidationResult(this._el, stateEnum.valid, '', stateEnum); // called with valid state to clear any previous errors UI | ||
} | ||
function addChangeListener(){ | ||
if (this.isKeyed){ | ||
this.el.addEventListener('input', function () { this.isChanged = true;}, false); | ||
} | ||
function _addChangeListener() { | ||
else if (this.elName === 'input' && (this.elType === 'radio' || this.elType === 'checkbox')){ | ||
var self = this; | ||
var groupElements = document.querySelectorAll('input[name="'+this.el.name+'"]'); | ||
if (this._isKeyed) { | ||
if (this._isBlurOnly) { | ||
return; | ||
} else { | ||
this._el.addEventListener('input', function() { | ||
self._inputState.isChanged = true; | ||
}, false); | ||
} | ||
} else if (this._elName === 'input' && (this._elType === 'radio' || this._elType === 'checkbox')) { | ||
var i=0; | ||
for (; i <groupElements.length ; i++) { | ||
groupElements[i].addEventListener('change', function (){this.isChanged = true;}, false); | ||
var groupElements = document.querySelectorAll('input[name="' + this._el.name + '"]'); | ||
var i = 0; | ||
for (; i < groupElements.length; i++) { | ||
groupElements[i].addEventListener('change', function() { | ||
self._inputState.isChanged = true; | ||
}, false); | ||
} | ||
} else if (this._elName === 'select') { | ||
this._el.addEventListener('change', function() { | ||
self._inputState.isChanged = true; | ||
}, false); | ||
} | ||
else if(this.elName === 'select'){ | ||
this.el.addEventListener('change', function (){this.isChanged = true;}, false); | ||
} | ||
} | ||
function addEventType(eventType) { | ||
if (this.isKeyed){ | ||
this.el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
function _addEventType(eventType) { | ||
if (this._isKeyed) { | ||
this._el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} else if (this._elName === 'input' && (this._elType === 'radio' || this._elType === 'checkbox')) { | ||
else if (this.elName === 'input' && (this.elType === 'radio' || this.elType === 'checkbox')){ | ||
var groupElements = document.querySelectorAll('input[name="' + this._el.name + '"]'); | ||
var groupElements = document.querySelectorAll('input[name="'+this.el.name+'"]'); | ||
var i=0; | ||
for (; i <groupElements.length ; i++) { | ||
var i = 0; | ||
for (; i < groupElements.length; i++) { | ||
groupElements[i].addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
} else if (this._elName === 'select') { | ||
this._el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
else if(this.elName === 'select'){ | ||
this.el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
this.activeEventType = eventType; | ||
this._inputState.activeEventType = eventType; | ||
} | ||
function removeActiveEventType() { | ||
this.el.removeEventListener(this.activeEventType, this._runValidatorsBounded, false); | ||
function _removeActiveEventType() { | ||
this._el.removeEventListener(this._inputState.activeEventType, this._runValidatorsBounded, false); | ||
} | ||
function getUpdateInputValidationResultAsync(validatorName, validatorIndex, asyncValidationCycle) { | ||
function _getUpdateInputValidationResultAsync(validatorName, validatorIndex, asyncValidationCycle) { | ||
var self = this; | ||
return function(validatorResult){ | ||
return function(validatorResult) { | ||
// guard against updating async validations from old cycles | ||
if(asyncValidationCycle && asyncValidationCycle !== self.validationCycle){ | ||
if (asyncValidationCycle && asyncValidationCycle !== self._inputState.validationCycle) { | ||
return; | ||
@@ -803,9 +937,7 @@ } | ||
// if pending turned to be valid, and there are more validation to run, run them: | ||
if (validatorResult.stateEnum === stateEnum.valid && validatorIndex+1 < self.validators.length){ | ||
self._runValidatorsBounded(null,validatorIndex+1); | ||
if (validatorResult.stateEnum === stateEnum.valid && validatorIndex + 1 < self._validators.length) { | ||
self._runValidatorsBounded(null, validatorIndex + 1); | ||
} else { | ||
self._updateInputValidationResult(validatorResult, validatorName); | ||
} | ||
else { | ||
self.updateInputValidationResult(validatorResult,validatorName); | ||
} | ||
}; | ||
@@ -815,9 +947,9 @@ | ||
function updateInputValidationResult(validationsResult,validatorName) { | ||
function _updateInputValidationResult(validationsResult, validatorName) { | ||
this.group.updateGroupStates(this.validationState, validationsResult); // filter equal state at caller | ||
this.group.updateGroupListeners(); | ||
this._group.updateGroupStates(this._inputState.validationState, validationsResult); // filter equal state at caller | ||
this._group.updateGroupListeners(); | ||
this.validationState = validationsResult; | ||
this.onInputValidationResult(this.el,validationsResult,validatorName,stateEnum); | ||
this._inputState.validationState = validationsResult; | ||
this._onInputValidationResult(this._el, validationsResult, validatorName, stateEnum); | ||
@@ -847,3 +979,3 @@ } | ||
},{"./constants":1,"./state-enum":6,"./validation-state":7,"./validator-repo":8}],6:[function(require,module,exports){ | ||
},{"./constants":1,"./dom-helpers":2,"./input-state":5,"./state-enum":7,"./validation-state":8,"./validator-repo":9}],7:[function(require,module,exports){ | ||
/** | ||
@@ -862,4 +994,3 @@ * An Enum with 3 states: invalid , pending , valid . | ||
module.exports = stateEnum; | ||
},{}],7:[function(require,module,exports){ | ||
},{}],8:[function(require,module,exports){ | ||
/** | ||
@@ -873,3 +1004,3 @@ * {constructor} creates a new ValidationState object with a validation message and state. | ||
*/ | ||
function ValidationState(message, stateEnum){ | ||
function ValidationState(message, stateEnum) { | ||
this.message = message; | ||
@@ -884,4 +1015,3 @@ this.stateEnum = stateEnum; | ||
*/ | ||
},{}],8:[function(require,module,exports){ | ||
},{}],9:[function(require,module,exports){ | ||
var stateEnum = require('./state-enum'); | ||
@@ -900,5 +1030,5 @@ var ValidationState = require('./validation-state'); | ||
*/ | ||
function addBuilder(name, fn){ | ||
function addBuilder(name, fn) { | ||
if (typeof fn !== 'function') throw 'error while trying to register a Validator: argument must be a function'; | ||
validatorBuildersRepository [name] = fn; | ||
validatorBuildersRepository[name] = fn; | ||
} | ||
@@ -914,3 +1044,3 @@ | ||
*/ | ||
function build(validatorName,validatorOptions) { | ||
function build(validatorName, validatorOptions) { | ||
@@ -921,3 +1051,3 @@ if (typeof validatorBuildersRepository[validatorName] !== 'function') { | ||
return validatorBuildersRepository[validatorName](ValidationState,stateEnum,validatorOptions); | ||
return validatorBuildersRepository[validatorName](ValidationState, stateEnum, validatorOptions); | ||
} | ||
@@ -934,4 +1064,3 @@ | ||
}; | ||
},{"./state-enum":6,"./validation-state":7}],"vivalid":[function(require,module,exports){ | ||
},{"./state-enum":7,"./validation-state":8}],"vivalid":[function(require,module,exports){ | ||
'use strict'; | ||
@@ -957,3 +1086,3 @@ | ||
},{"./constants":1,"./html-interface":3,"./input":5,"./input-group":4,"./validator-repo":8}]},{},["vivalid"]))("vivalid") | ||
},{"./constants":1,"./html-interface":3,"./input":6,"./input-group":4,"./validator-repo":9}]},{},["vivalid"]))("vivalid") | ||
}); |
@@ -1,1 +0,1 @@ | ||
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.vivalid=t()}}(function(){return(require=function t(e,i,n){function a(r,o){if(!i[r]){if(!e[r]){var u="function"==typeof require&&require;if(!o&&u)return u(r,!0);if(s)return s(r,!0);var d=new Error("Cannot find module '"+r+"'");throw d.code="MODULE_NOT_FOUND",d}var l=i[r]={exports:{}};e[r][0].call(l.exports,function(t){var i=e[r][1][t];return a(i?i:t)},l,l.exports,t,e,i,n)}return i[r].exports}for(var s="function"==typeof require&&require,r=0;r<n.length;r++)a(n[r]);return a}({1:[function(t,e,i){e.exports={VERSION:"0.1.0",DEBUG:!1,validInputTagNames:["input","textarea","select"],keyStrokedInputTypes:["text","email","password","search"],ERROR:{mandatorySuccessFailure:"passing callbacks for onValidationSuccess and onValidationFailure is mandatory"}}},{}],2:[function(t,e,i){function n(t){return Array.prototype.slice.call(t)}function a(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}function s(t,e){e||(e=document);for(var i=[],n=0;n<t.length;n++)for(var a=e.getElementsByTagName(t[n]),s=0;s<a.length;s++)i.push(a[s]);return i}function r(t,e){for(;t&&t!==document;t=t.parentNode)if(h(t,e))return t;return!1}function o(t,e){return n(t.getElementsByTagName("*")).filter(function(t){return h(t,e)?!0:void 0})}function u(){var t=document.createElement("div");return t.setAttribute("data-a-b","c"),!(!t.dataset||"c"!==t.dataset.aB)}function d(t,e){return t.nodeType!==Node.ELEMENT_NODE?!1:t.getAttribute("data-"+c(e))}function l(t,e){return t.nodeType!==Node.ELEMENT_NODE?!1:t.dataset[e]}function h(t,e){return t.nodeType===Node.ELEMENT_NODE&&t.hasAttribute("data-"+c(e))}function c(t){return t.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()})}var p=u();e.exports={getDataSet:p?l:d,hasDataSet:h,getElementsByTagNames:s,getClosestParentByAttribute:r,getChildrenByAttribute:o,ready:a,toArray:n}},{}],3:[function(t,e,i){function n(t,e){if("function"!=typeof e)throw"error while trying to add a custom callback: argument must be a function";if(p[t])throw"error while trying to add a custom callback: "+t+" already exists";p[t]=e}function a(t){function e(){inputElems=l.getElementsByTagNames(c,t).filter(function(t){return l.hasDataSet(t,"vivalidTuples")}),r(t,inputElems)}l.ready(e)}function s(){function t(){function t(t,a){if(!t)throw"an input validation is missing a group, input id: "+a.id;t._groupId||(t._groupId=e++),i[t._groupId]||(i[t._groupId]=[],n[t._groupId]=t),i[t._groupId].push(a)}var e=1,i={},n={};l.getElementsByTagNames(c).filter(function(t){return l.hasDataSet(t,"vivalidTuples")}).forEach(function(e){t(l.getClosestParentByAttribute(e,"vivalidGroup"),e)});for(var a in i)r(n[a],i[a])}l.ready(t)}function r(t,e){var i,n,a=e.map(o),s=[null,null],r=[null,null];if(l.hasDataSet(t,"vivalidOnValidation")&&(s=JSON.parse(l.getDataSet(t,"vivalidOnValidation")),!Array.isArray(s)||2!==s.length))throw"data-vivalid-on-validation value should be an array of size 2";if(l.hasDataSet(t,"vivalidPendingUi")&&(r=JSON.parse(l.getDataSet(t,"vivalidPendingUi")),!Array.isArray(r)||2!==r.length))throw"data-vivalid-pending-ui value should be an array of size 2";l.hasDataSet(t,"vivalidStatesChanged")&&(i=l.getDataSet(t,"vivalidStatesChanged")),l.hasDataSet(t,"vivalidPendingChanged")&&(n=l.getDataSet(t,"vivalidPendingChanged")),new d(a,l.getChildrenByAttribute(t,"vivalidSubmit"),p[s[0]],p[s[1]],p[r[0]],p[r[1]],i,n)}function o(t){var e,i=JSON.parse(l.getDataSet(t,"vivalidTuples"));return l.hasDataSet(t,"vivalidResult")&&(e=l.getDataSet(t,"vivalidResult")),new u(t,i,p[e])}var u=t("./input"),d=t("./input-group"),l=t("./dom-helpers"),h=t("./constants"),c=h.validInputTagNames,p={};e.exports={addCallback:n,initAll:s,initGroup:a}},{"./constants":1,"./dom-helpers":2,"./input":5,"./input-group":4}],4:[function(t,e,i){function n(t,e,i,n,s,o,u,d){if(!i||!n)throw r.mandatorySuccessFailure;this.inputs=[],this.inputElems=[],this.submitElems=[],this.onValidationSuccess=i,this.onValidationFailure=n,this.pendingUiStart=s,this.pendingUiStop=o,this.groupStatesChanged=u,this.groupPendingChangedListeners=[],this.groupPendingChangedListeners.push(function(t){t||this.isPendingUiStartRun&&(this.pendingUiStop.call(this.pendingUiLastSubmitElem,this.inputElems,this.submitElems),this.getOnSubmit.call(this).call(this.pendingUiLastSubmitElem),this.isPendingUiStartRun=!1,this.pendingUiLastSubmitElem={})}.bind(this)),d&&this.groupPendingChangedListeners.push(d),this.stateCounters={},this.stateCounters[a.invalid]=0,this.stateCounters[a.pending]=0,this.stateCounters[a.valid]=0,this.isPendingChangeTrueRun=!1,this.isPendingUiStartRun=!1,this.pendingUiLastSubmitElem={},this.inputs=t.map(function(t){return t.setGroup(this),t},this),this.inputElems=t.map(function(t){return t.el}),this.stateCounters[a.valid]=t.length,this.submitElems=Array.prototype.slice.call(e),this.submitElems.forEach(function(t){t.addEventListener("click",this.getOnSubmit.call(this))},this)}var a=(t("./input"),t("./state-enum")),s=t("./constants").DEBUG,r=t("./constants").ERROR;n.prototype=function(){function t(){return this.triggerInputsValidation(),0===this.stateCounters[a.invalid]&&0===this.stateCounters[a.pending]}function e(){return this.triggerInputsValidation(),0===this.stateCounters[a.invalid]&&this.stateCounters[a.pending]>0}function i(){var t=this;return function(e){e&&e.preventDefault(),t.isPending()?(t.pendingUiStart.call(this,t.inputElems,t.submitElems),t.isPendingUiStartRun=!0,t.pendingUiLastSubmitElem=this):t.isValid()?t.onValidationSuccess.call(this):t.onValidationFailure.call(this,t.stateCounters[a.invalid],t.stateCounters[a.pending],t.stateCounters[a.valid]),s&&(console.debug("cuurent states:"),console.debug("invalid: "+t.stateCounters[a.invalid]),console.debug("pending: "+t.stateCounters[a.pending]),console.debug("valid: "+t.stateCounters[a.valid]))}}function n(){this.inputs.forEach(function(t){t.triggerValidation()})}function r(t,e){t.stateEnum!==e.stateEnum&&(this.stateCounters[t.stateEnum]--,this.stateCounters[e.stateEnum]++)}function o(){this.groupStatesChanged&&this.groupStatesChanged(),this.groupPendingChangedListeners.forEach(function(t){!this.isPendingChangeTrueRun&&0===this.stateCounters[a.invalid]&&this.stateCounters[a.pending]>0?(t(!0),this.isPendingChangeTrueRun=!0):this.isPendingChangeTrueRun&&0===this.stateCounters[a.pending]&&(t(!1),this.isPendingChangeTrueRun=!1)},this)}return{isValid:t,isPending:e,getOnSubmit:i,triggerInputsValidation:n,updateGroupListeners:o,updateGroupStates:r}}(),e.exports=n},{"./constants":1,"./input":5,"./state-enum":6}],5:[function(t,e,i){function n(t,e,i){function n(){var t=[];return e.forEach(function(e){var i=e[0],n=e[1];t.push({name:i,run:a.build(i,n)})}),t}function o(t,e,i,n){function a(t){return"vivalid-error"===t.nextSibling.className?t.nextSibling:void 0}function s(t,i){r=document.createElement("DIV"),r.className="vivalid-error",r.style.color="#ff0000";var n=document.createTextNode(e.message);r.appendChild(n),t.parentNode.insertBefore(r,t.nextSibling)}var r;if("input"===t.nodeName.toLowerCase()&&("radio"===t.type||"checkbox"===t.type)){var o=t.parentNode.querySelectorAll('input[name="'+t.name+'"]');t=o.item(o.length-1)}e.stateEnum===n.invalid?(r=a(t),r?r.textContent=e.message:s(t,e.message),t.style.borderStyle="solid",t.style.borderColor="#ff0000"):(r=a(t),r&&(r.parentNode.removeChild(r),t.style.borderStyle="",t.style.borderColor=""))}if(-1===u.indexOf(t.nodeName.toLowerCase()))throw"only operates on the following html tags: "+u.toString();this.group={},this.el=t,this.validators=n(),this.onInputValidationResult=i||o,this.isNoneChecked=!1,this.validationState=new r("",s.valid),this.validationCycle=0,this.isChanged=!1,this.elName=t.nodeName.toLowerCase(),this.elType=t.type,this.isKeyed="textarea"===this.elName||d.indexOf(this.elType)>-1,this.activeEventType="",this._runValidatorsBounded=this.runValidators.bind(this),this.initListeners()}var a=t("./validator-repo"),s=t("./state-enum"),r=t("./validation-state"),o=t("./constants"),u=o.validInputTagNames,d=o.keyStrokedInputTypes;n.prototype=function(){function t(){if("input"===this.el.nodeName.toLowerCase()&&("radio"===this.el.type||"checkbox"===this.el.type)){var t=document.querySelector('input[name="'+this.el.name+'"]:checked');t?(this.el=t,this.isNoneChecked=!1):this.isNoneChecked=!0}}function e(){(0===this.validationCycle||this.isChanged)&&this._runValidatorsBounded()}function i(t){this.isKeyed&&t!==this.activeEventType&&(this.removeActiveEventType(),this.addEventType(t))}function n(t){this.group=t}function a(){this.addChangeListener(),this.isKeyed?this.addEventType("blur"):this.addEventType("change")}function o(t,e){this.validationCycle++,this.reBindCheckedElement();for(var i,n,a=e||0;a<this.validators.length;a++){var o=this.validators[a],u=this.isNoneChecked?"":this.el.value,d=o.run(u,this.getUpdateInputValidationResultAsync(o.name,a,this.validationCycle));if(d.stateEnum!==s.valid){i=d,n=o.name,this.changeEventType("input");break}}i=i||new r("",s.valid),this.updateInputValidationResult(i,n),this.isChanged=!1}function u(){if(this.isKeyed)this.el.addEventListener("input",function(){this.isChanged=!0},!1);else if("input"!==this.elName||"radio"!==this.elType&&"checkbox"!==this.elType)"select"===this.elName&&this.el.addEventListener("change",function(){this.isChanged=!0},!1);else for(var t=document.querySelectorAll('input[name="'+this.el.name+'"]'),e=0;e<t.length;e++)t[e].addEventListener("change",function(){this.isChanged=!0},!1)}function d(t){if(this.isKeyed)this.el.addEventListener(t,this._runValidatorsBounded,!1);else if("input"!==this.elName||"radio"!==this.elType&&"checkbox"!==this.elType)"select"===this.elName&&this.el.addEventListener(t,this._runValidatorsBounded,!1);else for(var e=document.querySelectorAll('input[name="'+this.el.name+'"]'),i=0;i<e.length;i++)e[i].addEventListener(t,this._runValidatorsBounded,!1);this.activeEventType=t}function l(){this.el.removeEventListener(this.activeEventType,this._runValidatorsBounded,!1)}function h(t,e,i){var n=this;return function(a){i&&i!==n.validationCycle||(a.stateEnum===s.valid&&e+1<n.validators.length?n._runValidatorsBounded(null,e+1):n.updateInputValidationResult(a,t))}}function c(t,e){this.group.updateGroupStates(this.validationState,t),this.group.updateGroupListeners(),this.validationState=t,this.onInputValidationResult(this.el,t,e,s)}return{reBindCheckedElement:t,triggerValidation:e,runValidators:o,changeEventType:i,initListeners:a,setGroup:n,addChangeListener:u,addEventType:d,removeActiveEventType:l,getUpdateInputValidationResultAsync:h,updateInputValidationResult:c}}(),e.exports=n},{"./constants":1,"./state-enum":6,"./validation-state":7,"./validator-repo":8}],6:[function(t,e,i){var n={invalid:1,pending:2,valid:3};e.exports=n},{}],7:[function(t,e,i){function n(t,e){this.message=t,this.stateEnum=e}e.exports=n},{}],8:[function(t,e,i){function n(t,e){if("function"!=typeof e)throw"error while trying to register a Validator: argument must be a function";o[t]=e}function a(t,e){if("function"!=typeof o[t])throw t+" does not exists. use addValidatorBuilder to add a new validation rule";return o[t](r,s,e)}var s=t("./state-enum"),r=t("./validation-state"),o={};e.exports={addBuilder:n,build:a}},{"./state-enum":6,"./validation-state":7}],vivalid:[function(t,e,i){"use strict";var n=t("./input"),a=t("./input-group"),s=t("./validator-repo"),r=t("./html-interface"),o=t("./constants");e.exports={VERSION:o.VERSION,Input:n,InputGroup:a,validatorRepo:s,htmlInterface:r,_ERROR:o.ERROR}},{"./constants":1,"./html-interface":3,"./input":5,"./input-group":4,"./validator-repo":8}]},{},["vivalid"]))("vivalid")}); | ||
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.vivalid=t()}}(function(){return(require=function t(e,i,n){function a(r,o){if(!i[r]){if(!e[r]){var u="function"==typeof require&&require;if(!o&&u)return u(r,!0);if(s)return s(r,!0);var l=new Error("Cannot find module '"+r+"'");throw l.code="MODULE_NOT_FOUND",l}var d=i[r]={exports:{}};e[r][0].call(d.exports,function(t){var i=e[r][1][t];return a(i?i:t)},d,d.exports,t,e,i,n)}return i[r].exports}for(var s="function"==typeof require&&require,r=0;r<n.length;r++)a(n[r]);return a}({1:[function(t,e,i){e.exports={VERSION:"0.1.0",DEBUG:!1,validInputTagNames:["input","textarea","select"],keyStrokedInputTypes:["text","email","password","search","hidden"],ERROR:{mandatorySuccessFailure:"passing callbacks for onValidationSuccess and onValidationFailure is mandatory",errorInCallback:"callback failed: "}}},{}],2:[function(t,e,i){function n(t){return Array.prototype.slice.call(t)}function a(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}function s(t,e){e||(e=document);for(var i=[],n=0;n<t.length;n++)for(var a=e.getElementsByTagName(t[n]),s=0;s<a.length;s++)i.push(a[s]);return i}function r(t,e){for(;t&&t!==document;t=t.parentNode)if(h(t,e))return t;return!1}function o(t,e){return n(t.getElementsByTagName("*")).filter(function(t){return h(t,e)?!0:void 0})}function u(){var t=document.createElement("div");return t.setAttribute("data-a-b","c"),!(!t.dataset||"c"!==t.dataset.aB)}function l(t,e){return t.nodeType!==Node.ELEMENT_NODE?!1:t.getAttribute("data-"+p(e))}function d(t,e){return t.nodeType!==Node.ELEMENT_NODE?!1:t.dataset[e]}function h(t,e){return t.nodeType===Node.ELEMENT_NODE&&t.hasAttribute("data-"+p(e))}function p(t){return t.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()})}function c(t,e){return t.classList?t.classList.contains(e):!!t.className.match(new RegExp("(\\s|^)"+e+"(\\s|$)"))}function v(t,e){t.classList?t.classList.add(e):c(t,e)||(t.className+=" "+e)}function f(t,e){if(t.classList)t.classList.remove(e);else if(c(t,e)){var i=new RegExp("(\\s|^)"+e+"(\\s|$)");t.className=t.className.replace(i," ")}}var _=u();e.exports={getDataSet:_?d:l,hasDataSet:h,getElementsByTagNames:s,getClosestParentByAttribute:r,getChildrenByAttribute:o,ready:a,toArray:n,addClass:v,removeClass:f}},{}],3:[function(t,e,i){function n(t,e){if("function"!=typeof e)throw"error while trying to add a custom callback: argument must be a function";if(f[t])throw"error while trying to add a custom callback: "+t+" already exists";f[t]=e}function a(t){function e(){inputElems=p.getElementsByTagNames(v,t).filter(function(t){return p.hasDataSet(t,"vivalidTuples")});var e=u(t,inputElems);o(t,e)}p.ready(e)}function s(){function t(){function t(t,a){if(!t)throw"an input validation is missing a group, input id: "+a.id;t._groupId||(t._groupId=e++),i[t._groupId]||(i[t._groupId]=[],n[t._groupId]=t),i[t._groupId].push(a)}var e=1,i={},n={};p.getElementsByTagNames(v).filter(function(t){return p.hasDataSet(t,"vivalidTuples")}).forEach(function(e){t(p.getClosestParentByAttribute(e,"vivalidGroup"),e)});for(var a in i){var s=u(n[a],i[a]);o(n[a],s)}}p.ready(t)}function r(t){var e=_[t];e?e.reset():console.log("could not find group named "+t)}function o(t,e){groupName=p.getDataSet(t,"vivalidGroup"),_[groupName]=e}function u(t,e){var i,n,a,s,r=e.map(l),o=[null,null],u=[null,null];if(p.hasDataSet(t,"vivalidOnValidation")&&(o=JSON.parse(p.getDataSet(t,"vivalidOnValidation")),!Array.isArray(o)||2!==o.length))throw"data-vivalid-on-validation value should be an array of size 2";if(p.hasDataSet(t,"vivalidPendingUi")&&(u=JSON.parse(p.getDataSet(t,"vivalidPendingUi")),!Array.isArray(u)||2!==u.length))throw"data-vivalid-pending-ui value should be an array of size 2";return p.hasDataSet(t,"vivalidStatesChanged")&&(i=p.getDataSet(t,"vivalidStatesChanged")),p.hasDataSet(t,"vivalidPendingChanged")&&(n=p.getDataSet(t,"vivalidPendingChanged")),p.hasDataSet(t,"vivalidBeforeValidation")&&(a=p.getDataSet(t,"vivalidBeforeValidation")),p.hasDataSet(t,"vivalidAfterValidation")&&(s=p.getDataSet(t,"vivalidAfterValidation")),new h(r,p.getChildrenByAttribute(t,"vivalidSubmit"),f[o[0]],f[o[1]],f[u[0]],f[u[1]],i,n,f[a],f[s],p.getChildrenByAttribute(t,"vivalidReset"))}function l(t){var e,i=JSON.parse(p.getDataSet(t,"vivalidTuples"));p.hasDataSet(t,"vivalidResult")&&(e=p.getDataSet(t,"vivalidResult"));var n=p.hasDataSet(t,"vivalidBlurOnly");return new d(t,i,f[e],n)}var d=t("./input"),h=t("./input-group"),p=t("./dom-helpers"),c=t("./constants"),v=c.validInputTagNames,f={},_={};e.exports={addCallback:n,initAll:s,initGroup:a,resetGroup:r}},{"./constants":1,"./dom-helpers":2,"./input":6,"./input-group":4}],4:[function(t,e,i){function n(t,e,i,n,s,o,u,l,d,h,p){if(!i||!n)throw r.mandatorySuccessFailure;this._inputs=[],this._inputElems=[],this._submitElems=[],this._resetElems=[],this._onValidationSuccess=i,this._onValidationFailure=n,this._pendingUiStart=s,this._pendingUiStop=o,this._groupStatesChanged=u,this._onBeforeValidation=d,this._onAfterValidation=h,this._groupPendingChangedListeners=[],this._groupPendingChangedListeners.push(function(t){t||this._isPendingUiStartRun&&(this._pendingUiStop.call(this._pendingUiLastSubmitElem,this._inputElems,this._submitElems,this._resetElems),this._getOnSubmit.call(this).call(this._pendingUiLastSubmitElem),this._isPendingUiStartRun=!1,this._pendingUiLastSubmitElem={})}.bind(this)),l&&this._groupPendingChangedListeners.push(l),this._stateCounters={},this._stateCounters[a.invalid]=0,this._stateCounters[a.pending]=0,this._stateCounters[a.valid]=0,this._isPendingChangeTrueRun=!1,this._isPendingUiStartRun=!1,this._pendingUiLastSubmitElem={},this._inputs=t.map(function(t){return t.setGroup(this),t},this),this._inputElems=t.map(function(t){return t.getDomElement()}),this._stateCounters[a.valid]=t.length,this._submitElems=Array.prototype.slice.call(e),this._submitElems.forEach(function(t){t.addEventListener("click",this._getOnSubmit.call(this))},this),p&&(this._resetElems=Array.prototype.slice.call(p),this._resetElems.forEach(function(t){t.addEventListener("click",this.reset.bind(this))},this))}var a=(t("./input"),t("./state-enum")),s=t("./constants").DEBUG,r=t("./constants").ERROR;n.prototype=function(){function t(){return this._triggerInputsValidation(),0===this._stateCounters[a.invalid]&&0===this._stateCounters[a.pending]}function e(){return this._triggerInputsValidation(),0===this._stateCounters[a.invalid]&&this._stateCounters[a.pending]>0}function i(){var t=this;return function(e){e&&e.preventDefault(),t._isPending()?(t._pendingUiStart.call(this,t._inputElems,t._submitElems,t._resetElems),t._isPendingUiStartRun=!0,t._pendingUiLastSubmitElem=this):t._isValid()?t._onValidationSuccess.call(this):t._onValidationFailure.call(this,t._stateCounters[a.invalid],t._stateCounters[a.pending],t._stateCounters[a.valid]),s&&(console.debug("cuurent states:"),console.debug("invalid: "+t._stateCounters[a.invalid]),console.debug("pending: "+t._stateCounters[a.pending]),console.debug("valid: "+t._stateCounters[a.valid]))}}function n(){this._inputs.forEach(function(t){t.triggerValidation()})}function r(){return this._onBeforeValidation}function o(){return this._onAfterValidation}function u(t,e){t.stateEnum!==e.stateEnum&&(this._stateCounters[t.stateEnum]--,this._stateCounters[e.stateEnum]++)}function l(){this._groupStatesChanged&&this._groupStatesChanged(),this._groupPendingChangedListeners.forEach(function(t){!this._isPendingChangeTrueRun&&0===this._stateCounters[a.invalid]&&this._stateCounters[a.pending]>0?(t(!0),this._isPendingChangeTrueRun=!0):this._isPendingChangeTrueRun&&0===this._stateCounters[a.pending]&&(t(!1),this._isPendingChangeTrueRun=!1)},this)}function d(t){t&&t.preventDefault&&t.preventDefault(),this._inputs.forEach(function(t){t.reset()}),this._stateCounters[a.invalid]=0,this._stateCounters[a.pending]=0,this._stateCounters[a.valid]=this._inputs.length}return{updateGroupListeners:l,updateGroupStates:u,reset:d,getOnBeforeValidation:r,getOnAfterValidation:o,_isValid:t,_isPending:e,_getOnSubmit:i,_triggerInputsValidation:n}}(),e.exports=n},{"./constants":1,"./input":6,"./state-enum":7}],5:[function(t,e,i){function n(){this.isNoneChecked=!1,this.validationState=new a("",s.valid),this.validationCycle=0,this.isChanged=!1,this.activeEventType=""}var a=t("./validation-state"),s=t("./state-enum");e.exports=n},{"./state-enum":7,"./validation-state":8}],6:[function(t,e,i){function n(t,e,i,n){function s(){var t=[];return e.forEach(function(e){var i=e[0],n=e[1];t.push({name:i,run:a.build(i,n)})}),t}function r(t,e,i,n){function a(t){return t.nextElementSibling&&"vivalid-error"===t.nextElementSibling.className?t.nextElementSibling:void 0}function s(t,i){r=document.createElement("DIV"),r.className="vivalid-error",r.style.color="#ff0000";var n=document.createTextNode(e.message);r.appendChild(n),t.parentNode.insertBefore(r,t.nextElementSibling)}var r;if("input"===t.nodeName.toLowerCase()&&("radio"===t.type||"checkbox"===t.type)){var o=t.parentNode.querySelectorAll('input[name="'+t.name+'"]');t=o.item(o.length-1)}e.stateEnum===n.invalid?(r=a(t),r?r.textContent=e.message:s(t,e.message),t.style.borderStyle="solid",t.style.borderColor="#ff0000",l.addClass(t,"vivalid-error-input")):(r=a(t),r&&(r.parentNode.removeChild(r),t.style.borderStyle="",t.style.borderColor="",l.removeClass(t,"vivalid-error-input")))}if(-1===d.indexOf(t.nodeName.toLowerCase()))throw"only operates on the following html tags: "+d.toString();this._el=t,this._validatorsNameOptionsTuples=e,this._onInputValidationResult=i||r,this._isBlurOnly=n,this._validators=s(),this._inputState=new o,this._elName=t.nodeName.toLowerCase(),this._elType=t.type,this._isKeyed="textarea"===this._elName||h.indexOf(this._elType)>-1,this._runValidatorsBounded=this._runValidators.bind(this),this._initListeners()}var a=t("./validator-repo"),s=t("./state-enum"),r=t("./validation-state"),o=t("./input-state"),u=t("./constants"),l=t("./dom-helpers"),d=u.validInputTagNames,h=u.keyStrokedInputTypes;n.prototype=function(){function t(){if("input"===this._el.nodeName.toLowerCase()&&("radio"===this._el.type||"checkbox"===this._el.type)){var t=document.querySelector('input[name="'+this._el.name+'"]:checked');t?(this._el=t,this._inputState.isNoneChecked=!1):this._inputState.isNoneChecked=!0}}function e(){(0===this._inputState.validationCycle||this._inputState.isChanged)&&this._runValidatorsBounded()}function i(t){this._isKeyed&&t!==this._inputState.activeEventType&&(this._removeActiveEventType(),this._addEventType(t))}function n(t){this._group=t}function a(){return this._el}function u(){this._addChangeListener(),this._isKeyed?this._addEventType("blur"):this._addEventType("change")}function l(t,e){this._inputState.validationCycle++,this._reBindCheckedElement(),"function"==typeof this._group.getOnBeforeValidation()&&this._group.getOnBeforeValidation()(this._el);for(var i,n,a=e||0;a<this._validators.length;a++){var o=this._validators[a],u=this._inputState.isNoneChecked?"":this._el.value,l=o.run(u,this._getUpdateInputValidationResultAsync(o.name,a,this._inputState.validationCycle));if(l.stateEnum!==s.valid){i=l,n=o.name,this._isBlurOnly||this._changeEventType("input");break}}i=i||new r("",s.valid),this._updateInputValidationResult(i,n),this._inputState.isChanged=!1,"function"==typeof this._group.getOnAfterValidation()&&this._group.getOnAfterValidation()(this._el)}function d(){this._removeActiveEventType(),this._initListeners(),this._inputState=new o,this._onInputValidationResult(this._el,s.valid,"",s)}function h(){var t=this;if(this._isKeyed){if(this._isBlurOnly)return;this._el.addEventListener("input",function(){t._inputState.isChanged=!0},!1)}else if("input"!==this._elName||"radio"!==this._elType&&"checkbox"!==this._elType)"select"===this._elName&&this._el.addEventListener("change",function(){t._inputState.isChanged=!0},!1);else for(var e=document.querySelectorAll('input[name="'+this._el.name+'"]'),i=0;i<e.length;i++)e[i].addEventListener("change",function(){t._inputState.isChanged=!0},!1)}function p(t){if(this._isKeyed)this._el.addEventListener(t,this._runValidatorsBounded,!1);else if("input"!==this._elName||"radio"!==this._elType&&"checkbox"!==this._elType)"select"===this._elName&&this._el.addEventListener(t,this._runValidatorsBounded,!1);else for(var e=document.querySelectorAll('input[name="'+this._el.name+'"]'),i=0;i<e.length;i++)e[i].addEventListener(t,this._runValidatorsBounded,!1);this._inputState.activeEventType=t}function c(){this._el.removeEventListener(this._inputState.activeEventType,this._runValidatorsBounded,!1)}function v(t,e,i){var n=this;return function(a){i&&i!==n._inputState.validationCycle||(a.stateEnum===s.valid&&e+1<n._validators.length?n._runValidatorsBounded(null,e+1):n._updateInputValidationResult(a,t))}}function f(t,e){this._group.updateGroupStates(this._inputState.validationState,t),this._group.updateGroupListeners(),this._inputState.validationState=t,this._onInputValidationResult(this._el,t,e,s)}return{triggerValidation:e,setGroup:n,reset:d,getDomElement:a,_reBindCheckedElement:t,_runValidators:l,_changeEventType:i,_initListeners:u,_addChangeListener:h,_addEventType:p,_removeActiveEventType:c,_getUpdateInputValidationResultAsync:v,_updateInputValidationResult:f}}(),e.exports=n},{"./constants":1,"./dom-helpers":2,"./input-state":5,"./state-enum":7,"./validation-state":8,"./validator-repo":9}],7:[function(t,e,i){var n={invalid:1,pending:2,valid:3};e.exports=n},{}],8:[function(t,e,i){function n(t,e){this.message=t,this.stateEnum=e}e.exports=n},{}],9:[function(t,e,i){function n(t,e){if("function"!=typeof e)throw"error while trying to register a Validator: argument must be a function";o[t]=e}function a(t,e){if("function"!=typeof o[t])throw t+" does not exists. use addValidatorBuilder to add a new validation rule";return o[t](r,s,e)}var s=t("./state-enum"),r=t("./validation-state"),o={};e.exports={addBuilder:n,build:a}},{"./state-enum":7,"./validation-state":8}],vivalid:[function(t,e,i){"use strict";var n=t("./input"),a=t("./input-group"),s=t("./validator-repo"),r=t("./html-interface"),o=t("./constants");e.exports={VERSION:o.VERSION,Input:n,InputGroup:a,validatorRepo:s,htmlInterface:r,_ERROR:o.ERROR}},{"./constants":1,"./html-interface":3,"./input":6,"./input-group":4,"./validator-repo":9}]},{},["vivalid"]))("vivalid")}); |
@@ -17,3 +17,3 @@ 'use strict'; | ||
files: [ | ||
'dist/*.js', | ||
'dist/vivalid-bundle.js', | ||
'test/*.js', | ||
@@ -20,0 +20,0 @@ 'test/*.html' |
@@ -5,6 +5,7 @@ module.exports = { | ||
validInputTagNames: ['input', 'textarea', 'select'], | ||
keyStrokedInputTypes: ['text', 'email', 'password', 'search'], | ||
keyStrokedInputTypes: ['text', 'email', 'password', 'search', 'hidden'], | ||
ERROR: { | ||
mandatorySuccessFailure: 'passing callbacks for onValidationSuccess and onValidationFailure is mandatory' | ||
} | ||
mandatorySuccessFailure: 'passing callbacks for onValidationSuccess and onValidationFailure is mandatory', | ||
errorInCallback: 'callback failed: ' | ||
} | ||
}; |
var isDataSetSupport = testIsDataSetSupport(); | ||
function toArray(arrayLike){ | ||
function toArray(arrayLike) { | ||
return Array.prototype.slice.call(arrayLike); | ||
@@ -8,3 +8,3 @@ } | ||
function ready(fn) { | ||
if (document.readyState != 'loading'){ | ||
if (document.readyState != 'loading') { | ||
fn(); | ||
@@ -16,10 +16,10 @@ } else { | ||
function getElementsByTagNames(tagsArray,obj) { | ||
function getElementsByTagNames(tagsArray, obj) { | ||
if (!obj) obj = document; | ||
var results= []; | ||
var i=0; | ||
for (;i<tagsArray.length;i++) { | ||
var results = []; | ||
var i = 0; | ||
for (; i < tagsArray.length; i++) { | ||
var tags = obj.getElementsByTagName(tagsArray[i]); | ||
var j=0; | ||
for (;j<tags.length;j++) { | ||
var j = 0; | ||
for (; j < tags.length; j++) { | ||
results.push(tags[j]); | ||
@@ -34,5 +34,5 @@ } | ||
// Get closest match | ||
for ( ; elem && elem !== document; elem = elem.parentNode ) { | ||
for (; elem && elem !== document; elem = elem.parentNode) { | ||
if (hasDataSet(elem,attr)) { | ||
if (hasDataSet(elem, attr)) { | ||
return elem; | ||
@@ -47,5 +47,5 @@ } | ||
return toArray(elem.getElementsByTagName('*')) | ||
.filter(function(el){ | ||
if (hasDataSet(el,attr)) return true; | ||
}); | ||
.filter(function(el) { | ||
if (hasDataSet(el, attr)) return true; | ||
}); | ||
} | ||
@@ -61,3 +61,3 @@ | ||
function getDataSet_unsupported(node, attr) { | ||
if (node.nodeType !== Node.ELEMENT_NODE ) return false; | ||
if (node.nodeType !== Node.ELEMENT_NODE) return false; | ||
@@ -68,3 +68,3 @@ return node.getAttribute('data-' + toDashed(attr)); | ||
function getDataSet(node, attr) { | ||
if (node.nodeType !== Node.ELEMENT_NODE ) return false; | ||
if (node.nodeType !== Node.ELEMENT_NODE) return false; | ||
@@ -74,3 +74,3 @@ return node.dataset[attr]; | ||
function hasDataSet(node, attr){ | ||
function hasDataSet(node, attr) { | ||
return (node.nodeType === Node.ELEMENT_NODE && node.hasAttribute('data-' + toDashed(attr))); | ||
@@ -85,4 +85,26 @@ } | ||
// from http://jaketrent.com/post/addremove-classes-raw-javascript/ | ||
// used instead of classList because of lacking browser support | ||
function hasClass(el, className) { | ||
if (el.classList) | ||
return el.classList.contains(className) | ||
else | ||
return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')) | ||
} | ||
function addClass(el, className) { | ||
if (el.classList) | ||
el.classList.add(className) | ||
else if (!hasClass(el, className)) el.className += " " + className | ||
} | ||
function removeClass(el, className) { | ||
if (el.classList) | ||
el.classList.remove(className) | ||
else if (hasClass(el, className)) { | ||
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)') | ||
el.className = el.className.replace(reg, ' ') | ||
} | ||
} | ||
module.exports = { | ||
@@ -95,5 +117,5 @@ getDataSet: isDataSetSupport ? getDataSet : getDataSet_unsupported, | ||
ready: ready, | ||
toArray: toArray | ||
}; | ||
toArray: toArray, | ||
addClass: addClass, | ||
removeClass: removeClass | ||
}; |
@@ -9,2 +9,3 @@ var Input = require('./input'); | ||
var callbacks = {}; // custom user defined callbacks | ||
var groupNameToVivalidGroup = {}; | ||
@@ -19,3 +20,3 @@ /** | ||
*/ | ||
function addCallback(name,fn) { | ||
function addCallback(name, fn) { | ||
if (typeof fn !== 'function') throw 'error while trying to add a custom callback: argument must be a function'; | ||
@@ -33,13 +34,14 @@ if (callbacks[name]) throw 'error while trying to add a custom callback: ' + name + ' already exists'; | ||
*/ | ||
function initGroup(groupElem){ | ||
function initGroup(groupElem) { | ||
$$.ready(registerGroupFromDataAttribtues); | ||
function registerGroupFromDataAttribtues(){ | ||
function registerGroupFromDataAttribtues() { | ||
inputElems = $$.getElementsByTagNames(validInputTagNames,groupElem) | ||
.filter(function(el){ | ||
return $$.hasDataSet(el,'vivalidTuples'); | ||
}); | ||
inputElems = $$.getElementsByTagNames(validInputTagNames, groupElem) | ||
.filter(function(el) { | ||
return $$.hasDataSet(el, 'vivalidTuples'); | ||
}); | ||
createGroupFromDataAttribtues(groupElem,inputElems); | ||
var vivalidGroup = createGroupFromDataAttribtues(groupElem, inputElems); | ||
addToGroupNameDictionairy(groupElem, vivalidGroup); | ||
@@ -59,3 +61,3 @@ } | ||
function registerAllFromDataAttribtues(){ | ||
function registerAllFromDataAttribtues() { | ||
@@ -69,17 +71,17 @@ var _nextGroupId = 1; | ||
$$.getElementsByTagNames(validInputTagNames) | ||
.filter(function(el){ | ||
return $$.hasDataSet(el,'vivalidTuples'); | ||
}) | ||
.forEach(function(el){ | ||
addGroupInputs($$.getClosestParentByAttribute(el,'vivalidGroup'),el); | ||
}); | ||
.filter(function(el) { | ||
return $$.hasDataSet(el, 'vivalidTuples'); | ||
}) | ||
.forEach(function(el) { | ||
addGroupInputs($$.getClosestParentByAttribute(el, 'vivalidGroup'), el); | ||
}); | ||
for (var groupId in groupIdToInputs){ | ||
createGroupFromDataAttribtues(groupIdToGroup[groupId], groupIdToInputs[groupId]); | ||
for (var groupId in groupIdToInputs) { | ||
var vivalidGroup = createGroupFromDataAttribtues(groupIdToGroup[groupId], groupIdToInputs[groupId]); | ||
addToGroupNameDictionairy(groupIdToGroup[groupId], vivalidGroup); | ||
} | ||
function addGroupInputs(group, input) { | ||
function addGroupInputs(group,input){ | ||
if (!group){ | ||
if (!group) { | ||
throw 'an input validation is missing a group, input id: ' + input.id; | ||
@@ -90,3 +92,3 @@ } | ||
if(!groupIdToInputs[group._groupId]){ | ||
if (!groupIdToInputs[group._groupId]) { | ||
groupIdToInputs[group._groupId] = []; | ||
@@ -104,55 +106,94 @@ groupIdToGroup[group._groupId] = group; | ||
/** | ||
* Allow's an application to reset the validations state and event listeners of a group | ||
* @memberof! vivalid.htmlInterface | ||
* @function | ||
* @example vivalid.htmlInterface.resetGroup('contactGroup'); | ||
* @param {string} groupName | ||
*/ | ||
function resetGroup(groupName) { | ||
var vivalidGroup = groupNameToVivalidGroup[groupName]; | ||
if (vivalidGroup) { | ||
vivalidGroup.reset(); | ||
} else { | ||
console.log('could not find group named ' + groupName); | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function createGroupFromDataAttribtues(groupElem,inputElems){ | ||
function addToGroupNameDictionairy(groupElem, vivalidGroup) { | ||
groupName = $$.getDataSet(groupElem, 'vivalidGroup'); | ||
groupNameToVivalidGroup[groupName] = vivalidGroup; | ||
} | ||
function createGroupFromDataAttribtues(groupElem, inputElems) { | ||
var inputs = inputElems.map(vivalidInputFromElem); | ||
var onValidation = [null,null]; | ||
var pendingUi = [null,null]; | ||
var onValidation = [null, null]; | ||
var pendingUi = [null, null]; | ||
var groupStatesChanged; | ||
var groupPendingChanged; | ||
var onBeforeValidation; | ||
var onAfterValidation; | ||
if ($$.hasDataSet(groupElem,'vivalidOnValidation')){ | ||
onValidation = JSON.parse($$.getDataSet(groupElem,'vivalidOnValidation')); | ||
if ($$.hasDataSet(groupElem, 'vivalidOnValidation')) { | ||
onValidation = JSON.parse($$.getDataSet(groupElem, 'vivalidOnValidation')); | ||
if (!Array.isArray(onValidation) || onValidation.length !== 2) throw 'data-vivalid-on-validation value should be an array of size 2'; | ||
} | ||
if ($$.hasDataSet(groupElem,'vivalidPendingUi')){ | ||
pendingUi = JSON.parse($$.getDataSet(groupElem,'vivalidPendingUi')); | ||
if ($$.hasDataSet(groupElem, 'vivalidPendingUi')) { | ||
pendingUi = JSON.parse($$.getDataSet(groupElem, 'vivalidPendingUi')); | ||
if (!Array.isArray(pendingUi) || pendingUi.length !== 2) throw 'data-vivalid-pending-ui value should be an array of size 2'; | ||
} | ||
if ($$.hasDataSet(groupElem,'vivalidStatesChanged')){ | ||
groupStatesChanged = $$.getDataSet(groupElem,'vivalidStatesChanged'); | ||
if ($$.hasDataSet(groupElem, 'vivalidStatesChanged')) { | ||
groupStatesChanged = $$.getDataSet(groupElem, 'vivalidStatesChanged'); | ||
} | ||
if ($$.hasDataSet(groupElem,'vivalidPendingChanged')){ | ||
groupPendingChanged = $$.getDataSet(groupElem,'vivalidPendingChanged'); | ||
if ($$.hasDataSet(groupElem, 'vivalidPendingChanged')) { | ||
groupPendingChanged = $$.getDataSet(groupElem, 'vivalidPendingChanged'); | ||
} | ||
new InputGroup(inputs, | ||
$$.getChildrenByAttribute(groupElem,'vivalidSubmit'), | ||
callbacks[onValidation[0]], | ||
callbacks[onValidation[1]], | ||
callbacks[pendingUi[0]], | ||
callbacks[pendingUi[1]], | ||
groupStatesChanged, | ||
groupPendingChanged); | ||
if ($$.hasDataSet(groupElem, 'vivalidBeforeValidation')) { | ||
onBeforeValidation = $$.getDataSet(groupElem, 'vivalidBeforeValidation'); | ||
} | ||
if ($$.hasDataSet(groupElem, 'vivalidAfterValidation')) { | ||
onAfterValidation = $$.getDataSet(groupElem, 'vivalidAfterValidation'); | ||
} | ||
return new InputGroup(inputs, | ||
$$.getChildrenByAttribute(groupElem, 'vivalidSubmit'), | ||
callbacks[onValidation[0]], | ||
callbacks[onValidation[1]], | ||
callbacks[pendingUi[0]], | ||
callbacks[pendingUi[1]], | ||
groupStatesChanged, | ||
groupPendingChanged, | ||
callbacks[onBeforeValidation], | ||
callbacks[onAfterValidation], | ||
$$.getChildrenByAttribute(groupElem, 'vivalidReset') | ||
); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
function vivalidInputFromElem(el){ | ||
var tuplesArray = JSON.parse($$.getDataSet(el,'vivalidTuples')); | ||
function vivalidInputFromElem(el) { | ||
var tuplesArray = JSON.parse($$.getDataSet(el, 'vivalidTuples')); | ||
var onInputValidationResult; | ||
if ($$.hasDataSet(el,'vivalidResult')){ | ||
onInputValidationResult = $$.getDataSet(el,'vivalidResult'); | ||
if ($$.hasDataSet(el, 'vivalidResult')) { | ||
onInputValidationResult = $$.getDataSet(el, 'vivalidResult'); | ||
} | ||
return new Input(el,tuplesArray,callbacks[onInputValidationResult]); | ||
var isBlurOnly = $$.hasDataSet(el, 'vivalidBlurOnly'); | ||
return new Input(el, tuplesArray, callbacks[onInputValidationResult], isBlurOnly); | ||
} | ||
/** | ||
@@ -166,3 +207,4 @@ * The interface to use when using data attributes to define Inputs And Groups. | ||
initAll: initAll, | ||
initGroup: initGroup | ||
}; | ||
initGroup: initGroup, | ||
resetGroup: resetGroup | ||
}; |
@@ -18,126 +18,137 @@ var Input = require('./input'); | ||
* @param {function} [groupPendingChanged] | ||
* @param {function} [onBeforeValidation] Signature of {@link _internal.onBeforeValidation onBeforeValidation}. A function to be called before triggering any of the input's validators | ||
* @param {function} [onAfterValidation] Signature of {@link _internal.onAfterValidation onAfterValidation}. A function to be called after triggering all of the input's validators | ||
* @param {HTMLElement[]} [resetElems] an array of elements that should trigger the group's validation _reset. | ||
*/ | ||
function InputGroup(inputsArray,submitElems,onValidationSuccess,onValidationFailure,pendingUiStart,pendingUiStop, groupStatesChanged, groupPendingChanged){ | ||
function InputGroup(inputsArray, submitElems, onValidationSuccess, onValidationFailure, pendingUiStart, pendingUiStop, groupStatesChanged, groupPendingChanged, onBeforeValidation, onAfterValidation, resetElems) { | ||
if(!onValidationSuccess || !onValidationFailure) throw ERROR.mandatorySuccessFailure; | ||
if (!onValidationSuccess || !onValidationFailure) throw ERROR.mandatorySuccessFailure; | ||
this.inputs = []; | ||
this.inputElems = []; | ||
this.submitElems = []; | ||
this._inputs = []; | ||
this._inputElems = []; | ||
this._submitElems = []; | ||
this._resetElems = []; | ||
this._onValidationSuccess = onValidationSuccess; | ||
this._onValidationFailure = onValidationFailure; | ||
this._pendingUiStart = pendingUiStart; | ||
this._pendingUiStop = pendingUiStop; | ||
this._groupStatesChanged = groupStatesChanged; | ||
this._onBeforeValidation = onBeforeValidation; | ||
this._onAfterValidation = onAfterValidation; | ||
this.onValidationSuccess = onValidationSuccess; | ||
this.onValidationFailure = onValidationFailure; | ||
this.pendingUiStart = pendingUiStart; | ||
this.pendingUiStop = pendingUiStop; | ||
this.groupStatesChanged = groupStatesChanged; | ||
this._groupPendingChangedListeners = []; | ||
this._groupPendingChangedListeners.push( | ||
function(_isPending) { | ||
if (!_isPending) { | ||
if (this._isPendingUiStartRun) { | ||
this.groupPendingChangedListeners = []; | ||
this.groupPendingChangedListeners.push( | ||
function(isPending){ | ||
if(!isPending){ | ||
if(this.isPendingUiStartRun){ | ||
this._pendingUiStop.call(this._pendingUiLastSubmitElem, this._inputElems, this._submitElems, this._resetElems); | ||
this.pendingUiStop.call(this.pendingUiLastSubmitElem,this.inputElems,this.submitElems); | ||
this._getOnSubmit.call(this).call(this._pendingUiLastSubmitElem); | ||
this.getOnSubmit.call(this).call(this.pendingUiLastSubmitElem); | ||
this.isPendingUiStartRun = false; | ||
this.pendingUiLastSubmitElem = {}; | ||
this._isPendingUiStartRun = false; | ||
this._pendingUiLastSubmitElem = {}; | ||
} | ||
} | ||
}.bind(this) | ||
}.bind(this) | ||
); | ||
if (groupPendingChanged) | ||
this.groupPendingChangedListeners.push(groupPendingChanged); | ||
if (groupPendingChanged) | ||
this._groupPendingChangedListeners.push(groupPendingChanged); | ||
this.stateCounters = {}; | ||
this.stateCounters[stateEnum.invalid] = 0; | ||
this.stateCounters[stateEnum.pending] = 0; | ||
this.stateCounters[stateEnum.valid] = 0; | ||
this._stateCounters = {}; | ||
this._stateCounters[stateEnum.invalid] = 0; | ||
this._stateCounters[stateEnum.pending] = 0; | ||
this._stateCounters[stateEnum.valid] = 0; | ||
this.isPendingChangeTrueRun = false; | ||
this._isPendingChangeTrueRun = false; | ||
this.isPendingUiStartRun = false; | ||
this._isPendingUiStartRun = false; | ||
this.pendingUiLastSubmitElem = {}; | ||
this._pendingUiLastSubmitElem = {}; | ||
this.inputs = inputsArray.map(function(input){ | ||
this._inputs = inputsArray.map(function(input) { | ||
input.setGroup(this); | ||
return input; | ||
},this); | ||
}, this); | ||
this.inputElems = inputsArray | ||
.map(function(input){ | ||
return input.el; | ||
}); | ||
this._inputElems = inputsArray | ||
.map(function(input) { | ||
return input.getDomElement(); | ||
}); | ||
this._stateCounters[stateEnum.valid] = inputsArray.length; | ||
this.stateCounters[stateEnum.valid] = inputsArray.length; | ||
this._submitElems = Array.prototype.slice.call(submitElems); | ||
this.submitElems = Array.prototype.slice.call((submitElems)); | ||
this._submitElems.forEach(function(submit) { | ||
submit.addEventListener('click', this._getOnSubmit.call(this)); | ||
}, this); | ||
this.submitElems.forEach(function(submit){ | ||
submit.addEventListener('click',this.getOnSubmit.call(this)); | ||
},this); | ||
if (resetElems) { | ||
this._resetElems = Array.prototype.slice.call(resetElems); | ||
this._resetElems.forEach(function(submit) { | ||
submit.addEventListener('click', this.reset.bind(this)); | ||
}, this); | ||
} | ||
} | ||
InputGroup.prototype = (function(){ | ||
InputGroup.prototype = (function() { | ||
return { | ||
isValid: isValid, | ||
isPending: isPending, | ||
getOnSubmit: getOnSubmit, | ||
triggerInputsValidation: triggerInputsValidation, | ||
updateGroupListeners: updateGroupListeners, | ||
updateGroupStates: updateGroupStates, | ||
reset: reset, | ||
getOnBeforeValidation: getOnBeforeValidation, | ||
getOnAfterValidation: getOnAfterValidation, | ||
_isValid: _isValid, | ||
_isPending: _isPending, | ||
_getOnSubmit: _getOnSubmit, | ||
_triggerInputsValidation: _triggerInputsValidation | ||
}; | ||
function isValid(){ | ||
this.triggerInputsValidation(); | ||
function _isValid() { | ||
this._triggerInputsValidation(); | ||
return (this.stateCounters[stateEnum.invalid] === 0 && | ||
this.stateCounters[stateEnum.pending] === 0); | ||
return (this._stateCounters[stateEnum.invalid] === 0 && | ||
this._stateCounters[stateEnum.pending] === 0); | ||
} | ||
function isPending(){ | ||
this.triggerInputsValidation(); | ||
function _isPending() { | ||
this._triggerInputsValidation(); | ||
return (this.stateCounters[stateEnum.invalid] === 0 && | ||
this.stateCounters[stateEnum.pending] > 0); | ||
return (this._stateCounters[stateEnum.invalid] === 0 && | ||
this._stateCounters[stateEnum.pending] > 0); | ||
} | ||
function getOnSubmit() { | ||
function _getOnSubmit() { | ||
var self = this; | ||
return function (e) { | ||
if(e) e.preventDefault(); | ||
return function(e) { | ||
if (e) e.preventDefault(); | ||
if(self.isPending()){ | ||
self.pendingUiStart.call(this,self.inputElems,self.submitElems); | ||
self.isPendingUiStartRun = true; | ||
self.pendingUiLastSubmitElem = this; | ||
if (self._isPending()) { | ||
self._pendingUiStart.call(this, self._inputElems, self._submitElems, self._resetElems); | ||
self._isPendingUiStartRun = true; | ||
self._pendingUiLastSubmitElem = this; | ||
} else if (!self._isValid()) { | ||
self._onValidationFailure.call(this, | ||
self._stateCounters[stateEnum.invalid], | ||
self._stateCounters[stateEnum.pending], | ||
self._stateCounters[stateEnum.valid]); | ||
} else { | ||
self._onValidationSuccess.call(this); | ||
} | ||
else if(!self.isValid()){ | ||
self.onValidationFailure.call(this, | ||
self.stateCounters[stateEnum.invalid], | ||
self.stateCounters[stateEnum.pending], | ||
self.stateCounters[stateEnum.valid]); | ||
} | ||
else { | ||
self.onValidationSuccess.call(this); | ||
} | ||
if (DEBUG){ | ||
if (DEBUG) { | ||
console.debug('cuurent states:'); | ||
console.debug("invalid: " + self.stateCounters[stateEnum.invalid]); | ||
console.debug("pending: " + self.stateCounters[stateEnum.pending]); | ||
console.debug("valid: " + self.stateCounters[stateEnum.valid]); | ||
console.debug("invalid: " + self._stateCounters[stateEnum.invalid]); | ||
console.debug("pending: " + self._stateCounters[stateEnum.pending]); | ||
console.debug("valid: " + self._stateCounters[stateEnum.valid]); | ||
} | ||
@@ -149,4 +160,4 @@ | ||
function triggerInputsValidation(){ | ||
this.inputs.forEach(function(input){ | ||
function _triggerInputsValidation() { | ||
this._inputs.forEach(function(input) { | ||
input.triggerValidation(); | ||
@@ -156,29 +167,47 @@ }); | ||
function updateGroupStates(fromInputState, toInputState){ | ||
function getOnBeforeValidation(){ | ||
return this._onBeforeValidation; | ||
} | ||
function getOnAfterValidation(){ | ||
return this._onAfterValidation; | ||
} | ||
function updateGroupStates(fromInputState, toInputState) { | ||
if (fromInputState.stateEnum === toInputState.stateEnum) return; | ||
this.stateCounters[fromInputState.stateEnum]--; | ||
this.stateCounters[toInputState.stateEnum]++; | ||
this._stateCounters[fromInputState.stateEnum]--; | ||
this._stateCounters[toInputState.stateEnum]++; | ||
} | ||
function updateGroupListeners(){ | ||
if (this.groupStatesChanged) this.groupStatesChanged(); | ||
function updateGroupListeners() { | ||
if (this._groupStatesChanged) this._groupStatesChanged(); | ||
// run both internal and user groupPendingChange functions | ||
this.groupPendingChangedListeners.forEach(function(listener){ | ||
this._groupPendingChangedListeners.forEach(function(listener) { | ||
if (!this.isPendingChangeTrueRun && this.stateCounters[stateEnum.invalid] === 0 && this.stateCounters[stateEnum.pending] > 0) | ||
{ | ||
listener(true); | ||
this.isPendingChangeTrueRun = true; | ||
} | ||
else if (this.isPendingChangeTrueRun && this.stateCounters[stateEnum.pending] === 0) | ||
{ | ||
listener(false); | ||
this.isPendingChangeTrueRun = false; | ||
} | ||
},this); | ||
if (!this._isPendingChangeTrueRun && this._stateCounters[stateEnum.invalid] === 0 && this._stateCounters[stateEnum.pending] > 0) { | ||
listener(true); | ||
this._isPendingChangeTrueRun = true; | ||
} else if (this._isPendingChangeTrueRun && this._stateCounters[stateEnum.pending] === 0) { | ||
listener(false); | ||
this._isPendingChangeTrueRun = false; | ||
} | ||
}, this); | ||
} | ||
function reset(e) { | ||
if (e && e.preventDefault) e.preventDefault(); | ||
this._inputs.forEach(function(input) { | ||
input.reset(); | ||
}); | ||
this._stateCounters[stateEnum.invalid] = 0; | ||
this._stateCounters[stateEnum.pending] = 0; | ||
this._stateCounters[stateEnum.valid] = this._inputs.length; | ||
} | ||
})(); | ||
@@ -218,2 +247,3 @@ | ||
* @param {HTMLElement[]} submitElems the group's submit elements | ||
* @param {HTMLElement[]} resetElems the group's _reset elements | ||
*/ | ||
@@ -227,2 +257,17 @@ | ||
* @param {HTMLElement[]} submitElems the group's submit elements | ||
* @param {HTMLElement[]} resetElems the group's _reset elements | ||
*/ | ||
/** A function to be called before triggering any of the input's validators | ||
* @name onBeforeValidation | ||
* @function | ||
* @memberof! _internal | ||
* @param {HTMLElement} el the input's DOM object. | ||
*/ | ||
/** A function to be called after triggering all of the input's validators | ||
* @name onAfterValidation | ||
* @function | ||
* @memberof! _internal | ||
* @param {HTMLElement} el the input's DOM object. | ||
*/ |
300
lib/input.js
var validatorRepo = require('./validator-repo'); | ||
var stateEnum = require('./state-enum'); | ||
var ValidationState = require('./validation-state'); | ||
var InputState = require('./input-state'); | ||
var constants = require('./constants'); | ||
var $$ = require('./dom-helpers'); | ||
@@ -17,42 +19,33 @@ var validInputTagNames = constants.validInputTagNames; | ||
* @param {function} [onInputValidationResult] Signature of {@link _internal.onInputValidationResult onInputValidationResult}. A function to handle an input state or message change. If not passed, {@link _internal.defaultOnInputValidationResult defaultOnInputValidationResult} will be used. | ||
* @param {boolean} isBlurOnly if true, doesn't not trigger validation on 'input' or 'change' events. | ||
*/ | ||
function Input(el, validatorsNameOptionsTuples, onInputValidationResult){ | ||
function Input(el, validatorsNameOptionsTuples, onInputValidationResult, isBlurOnly) { | ||
if (validInputTagNames.indexOf(el.nodeName.toLowerCase()) === -1){ | ||
if (validInputTagNames.indexOf(el.nodeName.toLowerCase()) === -1) { | ||
throw 'only operates on the following html tags: ' + validInputTagNames.toString(); | ||
} | ||
this.group = {}; | ||
this._el = el; | ||
this._validatorsNameOptionsTuples = validatorsNameOptionsTuples; | ||
this._onInputValidationResult = onInputValidationResult || defaultOnInputValidationResult; | ||
this._isBlurOnly = isBlurOnly; | ||
this._validators = buildValidators(); | ||
this._inputState = new InputState(); | ||
this._elName = el.nodeName.toLowerCase(); | ||
this._elType = el.type; | ||
this._isKeyed = (this._elName === 'textarea' || keyStrokedInputTypes.indexOf(this._elType) > -1); | ||
this._runValidatorsBounded = this._runValidators.bind(this); | ||
this.el = el; | ||
this.validators = buildValidators(); | ||
this.onInputValidationResult = onInputValidationResult || defaultOnInputValidationResult; | ||
this.isNoneChecked = false; | ||
this._initListeners(); | ||
this.validationState = new ValidationState('', stateEnum.valid); | ||
this.validationCycle = 0; | ||
this.isChanged = false; | ||
this.elName = el.nodeName.toLowerCase(); | ||
this.elType = el.type; | ||
this.isKeyed = (this.elName === 'textarea' || keyStrokedInputTypes.indexOf(this.elType) > -1); | ||
this.activeEventType = ''; | ||
this._runValidatorsBounded = this.runValidators.bind(this); | ||
this.initListeners(); | ||
function buildValidators(){ | ||
function buildValidators() { | ||
var result = []; | ||
validatorsNameOptionsTuples.forEach(function(validatorsNameOptionsTuple){ | ||
validatorsNameOptionsTuples.forEach(function(validatorsNameOptionsTuple) { | ||
var validatorName = validatorsNameOptionsTuple[0]; | ||
var validatorOptions = validatorsNameOptionsTuple[1]; | ||
result.push( | ||
{ | ||
name: validatorName, | ||
run: validatorRepo.build(validatorName,validatorOptions) | ||
} | ||
); | ||
result.push({ | ||
name: validatorName, | ||
run: validatorRepo.build(validatorName, validatorOptions) | ||
}); | ||
@@ -64,3 +57,2 @@ }); | ||
/** The default {@link _internal.onInputValidationResult onInputValidationResult} used when {@link vivalid.Input} is initiated without a 3rd parameter | ||
@@ -71,3 +63,3 @@ * @name defaultOnInputValidationResult | ||
*/ | ||
function defaultOnInputValidationResult(el,validationsResult,validatorName,stateEnum) { | ||
function defaultOnInputValidationResult(el, validationsResult, validatorName, stateEnum) { | ||
@@ -77,29 +69,28 @@ var errorDiv; | ||
// for radio buttons and checkboxes: get the last element in group by name | ||
if ((el.nodeName.toLowerCase() === 'input' && (el.type === 'radio' || el.type === 'checkbox' ))){ | ||
if ((el.nodeName.toLowerCase() === 'input' && (el.type === 'radio' || el.type === 'checkbox'))) { | ||
var getAllByName = el.parentNode.querySelectorAll('input[name="'+el.name+'"]'); | ||
var getAllByName = el.parentNode.querySelectorAll('input[name="' + el.name + '"]'); | ||
el = getAllByName.item(getAllByName.length-1); | ||
el = getAllByName.item(getAllByName.length - 1); | ||
} | ||
if(validationsResult.stateEnum === stateEnum.invalid){ | ||
if (validationsResult.stateEnum === stateEnum.invalid) { | ||
errorDiv = getExistingErrorDiv(el); | ||
if(errorDiv) { | ||
if (errorDiv) { | ||
errorDiv.textContent = validationsResult.message; | ||
} else { | ||
appendNewErrorDiv(el, validationsResult.message); | ||
} | ||
else { | ||
appendNewErrorDiv(el,validationsResult.message); | ||
} | ||
el.style.borderStyle = "solid"; | ||
el.style.borderColor = "#ff0000"; | ||
} | ||
else { | ||
$$.addClass(el, "vivalid-error-input"); | ||
} else { | ||
errorDiv = getExistingErrorDiv(el); | ||
if(errorDiv) { | ||
if (errorDiv) { | ||
errorDiv.parentNode.removeChild(errorDiv); | ||
el.style.borderStyle = ""; | ||
el.style.borderColor = ""; | ||
$$.removeClass(el, "vivalid-error-input"); | ||
} | ||
@@ -109,4 +100,4 @@ } | ||
function getExistingErrorDiv(el) { | ||
if (el.nextSibling.className === "vivalid-error") { | ||
return el.nextSibling; | ||
if (el.nextElementSibling && el.nextElementSibling.className === "vivalid-error") { | ||
return el.nextElementSibling; | ||
} | ||
@@ -116,9 +107,9 @@ | ||
function appendNewErrorDiv(el,message) { | ||
errorDiv = document.createElement("DIV"); | ||
function appendNewErrorDiv(el, message) { | ||
errorDiv = document.createElement("DIV"); | ||
errorDiv.className = "vivalid-error"; | ||
errorDiv.style.color = "#ff0000"; | ||
var t = document.createTextNode(validationsResult.message); | ||
errorDiv.appendChild(t); | ||
el.parentNode.insertBefore(errorDiv, el.nextSibling); | ||
var t = document.createTextNode(validationsResult.message); | ||
errorDiv.appendChild(t); | ||
el.parentNode.insertBefore(errorDiv, el.nextElementSibling); | ||
} | ||
@@ -133,37 +124,36 @@ | ||
return { | ||
reBindCheckedElement: reBindCheckedElement, | ||
triggerValidation: triggerValidation, | ||
runValidators: runValidators, | ||
changeEventType: changeEventType, | ||
initListeners: initListeners, | ||
setGroup: setGroup, | ||
addChangeListener: addChangeListener, | ||
addEventType: addEventType, | ||
removeActiveEventType: removeActiveEventType, | ||
getUpdateInputValidationResultAsync: getUpdateInputValidationResultAsync, | ||
updateInputValidationResult: updateInputValidationResult | ||
reset: reset, | ||
getDomElement: getDomElement, | ||
_reBindCheckedElement: _reBindCheckedElement, | ||
_runValidators: _runValidators, | ||
_changeEventType: _changeEventType, | ||
_initListeners: _initListeners, | ||
_addChangeListener: _addChangeListener, | ||
_addEventType: _addEventType, | ||
_removeActiveEventType: _removeActiveEventType, | ||
_getUpdateInputValidationResultAsync: _getUpdateInputValidationResultAsync, | ||
_updateInputValidationResult: _updateInputValidationResult | ||
}; | ||
// public | ||
function _reBindCheckedElement() { | ||
function reBindCheckedElement(){ | ||
// reBind only radio and checkbox buttons | ||
if (!(this.el.nodeName.toLowerCase() === 'input' && (this.el.type === 'radio' || this.el.type === 'checkbox' ))){ | ||
if (!(this._el.nodeName.toLowerCase() === 'input' && (this._el.type === 'radio' || this._el.type === 'checkbox'))) { | ||
return; | ||
} | ||
var checkedElement = document.querySelector('input[name="'+this.el.name+'"]:checked'); | ||
if (checkedElement){ | ||
this.el = checkedElement; | ||
this.isNoneChecked = false; | ||
var checkedElement = document.querySelector('input[name="' + this._el.name + '"]:checked'); | ||
if (checkedElement) { | ||
this._el = checkedElement; | ||
this._inputState.isNoneChecked = false; | ||
} else { | ||
this._inputState.isNoneChecked = true; | ||
} | ||
else{ | ||
this.isNoneChecked = true; | ||
} | ||
} | ||
function triggerValidation(){ | ||
if (this.validationCycle === 0 || this.isChanged) { | ||
function triggerValidation() { | ||
if (this._inputState.validationCycle === 0 || this._inputState.isChanged) { | ||
this._runValidatorsBounded(); | ||
@@ -173,45 +163,53 @@ } | ||
function changeEventType(eventType) { | ||
if (!this.isKeyed) return; | ||
if (eventType === this.activeEventType) return; | ||
this.removeActiveEventType(); | ||
this.addEventType(eventType); | ||
function _changeEventType(eventType) { | ||
if (!this._isKeyed) return; | ||
if (eventType === this._inputState.activeEventType) return; | ||
this._removeActiveEventType(); | ||
this._addEventType(eventType); | ||
} | ||
function setGroup(value) { | ||
this.group = value; | ||
this._group = value; | ||
} | ||
function initListeners() { | ||
function getDomElement(){ | ||
return this._el; | ||
} | ||
this.addChangeListener(); | ||
if (this.isKeyed){ | ||
this.addEventType('blur'); | ||
function _initListeners() { | ||
this._addChangeListener(); | ||
if (this._isKeyed) { | ||
this._addEventType('blur'); | ||
} else { | ||
this._addEventType('change'); | ||
} | ||
else { | ||
this.addEventType('change'); | ||
} | ||
} | ||
function runValidators(event, fromIndex){ | ||
function _runValidators(event, fromIndex) { | ||
this.validationCycle++; | ||
this.reBindCheckedElement(); | ||
this._inputState.validationCycle++; | ||
this._reBindCheckedElement(); | ||
if (typeof this._group.getOnBeforeValidation() === 'function') { | ||
this._group.getOnBeforeValidation()(this._el); | ||
} | ||
var validationsResult, validatorName; | ||
var i = fromIndex || 0; | ||
for (; i < this.validators.length; i++){ | ||
var validator = this.validators[i]; | ||
var elementValue = this.isNoneChecked ? '' : this.el.value; | ||
for (; i < this._validators.length; i++) { | ||
var validator = this._validators[i]; | ||
var elementValue = this._inputState.isNoneChecked ? '' : this._el.value; | ||
// if async, then return a pending enum with empty message and call the callback with result once ready | ||
var validatorResult = validator.run(elementValue, this.getUpdateInputValidationResultAsync(validator.name, i, this.validationCycle)); | ||
if (validatorResult.stateEnum !== stateEnum.valid) | ||
{ | ||
validationsResult = validatorResult; | ||
validatorName = validator.name; | ||
this.changeEventType('input'); //TODO: call only once? | ||
break; | ||
var validatorResult = validator.run(elementValue, this._getUpdateInputValidationResultAsync(validator.name, i, this._inputState.validationCycle)); | ||
if (validatorResult.stateEnum !== stateEnum.valid) { | ||
validationsResult = validatorResult; | ||
validatorName = validator.name; | ||
if (!this._isBlurOnly) { | ||
this._changeEventType('input'); //TODO: call only once? | ||
} | ||
break; | ||
} | ||
@@ -221,64 +219,78 @@ } | ||
validationsResult = validationsResult || new ValidationState('', stateEnum.valid); | ||
this.updateInputValidationResult(validationsResult,validatorName); | ||
this._updateInputValidationResult(validationsResult, validatorName); | ||
// new... | ||
this.isChanged = false; // TODO: move to top of function | ||
this._inputState.isChanged = false; // TODO: move to top of function? | ||
if (typeof this._group.getOnAfterValidation() === 'function') { | ||
this._group.getOnAfterValidation()(this._el); | ||
} | ||
} | ||
// private | ||
function reset() { | ||
this._removeActiveEventType(); | ||
this._initListeners(); | ||
this._inputState = new InputState(); | ||
this._onInputValidationResult(this._el, stateEnum.valid, '', stateEnum); // called with valid state to clear any previous errors UI | ||
} | ||
function addChangeListener(){ | ||
if (this.isKeyed){ | ||
this.el.addEventListener('input', function () { this.isChanged = true;}, false); | ||
} | ||
function _addChangeListener() { | ||
else if (this.elName === 'input' && (this.elType === 'radio' || this.elType === 'checkbox')){ | ||
var self = this; | ||
var groupElements = document.querySelectorAll('input[name="'+this.el.name+'"]'); | ||
if (this._isKeyed) { | ||
if (this._isBlurOnly) { | ||
return; | ||
} else { | ||
this._el.addEventListener('input', function() { | ||
self._inputState.isChanged = true; | ||
}, false); | ||
} | ||
} else if (this._elName === 'input' && (this._elType === 'radio' || this._elType === 'checkbox')) { | ||
var i=0; | ||
for (; i <groupElements.length ; i++) { | ||
groupElements[i].addEventListener('change', function (){this.isChanged = true;}, false); | ||
var groupElements = document.querySelectorAll('input[name="' + this._el.name + '"]'); | ||
var i = 0; | ||
for (; i < groupElements.length; i++) { | ||
groupElements[i].addEventListener('change', function() { | ||
self._inputState.isChanged = true; | ||
}, false); | ||
} | ||
} else if (this._elName === 'select') { | ||
this._el.addEventListener('change', function() { | ||
self._inputState.isChanged = true; | ||
}, false); | ||
} | ||
else if(this.elName === 'select'){ | ||
this.el.addEventListener('change', function (){this.isChanged = true;}, false); | ||
} | ||
} | ||
function addEventType(eventType) { | ||
if (this.isKeyed){ | ||
this.el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
function _addEventType(eventType) { | ||
if (this._isKeyed) { | ||
this._el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} else if (this._elName === 'input' && (this._elType === 'radio' || this._elType === 'checkbox')) { | ||
else if (this.elName === 'input' && (this.elType === 'radio' || this.elType === 'checkbox')){ | ||
var groupElements = document.querySelectorAll('input[name="' + this._el.name + '"]'); | ||
var groupElements = document.querySelectorAll('input[name="'+this.el.name+'"]'); | ||
var i=0; | ||
for (; i <groupElements.length ; i++) { | ||
var i = 0; | ||
for (; i < groupElements.length; i++) { | ||
groupElements[i].addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
} else if (this._elName === 'select') { | ||
this._el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
else if(this.elName === 'select'){ | ||
this.el.addEventListener(eventType, this._runValidatorsBounded, false); | ||
} | ||
this.activeEventType = eventType; | ||
this._inputState.activeEventType = eventType; | ||
} | ||
function removeActiveEventType() { | ||
this.el.removeEventListener(this.activeEventType, this._runValidatorsBounded, false); | ||
function _removeActiveEventType() { | ||
this._el.removeEventListener(this._inputState.activeEventType, this._runValidatorsBounded, false); | ||
} | ||
function getUpdateInputValidationResultAsync(validatorName, validatorIndex, asyncValidationCycle) { | ||
function _getUpdateInputValidationResultAsync(validatorName, validatorIndex, asyncValidationCycle) { | ||
var self = this; | ||
return function(validatorResult){ | ||
return function(validatorResult) { | ||
// guard against updating async validations from old cycles | ||
if(asyncValidationCycle && asyncValidationCycle !== self.validationCycle){ | ||
if (asyncValidationCycle && asyncValidationCycle !== self._inputState.validationCycle) { | ||
return; | ||
@@ -288,9 +300,7 @@ } | ||
// if pending turned to be valid, and there are more validation to run, run them: | ||
if (validatorResult.stateEnum === stateEnum.valid && validatorIndex+1 < self.validators.length){ | ||
self._runValidatorsBounded(null,validatorIndex+1); | ||
if (validatorResult.stateEnum === stateEnum.valid && validatorIndex + 1 < self._validators.length) { | ||
self._runValidatorsBounded(null, validatorIndex + 1); | ||
} else { | ||
self._updateInputValidationResult(validatorResult, validatorName); | ||
} | ||
else { | ||
self.updateInputValidationResult(validatorResult,validatorName); | ||
} | ||
}; | ||
@@ -300,9 +310,9 @@ | ||
function updateInputValidationResult(validationsResult,validatorName) { | ||
function _updateInputValidationResult(validationsResult, validatorName) { | ||
this.group.updateGroupStates(this.validationState, validationsResult); // filter equal state at caller | ||
this.group.updateGroupListeners(); | ||
this._group.updateGroupStates(this._inputState.validationState, validationsResult); // filter equal state at caller | ||
this._group.updateGroupListeners(); | ||
this.validationState = validationsResult; | ||
this.onInputValidationResult(this.el,validationsResult,validatorName,stateEnum); | ||
this._inputState.validationState = validationsResult; | ||
this._onInputValidationResult(this._el, validationsResult, validatorName, stateEnum); | ||
@@ -309,0 +319,0 @@ } |
@@ -13,2 +13,2 @@ /** | ||
module.exports = stateEnum; | ||
module.exports = stateEnum; |
@@ -9,3 +9,3 @@ /** | ||
*/ | ||
function ValidationState(message, stateEnum){ | ||
function ValidationState(message, stateEnum) { | ||
this.message = message; | ||
@@ -19,2 +19,2 @@ this.stateEnum = stateEnum; | ||
* @namespace _internal | ||
*/ | ||
*/ |
@@ -14,5 +14,5 @@ var stateEnum = require('./state-enum'); | ||
*/ | ||
function addBuilder(name, fn){ | ||
function addBuilder(name, fn) { | ||
if (typeof fn !== 'function') throw 'error while trying to register a Validator: argument must be a function'; | ||
validatorBuildersRepository [name] = fn; | ||
validatorBuildersRepository[name] = fn; | ||
} | ||
@@ -28,3 +28,3 @@ | ||
*/ | ||
function build(validatorName,validatorOptions) { | ||
function build(validatorName, validatorOptions) { | ||
@@ -35,3 +35,3 @@ if (typeof validatorBuildersRepository[validatorName] !== 'function') { | ||
return validatorBuildersRepository[validatorName](ValidationState,stateEnum,validatorOptions); | ||
return validatorBuildersRepository[validatorName](ValidationState, stateEnum, validatorOptions); | ||
} | ||
@@ -47,2 +47,2 @@ | ||
build: build | ||
}; | ||
}; |
{ | ||
"name": "vivalid", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"description": "client side validations with async (client-server) support", | ||
@@ -28,2 +28,3 @@ "main": "./lib/vivalid.js", | ||
"karma-phantomjs-launcher": "^0.1.4", | ||
"karma-sinon": "^1.0.4", | ||
"mocha": "^2.1.0", | ||
@@ -30,0 +31,0 @@ "vinyl-buffer": "^1.0.0", |
184
README.md
@@ -13,11 +13,34 @@ # ViValid [![Build Status](https://travis-ci.org/pazams/vivalid.svg)](https://travis-ci.org/pazams/vivalid) | ||
Sync rules return with `stateEnum.invalid` or `stateEnum.valid` | ||
Asyc rules return with `stateEnum.pending`, and also call a callback with `stateEnum.invalid` or `stateEnum.valid` when ready. | ||
Async rules return with `stateEnum.pending`, and also call a callback with `stateEnum.invalid` or `stateEnum.valid` when ready. Callbacks from previous cycles get filtered out, which helps when AJAX responses are out of order from multiple parallel requests. | ||
Demo: https://embed.plnkr.co/daGXkk1RrdRxjFWhQgwX/ | ||
### Full UI control | ||
Either edit the UI through a CSS rule for `.vivalid-error` , or gain complete control by passing a [callback](http://pazams.github.io/vivalid/documentation/-_internal.html#..onInputValidationResult) that will be called with a DOM element, validation message, and validation state. | ||
### Data attributes interface | ||
Use this library with full javascript interface, or the data attributes html interface (with js to only define callbacks). | ||
Either edit the UI through a CSS rule for `.vivalid-error` and `.vivalid-error-input` classes , or gain complete control by passing a [callback](http://pazams.github.io/vivalid/documentation/-_internal.html#..onInputValidationResult) that will be called with a DOM element, validation message, and validation state. | ||
Demo: https://embed.plnkr.co/JsA852mcYTTUHPoUf3F1/ | ||
### JS api or data attributes interface | ||
Use this library with full [javascript api](http://pazams.github.io/vivalid/documentation/vivalid.html), or the data attributes html interface (with js to only define callbacks). | ||
Data attribute | defined on | notes | ||
-------------- | --------------| ----- | ||
data-vivalid-group | group | defines an input group. may be applied on (but not restricted to) `<form>` elements. the value may used with [htmlInterface api](http://www.pazams.com/vivalid/documentation/vivalid.htmlInterface.html) in resetGroup. | ||
data-vivalid-on-validation | group | references success and failure callbacks. | ||
data-vivalid-pending-ui | group | references start and stop pending ui callbacks. used only in groups which contain async validators. | ||
data-vivalid-after-validation | group | references before and after validation callbacks. Defined on group level, but gets called on each input individually. | ||
data-vivalid-tuples | input | an array of [validatorsNameOptionsTuple] (http://www.pazams.com/vivalid/documentation/-_internal.html#..validatorsNameOptionsTuple) in JSON format. | ||
data-vivalid-blur-only | input | marks an input to be evaluated on `blur` event only, as opposed to default way: first evaluated on `blur` event, and after one event, evaluate on `input` event. | ||
data-vivalid-submit | button | triggers the group validation. Taking further action such as submitting a form, should be defined on the group's validation success callback. `preventDefault()` is applied to the DOM event. | ||
data-vivalid-reset | button | resets the state of the validation. this does not reset the form/group or clear its contents. This functionality, through the js api, is useful for SPA's. `preventDefault()` is applied to the DOM event. | ||
### Separation of validator rules | ||
No validator rules are included. Write your own, or also choose to include common ones from https://github.com/pazams/vivalid-rules-core | ||
No validator rules are included. Choose to include common ones from https://github.com/pazams/vivalid-rules-core, or write your own. | ||
Demo: https://embed.plnkr.co/Q6bTpj7PhqbQTBUZt166/ | ||
### Support for radio buttons and checkboxes | ||
see [here](http://pazams.github.io/vivalid/documentation/vivalid.Input.html) | ||
Demo: https://embed.plnkr.co/xtxe1YfsmxRR9hacZ3sn/ | ||
@@ -28,4 +51,4 @@ | ||
### Manual: | ||
* save and include: https://raw.githubusercontent.com/pazams/vivalid/master/dist/vivalid-bundle.min.js | ||
* (optional) https://raw.githubusercontent.com/pazams/vivalid-rules-core/master/dist/vivalid-rules-core-bundle.min.js | ||
* save and include: https://cdn.rawgit.com/pazams/vivalid/0.2.0/dist/vivalid-bundle.min.js | ||
* (optional) https://cdn.rawgit.com/pazams/vivalid-rules-core/0.2.0/dist/vivalid-rules-core-bundle.min.js | ||
@@ -40,146 +63,3 @@ ### npm: | ||
## JS interface | ||
See js [documentation](http://pazams.github.io/vivalid/documentation/vivalid.html) | ||
## Data attributes html interface | ||
See js [documentation](http://pazams.github.io/vivalid/documentation/vivalid.html), plus a short example ([live here](http://pazams.github.io/vivalid/demos/1/)): | ||
**index.html** | ||
```html | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script src="https://rawgit.com/pazams/vivalid/master/dist/vivalid-bundle.js"></script> | ||
<script src="https://rawgit.com/pazams/vivalid-rules-core/master/dist/vivalid-rules-core-bundle.min.js"></script> | ||
<script src="script.js"></script> | ||
</head> | ||
<body> | ||
<!-- inline css styles to make the exmaple work as a standalone without a css file --> | ||
<h1>Html data attributes interface</h1> | ||
<form id="MainForm" data-vivalid-group data-vivalid-on-validation='["onValidationSuccess", "onValidationFailure"]' data-vivalid-pending-ui='["pendingUiStart", "pendingUiStop"]'> | ||
<div> | ||
<input type="text" placeholder="First Name" data-vivalid-tuples='[["required",{}],["betweenlength",{"min": 4, "max": 10}]]' /> | ||
</div> | ||
<div style="position: relative;"> | ||
<input type="text" placeholder="User Name (type 'bob' and press send)" | ||
data-vivalid-tuples='[["required",{}],["exisitingUserBob",{}]]' | ||
data-vivalid-result='onInputValidationResult' | ||
/> | ||
<div class="js-message" style="background-color: blue; color: white; display: none; position: absolute; z-index: 1; padding: 6px; left: 122px; top: 0;"> | ||
</div> | ||
</div> | ||
<div> | ||
<input id="SendButton" type="button" value="send" data-vivalid-submit /> | ||
</div> | ||
<div id="MessageLogs"> | ||
</div> | ||
</form> | ||
</body> | ||
</html> | ||
``` | ||
**script.js** | ||
```javascript | ||
var addValidatorBuilder = vivalid.validatorRepo.addBuilder; | ||
var addCallback = vivalid.htmlInterface.addCallback; | ||
var initAll = vivalid.htmlInterface.initAll; | ||
function messageLog(message){ | ||
var log = document.getElementById('MessageLogs'); | ||
log.innerHTML = log.innerHTML +'<br/>' + message; | ||
} | ||
addValidatorBuilder('exisitingUserBob', function(ValidationState, stateEnum, options) { | ||
return function(value, callback) { | ||
var msg = 'user bob exists'; | ||
setTimeout(dummyServiceCall, 3000); | ||
return new ValidationState('', stateEnum.pending); | ||
function dummyServiceCall() { | ||
if (value.indexOf('bob') !== -1) { | ||
callback(new ValidationState(msg, stateEnum.invalid)); | ||
} else { | ||
callback(new ValidationState('', stateEnum.valid)); | ||
} | ||
} | ||
}; | ||
}); | ||
addCallback('onValidationSuccess', function() { | ||
messageLog('HOORAY!!!! input group is valid and form will submit'); | ||
}); | ||
addCallback('onValidationFailure', function(invalid, pending, valid) { | ||
messageLog('input group is invalid!: ' + invalid + ' invalid, ' + pending + ' pending, and ' + valid + ' valid '); | ||
}); | ||
addCallback('pendingUiStart', function(inputElems, submitElems) { | ||
messageLog('pendingUiStart'); | ||
inputElems.forEach(function(input) { | ||
input.disabled = true; | ||
}); | ||
submitElems.forEach(function(submit) { | ||
submit.disabled = true; | ||
}); | ||
this.style.backgroundColor = 'green'; | ||
}); | ||
addCallback('pendingUiStop', function(inputElems, submitElems) { | ||
messageLog('pendingUiStop'); | ||
inputElems.forEach(function(input) { | ||
input.disabled = false; | ||
}); | ||
submitElems.forEach(function(submit) { | ||
submit.disabled = false; | ||
}); | ||
this.style.backgroundColor = 'blue'; | ||
}); | ||
// show casing a callback for custom UI | ||
addCallback('onInputValidationResult', function(el, validationsResult, validatorName, stateEnum) { | ||
var msgEl = el.parentNode.querySelector('.js-message'); | ||
var displayEl = msgEl; | ||
if (validationsResult.stateEnum === stateEnum.invalid) { | ||
displayEl.style.display = 'block'; | ||
msgEl.innerHTML = validationsResult.message; | ||
} else { | ||
displayEl.style.display = 'none'; | ||
msgEl.innerHTML = ''; | ||
} | ||
}); | ||
initAll(); | ||
``` | ||
## Contributers | ||
## Contributors | ||
read [this](https://github.com/pazams/vivalid/issues/1) before attempting to `gulp build` | ||
## More demos | ||
Coming soon |
@@ -5,3 +5,3 @@ 'use strict'; | ||
var Name,Email,SendButton,Form; | ||
var Name,Email,SendButton,ResetButton,Form; | ||
@@ -30,2 +30,3 @@ var clickEvent = new MouseEvent('click', { | ||
SendButton = document.getElementById('SendButton'+formNumber); | ||
ResetButton = document.getElementById('ResetButton'+formNumber); | ||
Form = document.getElementById('Form'+formNumber); | ||
@@ -36,3 +37,3 @@ } | ||
function isErrorDisplayed(el) { | ||
return (el.nextSibling.className === "vivalid-error"); | ||
return (el.nextElementSibling && el.nextElementSibling.className === "vivalid-error"); | ||
} | ||
@@ -44,2 +45,3 @@ | ||
var initGroup = vivalid.htmlInterface.initGroup; | ||
var resetGroup = vivalid.htmlInterface.resetGroup; | ||
var ERROR = vivalid._ERROR; | ||
@@ -49,3 +51,2 @@ | ||
addValidatorBuilder('required',function(ValidationState,stateEnum,options){ | ||
@@ -85,3 +86,26 @@ | ||
addValidatorBuilder('exisitingUserBob', function(ValidationState, stateEnum, options) { | ||
return function(value, callback) { | ||
var msg = 'user bob exists'; | ||
setTimeout(dummyServiceCall, 0); | ||
return new ValidationState('', stateEnum.pending); | ||
function dummyServiceCall() { | ||
if (value.indexOf('bob') !== -1) { | ||
callback(new ValidationState(msg, stateEnum.invalid)); | ||
} else { | ||
callback(new ValidationState('', stateEnum.valid)); | ||
} | ||
} | ||
}; | ||
}); | ||
addCallback('onValidationSuccess', function(){ | ||
@@ -91,16 +115,51 @@ }); | ||
addCallback('onValidationFailure', function(invalid,pending,valid){ | ||
if(typeof invalid !== 'number' || typeof pending !== 'number' || typeof valid !== 'number'){ | ||
throw ERROR+"onValidationFailure"; | ||
} | ||
}); | ||
addCallback('pendingUiStart', function(inputElems,submitElems){ | ||
addCallback('pendingUiStart', function(inputElems,submitElems,resetElems){ | ||
try{ | ||
inputElems.concat(submitElems).concat(resetElems).forEach(function(input) { | ||
input.disabled = true; | ||
}); | ||
this.disabled = true; | ||
} | ||
catch(e){ | ||
throw ERROR+"pendingUiStart"; | ||
} | ||
finally { | ||
// done(); | ||
} | ||
}); | ||
addCallback('pendingUiStop' ,function(inputElems,submitElems){ | ||
addCallback('pendingUiStop' ,function(inputElems,submitElems,resetElems){ | ||
try{ | ||
inputElems.concat(submitElems).concat(resetElems).forEach(function(input) { | ||
input.disabled = true; | ||
}); | ||
this.disabled = true; | ||
} | ||
catch(e){ | ||
throw ERROR+"pendingUiStop"; | ||
} | ||
finally { | ||
// done(); | ||
} | ||
}); | ||
addCallback('onBeforeValidation' ,function(input){ | ||
}); | ||
addCallback('onAfterValidation' ,function(input){ | ||
}); | ||
}); | ||
describe('Async', function() { | ||
describe('Sanity', function() { | ||
@@ -169,7 +228,60 @@ // inject the HTML fixture for the tests | ||
// TODO: the function returned from _getUpdateInputValidationResultAsync does not get called- since tests do not wait for done() | ||
describe('Async', function() { | ||
describe('Sync', function() { | ||
// inject the HTML fixture for the tests | ||
beforeEach(function() { | ||
// Why this line? See: https://github.com/billtrik/karma-fixture/issues/3 | ||
fixture.base = 'test'; | ||
fixture.load('integrationTest.fixture.html'); | ||
it('should not require passing pendingUi callbacks', function() { | ||
selectFormElements(6); | ||
initGroup(Form); | ||
}); | ||
// remove the html fixture from the DOM | ||
afterEach(function() { | ||
fixture.cleanup(); | ||
}); | ||
// UX/UI | ||
it('should call pendingUiStart and pendingUiStop, and executes them without errors', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Email.dispatchEvent(clickEvent); | ||
Email.value = "bob@email.com"; | ||
SendButton.click(); | ||
// relays on done() inside these functions to test no errors | ||
// TODO: use sinon... | ||
expect(true).to.be.ok; | ||
}); | ||
it('should call pendingUiStart and pendingUiStop, if pending resolved to be valid, and there are more validation to run, run them', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Email.dispatchEvent(clickEvent); | ||
Email.value = "john@email.com"; | ||
SendButton.click(); | ||
// relays on done() inside these functions to test no errors | ||
// TODO: use sinon... | ||
expect(true).to.be.ok; | ||
}); | ||
}); | ||
describe('optional & required callbacks', function() { | ||
it('should not require passing pendingUi or onBefore/onAfterValidation callbacks', function() { | ||
// Why this line? See: https://github.com/billtrik/karma-fixture/issues/3 | ||
@@ -203,7 +315,218 @@ fixture.base = 'test'; | ||
}); | ||
/* use sinon to test these callbacks */ | ||
/* | ||
describe('onBefore/onAfter Validation', function() { | ||
it('should call onBeforeValidation', function() { | ||
// Why this line? See: https://github.com/billtrik/karma-fixture/issues/3 | ||
fixture.base = 'test'; | ||
fixture.load('integrationTest.fixture.html'); | ||
selectFormElements(5); | ||
initGroup(Form); | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
// works, but improve to test the throws of initAll(); | ||
// instead of dummy test below | ||
expect(true).to.be.ok; | ||
fixture.cleanup(); | ||
}); | ||
it('should call onAfterValidation', function() { | ||
// Why this line? See: https://github.com/billtrik/karma-fixture/issues/3 | ||
fixture.base = 'test'; | ||
fixture.load('integrationTest.fixture.html'); | ||
selectFormElements(5); | ||
initGroup(Form); | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
// works, but improve to test the throws of initAll(); | ||
// instead of dummy test below | ||
expect(true).to.be.ok; | ||
fixture.cleanup(); | ||
}); | ||
}); | ||
*/ | ||
describe('Blur-only inputs', function() { | ||
// inject the HTML fixture for the tests | ||
beforeEach(function() { | ||
// Why this line? See: https://github.com/billtrik/karma-fixture/issues/3 | ||
fixture.base = 'test'; | ||
fixture.load('integrationTest.fixture.html'); | ||
selectFormElements(4); | ||
initGroup(Form); | ||
}); | ||
// remove the html fixture from the DOM | ||
afterEach(function() { | ||
fixture.cleanup(); | ||
}); | ||
// UX/UI | ||
it('Blur-only: should not display any errors before interacting with the inputs', function() { | ||
expect(!isErrorDisplayed(Name) && !isErrorDisplayed(Email)).to.be.ok; | ||
}); | ||
it('Blur-only: should display all invalid errors after submitting, even without interacting with the inputs', function() { | ||
SendButton.click(); | ||
expect(isErrorDisplayed(Name) && isErrorDisplayed(Email)).to.be.ok; | ||
}); | ||
it('Blur-only: should not display an error before first blur (focus out)', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
expect(!isErrorDisplayed(Name)).to.be.ok; | ||
}); | ||
it('Blur-only: should display an error after first blur (focus out)', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
expect(isErrorDisplayed(Name)).to.be.ok; | ||
}); | ||
it('Blur-only: should ***NOT*** respond to fixes as they are typed, after first blur (focus out)', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
Name.value = "John"; | ||
Name.dispatchEvent(inputEvent); | ||
expect(isErrorDisplayed(Name)).to.be.ok; | ||
}); | ||
}); | ||
describe('Group reset', function() { | ||
resetMode('groupName'); | ||
resetMode('button'); | ||
function resetMode(mode){ | ||
// inject the HTML fixture for the tests | ||
beforeEach(function() { | ||
// Why this line? See: https://github.com/billtrik/karma-fixture/issues/3 | ||
fixture.base = 'test'; | ||
fixture.load('integrationTest.fixture.html'); | ||
selectFormElements(1); | ||
initGroup(Form); | ||
interactAndReset(); | ||
}); | ||
// remove the html fixture from the DOM | ||
afterEach(function() { | ||
fixture.cleanup(); | ||
}); | ||
function interactAndReset(){ | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
Name.value = "John Doe2"; | ||
Name.dispatchEvent(inputEvent); | ||
switch(mode){ | ||
case "groupName": | ||
resetGroup('FirstGroup'); | ||
break; | ||
case "button": | ||
ResetButton.click(); | ||
break; | ||
} | ||
} | ||
// UX/UI | ||
it('after error displayed and reset- should not display any errors before interacting with the inputs', function() { | ||
expect(!isErrorDisplayed(Name) && !isErrorDisplayed(Email)).to.be.ok; | ||
}); | ||
it('after error displayed and reset- should display all invalid errors after submitting, even without interacting with the inputs', function() { | ||
SendButton.click(); | ||
expect(isErrorDisplayed(Name) && isErrorDisplayed(Email)).to.be.ok; | ||
}); | ||
it('after error displayed and reset- should not display an error before first blur (focus out)', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
expect(!isErrorDisplayed(Name)).to.be.ok; | ||
}); | ||
it('after error displayed and reset- should display an error after first blur (focus out)', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
expect(isErrorDisplayed(Name)).to.be.ok; | ||
}); | ||
it('after error displayed and reset- should respond to fixes as they are typed, after first blur (focus out)', function() { | ||
Name.dispatchEvent(clickEvent); | ||
Name.value = "John Doe"; | ||
Name.dispatchEvent(blurEvent); | ||
Name.value = "John"; | ||
Name.dispatchEvent(inputEvent); | ||
expect(!isErrorDisplayed(Name)).to.be.ok; | ||
}); | ||
} | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
117451
24
2133
22
63