This repository has been archived on 2018-10-12. You can view files and clone it, but cannot push or open issues or pull requests.
node-task/public/js/ink.formvalidator.js

650 lines
23 KiB
JavaScript

/**
* Form Validation
* @module Ink.UI.FormValidator_1
* @version 1
**/
Ink.createModule('Ink.UI.FormValidator', '1', ['Ink.Dom.Element_1', 'Ink.Dom.Css_1','Ink.Util.Validator_1','Ink.Dom.Selector_1'], function( InkElement, Css, InkValidator , Selector) {
'use strict';
function elementsWithSameName(elm) {
if (!elm.name) { return []; }
if (!elm.form) {
return Selector.select('name="' + elm.name + '"');
}
var ret = elm.form[elm.name];
if(typeof(ret.length) === 'undefined') {
ret = [ret];
}
return ret;
}
/**
* @namespace Ink.UI.FormValidator
* @version 1
*/
var FormValidator = {
/**
* Specifies the version of the component
*
* @property version
* @type {String}
* @readOnly
* @public
*/
version: '1',
/**
* Available flags to use in the validation process.
* The keys are the 'rules', and their values are objects with the key 'msg', determining
* what is the error message.
*
* @property _flagMap
* @type {Object}
* @readOnly
* @private
*/
_flagMap: {
//'ink-fv-required': {msg: 'Campo obrigatório'},
'ink-fv-required': {msg: 'Required field'},
//'ink-fv-email': {msg: 'E-mail inválido'},
'ink-fv-email': {msg: 'Invalid e-mail address'},
//'ink-fv-url': {msg: 'URL inválido'},
'ink-fv-url': {msg: 'Invalid URL'},
//'ink-fv-number': {msg: 'Número inválido'},
'ink-fv-number': {msg: 'Invalid number'},
//'ink-fv-phone_pt': {msg: 'Número de telefone inválido'},
'ink-fv-phone_pt': {msg: 'Invalid phone number'},
//'ink-fv-phone_cv': {msg: 'Número de telefone inválido'},
'ink-fv-phone_cv': {msg: 'Invalid phone number'},
//'ink-fv-phone_mz': {msg: 'Número de telefone inválido'},
'ink-fv-phone_mz': {msg: 'Invalid phone number'},
//'ink-fv-phone_ao': {msg: 'Número de telefone inválido'},
'ink-fv-phone_ao': {msg: 'Invalid phone number'},
//'ink-fv-date': {msg: 'Data inválida'},
'ink-fv-date': {msg: 'Invalid date'},
//'ink-fv-confirm': {msg: 'Confirmação inválida'},
'ink-fv-confirm': {msg: 'Confirmation does not match'},
'ink-fv-custom': {msg: ''}
},
/**
* This property holds all form elements for later validation
*
* @property elements
* @type {Object}
* @public
*/
elements: {},
/**
* This property holds the objects needed to cross-check for the 'confirm' rule
*
* @property confirmElms
* @type {Object}
* @public
*/
confirmElms: {},
/**
* This property holds the previous elements in the confirmElms property, but with a
* true/false specifying if it has the class ink-fv-confirm.
*
* @property hasConfirm
* @type {Object}
*/
hasConfirm: {},
/**
* Defined class name to use in error messages label
*
* @property _errorClassName
* @type {String}
* @readOnly
* @private
*/
_errorClassName: 'tip error',
/**
* @property _errorValidationClassName
* @type {String}
* @readOnly
* @private
*/
_errorValidationClassName: 'validaton',
/**
* @property _errorTypeWarningClassName
* @type {String}
* @readOnly
* @private
*/
_errorTypeWarningClassName: 'warning',
/**
* @property _errorTypeErrorClassName
* @type {String}
* @readOnly
* @private
*/
_errorTypeErrorClassName: 'error',
/**
* Checks if a form is valid
*
* @method validate
* @param {DOMElement|String} elm DOM form element or form id
* @param {Object} options Configuration options
* @param {Function} [options.onSuccess] Callback to run when form is valid
* @param {Function} [options.onError] Callback to run when form is not valid
* @param {Array} [options.customFlag] Custom flags to use to validate form fields
* @public
* @return {Boolean} Whether the form is deemed valid or not.
*
* @sample Ink_UI_FormValidator_1.html
*/
validate: function(elm, options) {
this._free();
options = Ink.extendObj({
onSuccess: false,
onError: false,
customFlag: false,
confirmGroup: []
}, options || {});
if(typeof(elm) === 'string') {
elm = document.getElementById(elm);
}
if(elm === null){
return false;
}
this.element = elm;
if(typeof(this.element.id) === 'undefined' || this.element.id === null || this.element.id === '') {
// generate a random ID
// TODO ugly and potentially problematic, and you know Murphy's law.
this.element.id = 'ink-fv_randomid_'+(Math.round(Math.random() * 99999));
}
this.custom = options.customFlag;
this.confirmGroup = options.confirmGroup;
var fail = this._validateElements();
if(fail.length > 0) {
if(options.onError) {
options.onError(fail);
} else {
this._showError(elm, fail);
}
return false;
} else {
if(!options.onError) {
this._clearError(elm);
}
this._clearCache();
if(options.onSuccess) {
options.onSuccess();
}
return true;
}
},
/**
* Resets previously generated validation errors
*
* @method reset
* @public
*/
reset: function()
{
this._clearError();
this._clearCache();
},
/**
* Cleans the object
*
* @method _free
* @private
*/
_free: function()
{
this.element = null;
//this.elements = [];
this.custom = false;
this.confirmGroup = false;
},
/**
* Cleans the properties responsible for caching
*
* @method _clearCache
* @private
*/
_clearCache: function()
{
this.element = null;
this.elements = [];
this.custom = false;
this.confirmGroup = false;
},
/**
* Gets the form elements and stores them in the caching properties
*
* @method _getElements
* @private
*/
_getElements: function()
{
//this.elements = [];
// if(typeof(this.elements[this.element.id]) !== 'undefined') {
// return;
// }
var elements = this.elements[this.element.id] = [];
this.confirmElms[this.element.id] = [];
//console.log(this.element);
//console.log(this.element.elements);
var formElms = Selector.select(':input', this.element);
var curElm = false;
for(var i=0, totalElm = formElms.length; i < totalElm; i++) {
curElm = formElms[i];
var type = (curElm.getAttribute('type') + '').toLowerCase();
if (type === 'radio' || type === 'checkbox') {
if(elements.length === 0 ||
(
curElm.getAttribute('type') !== elements[elements.length - 1].getAttribute('type') &&
curElm.getAttribute('name') !== elements[elements.length - 1].getAttribute('name')
)) {
for(var flag in this._flagMap) {
if(Css.hasClassName(curElm, flag)) {
elements.push(curElm);
break;
}
}
}
} else {
for(var flag2 in this._flagMap) {
if(Css.hasClassName(curElm, flag2) && flag2 !== 'ink-fv-confirm') {
/*if(flag2 == 'ink-fv-confirm') {
this.confirmElms[this.element.id].push(curElm);
this.hasConfirm[this.element.id] = true;
}*/
elements.push(curElm);
break;
}
}
if(Css.hasClassName(curElm, 'ink-fv-confirm')) {
this.confirmElms[this.element.id].push(curElm);
this.hasConfirm[this.element.id] = true;
}
}
}
},
/**
* Runs the validation for each element
*
* @method _validateElements
* @private
*/
_validateElements: function() {
var oGroups;
this._getElements();
if(this.hasConfirm[this.element.id] === true) {
oGroups = this._makeConfirmGroups();
}
var errors = [];
var curElm = false;
var customErrors = false;
var inArray;
for(var i=0, totalElm = this.elements[this.element.id].length; i < totalElm; i++) {
inArray = false;
curElm = this.elements[this.element.id][i];
if(!curElm.disabled) {
for(var flag in this._flagMap) {
if(Css.hasClassName(curElm, flag)) {
if(flag !== 'ink-fv-custom' && flag !== 'ink-fv-confirm') {
if(!this._isValid(curElm, flag)) {
if(!inArray) {
errors.push({elm: curElm, errors:[flag]});
inArray = true;
} else {
errors[(errors.length - 1)].errors.push(flag);
}
}
} else if(flag !== 'ink-fv-confirm'){
customErrors = this._isCustomValid(curElm);
if(customErrors.length > 0) {
errors.push({elm: curElm, errors:[flag], custom: customErrors});
}
} else if(flag === 'ink-fv-confirm'){
continue;
}
}
}
}
}
errors = this._validateConfirmGroups(oGroups, errors);
//console.log(InkDumper.returnDump(errors));
return errors;
},
/**
* Runs the 'confirm' validation for each group of elements
*
* @method _validateConfirmGroups
* @param {Array} oGroups Array/Object that contains the group of confirm objects
* @param {Array} errors Array that will store the errors
* @private
* @return {Array} Array of errors that was passed as 2nd parameter (either changed, or not, depending if errors were found).
*/
_validateConfirmGroups: function(oGroups, errors) {
//console.log(oGroups);
var curGroup = false;
for(var i in oGroups) if (oGroups.hasOwnProperty(i)) {
curGroup = oGroups[i];
if(curGroup.length === 2) {
if(curGroup[0].value !== curGroup[1].value) {
errors.push({elm:curGroup[1], errors:['ink-fv-confirm']});
}
}
}
return errors;
},
/**
* Creates the groups of 'confirm' objects
*
* @method _makeConfirmGroups
* @private
* @return {Array|Boolean} Returns the array of confirm elements or false on error.
*/
_makeConfirmGroups: function()
{
var oGroups;
if(this.confirmGroup && this.confirmGroup.length > 0) {
oGroups = {};
var curElm = false;
var curGroup = false;
//this.confirmElms[this.element.id];
for(var i=0, total=this.confirmElms[this.element.id].length; i < total; i++) {
curElm = this.confirmElms[this.element.id][i];
for(var j=0, totalG=this.confirmGroup.length; j < totalG; j++) {
curGroup = this.confirmGroup[j];
if(Css.hasClassName(curElm, curGroup)) {
if(typeof(oGroups[curGroup]) === 'undefined') {
oGroups[curGroup] = [curElm];
} else {
oGroups[curGroup].push(curElm);
}
}
}
}
return oGroups;
} else {
if(this.confirmElms[this.element.id].length === 2) {
oGroups = {
"ink-fv-confirm": [
this.confirmElms[this.element.id][0],
this.confirmElms[this.element.id][1]
]
};
}
return oGroups;
}
return false;
},
/**
* Validates an element with a custom validation
*
* @method _isCustomValid
* @param {DOMElemenmt} elm Element to be validated
* @private
* @return {Array} Array of errors. If no errors are found, results in an empty array.
*/
_isCustomValid: function(elm)
{
var customErrors = [];
var curFlag = false;
for(var i=0, tCustom = this.custom.length; i < tCustom; i++) {
curFlag = this.custom[i];
if(Css.hasClassName(elm, curFlag.flag)) {
if(!curFlag.callback(elm, curFlag.msg)) {
customErrors.push({flag: curFlag.flag, msg: curFlag.msg});
}
}
}
return customErrors;
},
/**
* Runs the normal validation functions for a specific element
*
* @method _isValid
* @param {DOMElement} elm DOMElement that will be validated
* @param {String} fieldType Rule to be validated. This must be one of the keys present in the _flagMap property.
* @private
* @return {Boolean} The result of the validation.
*/
_isValid: function(elm, fieldType) {
var nodeName = elm.nodeName.toLowerCase();
var inputType = (elm.getAttribute('type') || '').toLowerCase();
var value = this._trim(elm.value);
// When we're analyzing emails, telephones, etc, and the field is
// empty, we check if it is required. If not required, it's valid.
if (fieldType !== 'ink-fv-required' &&
inputType !== 'checkbox' && inputType !== 'radio' &&
value === '') {
return !Css.hasClassName(elm, 'ink-fv-required');
}
switch(fieldType) {
case 'ink-fv-required':
if(nodeName === 'select') {
if(elm.selectedIndex > 0) {
return true;
} else {
return false;
}
}
if(inputType !== 'checkbox' && inputType !== 'radio' &&
value !== '') {
return true; // A input type=text,email,etc.
} else if(inputType === 'checkbox' || inputType === 'radio') {
var aFormRadios = elementsWithSameName(elm);
var isChecked = false;
// check if any input of the radio is checked
for(var i=0, totalRadio = aFormRadios.length; i < totalRadio; i++) {
if(aFormRadios[i].checked === true) {
isChecked = true;
break;
}
}
return isChecked;
}
return false;
case 'ink-fv-email':
return InkValidator.mail(elm.value);
case 'ink-fv-url':
return InkValidator.url(elm.value);
case 'ink-fv-number':
return !isNaN(Number(elm.value)) && isFinite(Number(elm.value));
case 'ink-fv-phone_pt':
return InkValidator.isPTPhone(elm.value);
case 'ink-fv-phone_cv':
return InkValidator.isCVPhone(elm.value);
case 'ink-fv-phone_ao':
return InkValidator.isAOPhone(elm.value);
case 'ink-fv-phone_mz':
return InkValidator.isMZPhone(elm.value);
case 'ink-fv-date':
var Element = Ink.getModule('Ink.Dom.Element',1);
var dataset = Element.data( elm );
var validFormat = 'yyyy-mm-dd';
if( Css.hasClassName(elm, 'ink-datepicker') && ('format' in dataset) ){
validFormat = dataset.format;
} else if( ('validFormat' in dataset) ){
validFormat = dataset.validFormat;
}
if( !(validFormat in InkValidator._dateParsers ) ){
var validValues = [];
for( var val in InkValidator._dateParsers ){
if (InkValidator._dateParsers.hasOwnProperty(val)) {
validValues.push(val);
}
}
throw new Error(
'The attribute data-valid-format must be one of ' +
'the following values: ' + validValues.join(', '));
}
return InkValidator.isDate( validFormat, elm.value );
case 'ink-fv-custom':
break;
}
return false;
},
/**
* Makes the necessary changes to the markup to show the errors of a given element
*
* @method _showError
* @param {DOMElement} formElm The form element to be changed to show the errors
* @param {Array} aFail An array with the errors found.
* @private
*/
_showError: function(formElm, aFail) {
this._clearError(formElm);
//ink-warning-field
//console.log(aFail);
var curElm = false;
for(var i=0, tFail = aFail.length; i < tFail; i++) {
curElm = aFail[i].elm;
if (curElm) {
this._showAnErrorOnElement(curElm, aFail[i]);
}
}
},
_showAnErrorOnElement: function (curElm, error) {
/* jshint noempty:false */
var controlGroupElm = InkElement.findUpwardsByClass(
curElm, 'control-group');
var controlElm = InkElement.findUpwardsByClass(
curElm, 'control');
var errorClasses = [
this._errorClassName,
this._errorTypeClassName].join(' ');
var errorMsg = InkElement.create('p', {
className: errorClasses
});
if(error.errors[0] !== 'ink-fv-custom') {
errorMsg.innerHTML = this._flagMap[error.errors[0]].msg;
} else {
errorMsg.innerHTML = error.custom[0].msg;
}
var target = (controlElm || controlGroupElm);
if (target) {
target.appendChild(errorMsg);
} else {
InkElement.insertAfter(errorMsg, curElm);
}
if (controlElm) {
if(error.errors[0] === 'ink-fv-required') {
Css.addClassName(controlGroupElm, 'validation error');
} else {
Css.addClassName(controlGroupElm, 'validation warning');
}
}
},
/**
* Clears the error of a given element. Normally executed before any validation, for all elements, as a reset.
*
* @method _clearErrors
* @param {DOMElement} formElm Form element to be cleared.
* @private
*/
_clearError: function(formElm) {
//return;
var aErrorLabel = formElm.getElementsByTagName('p');
var curElm;
var control;
for(var i = (aErrorLabel.length - 1); i >= 0; i--) {
curElm = aErrorLabel[i];
if(Css.hasClassName(curElm, this._errorClassName)) {
control = InkElement.findUpwardsBySelector(curElm, '.control-group');
if (control) {
Css.removeClassName(control, ['validation', 'error', 'warning']);
}
if(Css.hasClassName(curElm, this._errorClassName, true /*both*/)) {
InkElement.remove(curElm);
}
}
}
var aErrorLabel2 = formElm.getElementsByTagName('ul');
for(i = (aErrorLabel2.length - 1); i >= 0; i--) {
curElm = aErrorLabel2[i];
if(Css.hasClassName(curElm, 'control-group')) {
Css.removeClassName(curElm, 'validation error');
}
}
},
/**
* Removes unnecessary spaces to the left or right of a string
*
* @method _trim
* @param {String} stri String to be trimmed
* @private
* @return {String|undefined} String trimmed.
*/
_trim: function(str)
{
if(typeof(str) === 'string')
{
return str.replace(/^\s+|\s+$|\n+$/g, '');
}
}
};
return FormValidator;
});