var Input = require('./input');
var stateEnum = require('./state-enum');
var DEBUG = require('./constants').DEBUG;
var ERROR = require('./constants').ERROR;
/**
* creates a new InputGroup
* @memberof! vivalid
* @class
* @param {vivalid.Input[]} inputsArray <b> the group's state is evalutated by: <br/> </b> <b>invalid:</b> if at least 1 inputs in inputsArray is invalid <br/> <b>pending:</b> if all inputs in inputsArray are valid/pending and at least 1 is pending <br/> <b>valid:</b> if all inputs in inputsArray are valid
* @param {HTMLElement[]} submitElems an array of elements that should trigger the group's validation. The elements click event will either immediately trigger onValidationSuccess / onValidationFailure, or go through pendingUiStart and pendingUiStop if the group is in 'pending' mode.
* @param {function} onValidationSuccess signature of {@link _internal.onValidationSuccess onValidationSuccess}. A function called when a submitElem is clicked and group state is valid or as a callback after it reaches a valid state from a pending state. Use this function to proceed with the application flow.
* @param {function} onValidationFailure signature of {@link _internal.onValidationFailure onValidationFailure}. A function called when a submitElem is clicked and group state is invalid or as a callback after it reaches an invalid state from a pending state. Use this function to show message about the form state for the user.
* @param {function} [pendingUiStart] signature of {@link _internal.pendingUiStart pendingUiStart}. highly recommneded when using asyc client-server validations. A function called when a submitElem is clicked and group state is pending. Use this function to show a loader or disable inputs while the group waits for the pending validations to complete.
* @param {function} [pendingUiStop] signature of {@link _internal.pendingUiStop pendingUiStop}. highly recommneded when using asyc client-server validations. A function called when leaving a group pending state after a submitElem is clicked. Use this function to undo the UX effects taken inside pendingUiStart.
* @param {function} [groupStatesChanged]
* @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, onBeforeValidation, onAfterValidation, resetElems) {
if (!onValidationSuccess || !onValidationFailure) throw ERROR.mandatorySuccessFailure;
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._groupPendingChangedListeners = [];
this._groupPendingChangedListeners.push(
function(_isPending) {
if (!_isPending) {
if (this._isPendingUiStartRun) {
this._pendingUiStop.call(this._pendingUiLastSubmitElem, this._inputElems, this._submitElems, this._resetElems);
this._getOnSubmit.call(this).call(this._pendingUiLastSubmitElem);
this._isPendingUiStartRun = false;
this._pendingUiLastSubmitElem = {};
}
}
}.bind(this)
);
if (groupPendingChanged)
this._groupPendingChangedListeners.push(groupPendingChanged);
this._stateCounters = {};
this._stateCounters[stateEnum.invalid] = 0;
this._stateCounters[stateEnum.pending] = 0;
this._stateCounters[stateEnum.valid] = 0;
this._isPendingChangeTrueRun = false;
this._isPendingUiStartRun = false;
this._pendingUiLastSubmitElem = {};
this._inputs = inputsArray.map(function(input) {
input.setGroup(this);
return input;
}, this);
this._inputElems = inputsArray
.map(function(input) {
return input.getDomElement();
});
this._stateCounters[stateEnum.valid] = inputsArray.length;
this._submitElems = Array.prototype.slice.call(submitElems);
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() {
return {
updateGroupListeners: updateGroupListeners,
updateGroupStates: updateGroupStates,
reset: reset,
getOnBeforeValidation: getOnBeforeValidation,
getOnAfterValidation: getOnAfterValidation,
_isValid: _isValid,
_isPending: _isPending,
_getOnSubmit: _getOnSubmit,
_triggerInputsValidation: _triggerInputsValidation
};
function _isValid() {
this._triggerInputsValidation();
return (this._stateCounters[stateEnum.invalid] === 0 &&
this._stateCounters[stateEnum.pending] === 0);
}
function _isPending() {
this._triggerInputsValidation();
return (this._stateCounters[stateEnum.invalid] === 0 &&
this._stateCounters[stateEnum.pending] > 0);
}
function _getOnSubmit() {
var self = this;
return function(e) {
if (e) e.preventDefault();
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);
}
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]);
}
}
}
function _triggerInputsValidation() {
this._inputs.forEach(function(input) {
input.triggerValidation();
});
}
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]++;
}
function updateGroupListeners() {
if (this._groupStatesChanged) this._groupStatesChanged();
// run both internal and user groupPendingChange functions
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);
}
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;
}
})();
module.exports = InputGroup;
/*
*
* @param {vivalid.Input[]} onValidationSuccess
* @param {vivalid.Input[]} onValidationFailure
* @param {vivalid.Input[]} pendingUiStart
* @param {vivalid.Input[]} pendingUiStop
*
*/
/** <b> IMPORTANT - the 'this' context inside: {HTMLElement} of the original submitElem that triggered the validation </b>
* @name onValidationSuccess
* @function
* @memberof! _internal
*/
/** <b> IMPORTANT - the 'this' context inside: {HTMLElement} of the original submitElem that triggered the validation </b>
* @name onValidationFailure
* @function
* @memberof! _internal
* @param {number} invalid number of invalid inputs
* @param {number} pending number of pending inputs
* @param {number} valid number of valid inputs
*/
/** <b> IMPORTANT - the 'this' context inside: {HTMLElement} of the original submitElem that triggered the validation </b>
* @name pendingUiStart
* @function
* @memberof! _internal
* @param {HTMLElement[]} inputElems the group's input elements
* @param {HTMLElement[]} submitElems the group's submit elements
* @param {HTMLElement[]} resetElems the group's _reset elements
*/
/** <b> IMPORTANT - the 'this' context inside: {HTMLElement} of the original submitElem that triggered the validation </b>
* @name pendingUiStop
* @function
* @memberof! _internal
* @param {HTMLElement[]} inputElems the group's input elements
* @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.
*/