html-form-validation
Version:
HTML forms validation plugin
624 lines (523 loc) • 16.9 kB
JavaScript
/**
* @name form-validation.js
* @version 0.2.1
* @author Vitali Shapovalov
* @fileoverview
*
* This module is to validate HTML forms.
* Text fields, emails, phones, checkobxes etc.
*/
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;(function ($) {
/**
* Validator module
*
* @TODO: alphabet / numeric characters, rebuild data-attr logic, refactor lang, afterChange validation
*
* @constructor
*
* @param {HTMLElement|jQuery} form - form to validate
* @param {Object} [options] - user specified options
*
* @param {String} [options.fieldsSelector='.form-input'] - form's field selector string
* @param {String} [options.lang='en'] - error messages language (ru/en)
* @param {Boolean} [options.modal=false] - when true, remove fields incorrect state on modal hide
* @param {Boolean} [options.removeErrorOnFocusOut=false] - when true, remove fields incorrect state when clicked outside the form
* @param {Object} [options.ajax={}] - options for ajax request performed if form is valid
* @param {Function} [options.beforeValidation] - function performed before form validation
* @param {Function} [options.afterValidation] - function performed after form validation
* @param {Function} [options.onValid] - function performed if form is valid (before AJAX)
*
* @return {Validator}
*/
function Validator(form, options) {
/**
* Form to validate
*
* @type {jQuery}
* @public
*/
this.$form = $(form);
/**
* Defaults extended with user-specified options
*
* @type {Object}
* @public
*/
this.options = $.extend(true, {
modal: false,
fieldsSelector: '.form-input',
removeErrorOnFocusOut: false,
ajax: {},
lang: 'en',
beforeValidation: null,
afterValidation: null,
onValid: null
}, options);
/**
* Ru and En languages support.
*
* @type {Object}
* @public
*/
this.lang = {
ru: {
emptyField: 'Заполните поле',
incorrectPhone: 'Введите корректный номер',
incorrectEmail: 'Введите корректный Email',
incorrectSelect: 'Выберите один из вариантов',
symbols: 'символов',
reqFieldLength: 'Необходимая длинна поля - ',
maxFieldLength: 'Максимальная длинна поля - ',
minFieldLength: 'Минимальная длинна поля - ',
minMaxFieldLength: {
first: 'Кол-во символов должно быть не более ',
second: ' и не менее '
},
notEqual: 'Значение поля не совпало с ожидаемым'
},
en: {
emptyField: 'Fill in the field, please',
incorrectPhone: 'Please, enter a valid number',
incorrectEmail: 'Please, enter a valid email',
incorrectSelect: 'Please, select an option',
symbol: 'symbols',
reqFieldLength: 'Required field length is - ',
maxFieldLength: 'Maximum field length is - ',
minFieldLength: 'Minimum field length is - ',
minMaxFieldLength: {
first: 'Number of characters should be less than ',
second: ' but not less than '
},
notEqual: 'The field value did not match with the expected value'
}
};
// initialize after construction
this.init();
// return validator instance
return this;
}
/**
* Default options
*
* @type {Object}
* @protected
*/
var DEFAULTS = {
// SELECTORS
incorrectFields: '.incorrect',
error: '.error',
// CLASS NAMES
incorrect: 'incorrect',
formIsValid: 'validated',
// 'data-' TYPES
dataType: 'validation-type',
textDataName: 'validation-text',
dataCondition: 'validation-condition',
// 'data-validation-type=' VALUE TYPES
textType: 'text',
phoneType: 'phone',
emailType: 'email',
checkboxType: 'checkbox',
radioType: 'radio',
selectType: 'select',
// 'data-validation-condition=' VALUE TYPES
length: 'length',
equal: 'equal',
// 'data-validation=' VALUE TYPES
requiredToValidate: 'required'
};
/**
* Check existence and run callback.
*
* @param {*} callback
* @returns {Validator}
*/
Validator.prototype.checkAndRunCallback = function (callback) {
if (typeof callback === 'function') {
callback(this);
} else if (callback != undefined) {
console.warn('Callback should be a function.')
}
return this;
};
/**
* Check form for validness
*
* @return {Boolean}
*/
Validator.prototype.formIsValid = function () {
var incorrectFields = this.$form.find(DEFAULTS.incorrectFields);
return !incorrectFields.length && this.$form.hasClass(DEFAULTS.formIsValid);
};
/**
* Validates an email
*
* @static
* @param {String} email
* @return {Boolean}
*/
Validator.validateEmail = function (email) {
var pattern = new RegExp(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
return pattern.test(email);
};
/**
* Remove incorrect state from all fields
*
* @return {Validator}
*/
Validator.prototype.removeIncorrectState = function () {
var $formFields = this.$form.find(this.options.fieldsSelector);
$formFields.removeClass(DEFAULTS.incorrect);
return this;
};
/**
* Reset form to default state
*
* @return {Validator}
*/
Validator.prototype.resetForm = function () {
var form = this.$form[0];
form.reset();
return this;
};
/**
* Remove incorrect state from all fields when modal is closed
*
* @return {Validator}
*/
Validator.prototype.removeIncorrectStateOnModalClose = function () {
var _this = this;
var $modal = _this.$form.parents('.modal');
if (!$modal.length) {
console.warn('No modal was found');
return this;
}
$modal.on('hidden.bs.modal', function () {
_this.removeIncorrectState();
});
return this;
};
/**
* Remove incorrect state form all fields when clicked outside the form
*
* @return {Validator}
*/
Validator.prototype.removeIncorrectStateOnFocusOut = function () {
var _this = this;
$('body').on('click tap', function (e) {
var clickedOnForm = $(e.target).closest(_this.$form).length > 0;
if (!clickedOnForm) _this.removeIncorrectState();
});
return this;
};
/**
* Set incorrect state on field
*
* @param {jQuery} $field
* @param {String} errorText - displayed error text
*/
Validator.prototype.throwError = function ($field, errorText) {
var $error = $field.find(DEFAULTS.error);
$field.addClass(DEFAULTS.incorrect);
$error.text(errorText);
};
/**
* Check field for validness and set valid/incorrect state
*
* @param {jQuery} field
* @param {Number} valueLength
* @param {String} errorText
* @param {Boolean} condition - condition to set valid state
*/
Validator.prototype.checkFieldValidness = function (field, condition, errorText, valueLength) {
var dataText = field.data(DEFAULTS.textDataName);
var lang = this.lang[this.options.lang];
if (dataText && dataText.length) errorText = dataText;
if (!valueLength) {
this.throwError(field, lang.emptyField);
} else if (!condition) {
this.throwError(field, errorText);
} else {
this.$form.addClass(DEFAULTS.formIsValid);
}
};
/**
* Validates field
*
* @param {jQuery} field
*/
Validator.prototype.validateField = function (field) {
var lang = this.lang[this.options.lang];
var type = field.data(DEFAULTS.dataType);
var fieldParams = {
condition: true,
errorText: lang.emptyField,
length: 1
};
switch (type) {
// data-validation-type="text"
case DEFAULTS.textType: {
fieldParams = this.validateTextField(field);
break;
}
// data-validation-type="phone"
case DEFAULTS.phoneType: {
fieldParams = this.validateTextField(field);
fieldParams.errorText = lang.incorrectPhone;
break;
}
// data-validation-type="email"
case DEFAULTS.emailType: {
fieldParams = this.validateEmailField(field);
fieldParams.errorText = lang.incorrectEmail;
break;
}
// data-validation-type="radio"
case DEFAULTS.radioType: {
fieldParams.condition = this.validateRadioField(field);
break;
}
// data-validation-type="select"
case DEFAULTS.selectType: {
fieldParams = this.validateSelectField(field);
fieldParams.errorText = lang.incorrectSelect;
break;
}
}
this.checkFieldValidness(field, fieldParams.condition, fieldParams.errorText, fieldParams.length);
};
/**
* Validate 'Text' field
*
* @param {jQuery} field
* @return {Object}
*/
Validator.prototype.validateTextField = function (field) {
var lang = this.lang[this.options.lang];
var input = field.find('input').length ? field.find('input') : field.find('textarea');
var value = input.val();
var valueLength = value.length;
var conditionType = input.data(DEFAULTS.dataCondition);
var condition, errorText;
switch (conditionType) {
// data-validation-condition="length"
case DEFAULTS.length: {
var length = input.data('length');
var maxLength = input.data('max-length');
var minLength = input.data('min-length');
if (length) {
condition = valueLength === parseInt(length, 10);
errorText = lang.reqFieldLength + length + ' ' + lang.symbols;
break;
}
var maxLengthCondition = valueLength <= parseInt(maxLength, 10);
var minLengthCondition = valueLength >= parseInt(minLength, 10);
if (maxLength && minLength) {
condition = maxLengthCondition && minLengthCondition;
errorText = lang.minMaxFieldLength.first + maxLength
+ lang.minMaxFieldLength.second + minLength;
} else if (maxLength) {
condition = maxLengthCondition;
errorText = lang.maxFieldLength + maxLength + ' ' + lang.symbols;
} else if (minLength) {
condition = minLengthCondition;
errorText = lang.minFieldLength + minLength + ' ' + lang.symbols;
}
break;
}
// data-validation-condition="equal"
case DEFAULTS.equal: {
var neededValue = input.data(DEFAULTS.equal);
condition = value === neededValue;
errorText = lang.notEqual;
break;
}
}
return {
condition: condition,
errorText: errorText,
length: valueLength
};
};
/**
* Validate 'Email' field
*
* @param {jQuery} field
*/
Validator.prototype.validateEmailField = function (field) {
var value = field.find('input').val();
var condition = Validator.validateEmail(value);
var valueLength = value.length;
return {
condition: condition,
length: valueLength
};
};
/**
* Validate 'Radio' field
*
* @param {jQuery} field
*/
Validator.prototype.validateRadioField = function (field) {
var checkedVisibleRadio = field.find('input[type="radio"]:checked:visible');
return checkedVisibleRadio.length >= 1;
};
/**
* Validate 'Select' field
*
* @param {jQuery} field
*/
Validator.prototype.validateSelectField = function (field) {
var value = field.find('select').val();
var condition = value && value != 0;
return {
condition: condition,
length: 1
};
};
/**
* Serialize form
*
* @return {Array} serialized form data
*/
Validator.prototype.serializedFormData = function () {
return this.$form.serialize();
};
/**
* Send data if form is valid (perform ajax if options are passed)
*
* @param {Object} ajaxOptions - ajax options
* @return {Validator}
*/
Validator.prototype.sendIfValidated = function (ajaxOptions) {
var _this = this;
var options = _this.options;
ajaxOptions = ajaxOptions || options.ajax;
function formIsValidCallback() {
// 'onValid' callback
_this.checkAndRunCallback(options.onValid);
// ajax (if set)
if (ajaxOptions) $.ajax(ajaxOptions);
}
// 'beforeValidation' callback
_this.checkAndRunCallback(options.beforeValidation);
// run validation
if (this.formIsValid()) formIsValidCallback();
// 'afterValidation' callback
_this.checkAndRunCallback(options.afterValidation);
return this;
};
/**
* Validate form fields
*
* @return {Validator}
*/
Validator.prototype.validateAllFields = function () {
var _this = this;
var $formFields = _this.$form.find(_this.options.fieldsSelector);
$formFields.each(function (index, field) {
var $field = $(field);
var requiredToValidate = $field.data('validation') === DEFAULTS.requiredToValidate;
if (requiredToValidate) _this.validateField($field);
});
return this;
};
/**
* Validate form
*
* @return {Validator}
*/
Validator.prototype.runFormValidation = function () {
return this
.removeIncorrectState()
.validateAllFields()
.sendIfValidated();
};
/**
* Initialize on-click validation
*
* @return {Validator}
*/
Validator.prototype.bindOnClickValidation = function () {
var _this = this;
var $button = _this.$form.find('.validate-form-button');
$button.on('click.validation tap.validation', function (e) {
e.preventDefault();
_this.runFormValidation();
});
return this;
};
/**
* Unbind on-click event
*
* @return {Validator}
*/
Validator.prototype.unbindOnClick = function () {
var $button = this.$form.find('.validate-form-button');
$button.unbind('click.validation tap.validation');
return this;
};
/**
* Initialize all validation scripts
*
* @return {Validator}
*/
Validator.prototype.init = function () {
this.bindOnClickValidation();
// 'modal' options is true
if (this.options.modal) this.removeIncorrectStateOnModalClose();
// 'removeOnFocusOut' is true
if (this.options.removeErrorOnFocusOut) this.removeIncorrectStateOnFocusOut();
return this;
};
/**
* Expose popup module as jquery plugin.
* (jquery-webpack conflict fix)
*
* @static
* @param {jQuery} jQuery
*/
var exposeValidator = Validator.expose = function (newJquery) {
// refresh jquery itself
$ = newJquery;
// refresh jquery plugin
$.fn.validator = function (options) {
var instances = [];
this.each(function () {
var $this = $(this);
instances.push(new Validator($this, options));
});
return instances.length === 1 ? instances[0] : instances;
};
};
/**
* Expose Validator module.
*/
if (typeof module === 'object' && typeof module.exports === 'object') {
// CommonJS, just export
module.exports = Validator;
} else if (typeof define === 'function' && define.amd) {
// AMD support
define('vintage-popup', function () { return Validator; });
} else {
// Global
window.Validator = Validator;
}
/**
* Expose Validator module.
*/
exposeValidator($);
})(jQuery || window.jQuery || window.$);