/*!
* vForm - v2.1.0
* http://shapiromichael.github.io/vForm/
* Copyright (c) 2016
* Licensed MIT
*/
(function($) {
'use strict';
window.vForm = function(options) {
var _this = this,
__ = {
fields: jQuery(),
valid: false,
defaults: {},
params: {},
errors: [],
validators: []
},
defaults = {
// General
fields: 'input, textarea, select',
trim: true,
html: false,
focus: false,
live: '',
error: {
enabled: true,
messages: {
invalid: 'Invalid'
}
},
feedback: false,
// Events
onBegin: function() { return true; },
onBefore: function() { return true; },
onAfter: function($field, status) { return status; },
onFail: function() { return false; },
onSuccess: function() { return true; },
onErrorMessage: function($field, message) { return '' + message + ''; },
onValidFeedback: function() { return 'Valid'; }
};
this.validate = function(options) {
// Reset
__.valid = false;
__.errors = [];
// Single field validation mode
if ((options instanceof jQuery && options.size() === 1) || (typeof options === 'string' && $(options).size === 1)) {
if (_form.on('begin', $(options)) && _form.on('before', $(options))) {
return (_form.process($(options))) ? _form.on('success') : _form.on('fail');
}
} else {
// Update params
__.params = $.extend({}, __.defaults, options);
// Parse the form fields
_form.set.fields(__.params.fields);
if (__.params.fields.size()) {
if (_form.on('begin', __.params.fields)) {
var fields = __.params.fields.filter('input:not([trim=false]), textarea:not([trim=false])');
// Escape HTML string
if (!__.params.html) {
fields.each(_form.escape.html);
}
// Trim content
if (__.params.trim) {
fields.each(_form.trim);
}
// Process the validations
fields.each(function() {
if (_form.on('before', $(this))) {
_form.process($(this));
}
});
// Compleate validation
if (fields.filter('[error=true]').size()) {
// Auto focus on the first error occured
if (__.params.focus) {
fields.filter('[error=true]:first').focus();
}
_form.set.invalid();
} else {
_form.set.valid();
}
}
} else {
// In case there's no elements to validate
_form.set.valid();
}
return __.valid;
}
};
this.status = function() {
return __.valid;
};
this.add = function() {
var $field, handler, error, valdator = {};
// first argument can be $field / handler
if (arguments[0] && arguments[0] instanceof jQuery) {
$field = arguments[0];
} else if (arguments[0] && $.isFunction(arguments[0])) {
handler = arguments[0];
}
// Second argument can be handler / error
if (arguments[1] && $.isFunction(arguments[1])) {
handler = arguments[1];
} else if (arguments[1] && (typeof arguments[1] === 'string' || arguments[1] instanceof String)) {
error = $.trim(arguments[1]);
}
// Third argument can be only error
if (arguments[2] && (typeof arguments[2] === 'string' || arguments[2] instanceof String)) {
error = $.trim(arguments[2]);
}
if (handler) {
valdator.handler = handler;
if ($field && $field.size()) { valdator.field = $field; }
if (error) { valdator.error = error; }
__.validators.push(valdator);
return true;
} else {
return false;
}
};
this.get = function(filter) {
switch (filter) {
case 'errors':
return __.errors;
case 'valid':
return __.params.fields.not('[error=true]');
case 'invalid':
return __.params.fields.filter('[error=true]');
default:
return __.params.fields;
}
};
this.set = function(key, value) {
if (key && __.params[ key ] && typeof __.params[ key ] === typeof value) {
__.params[ key ] = value;
return true;
} else {
return false;
}
};
this.clear = function() {
__.params.fields.each(function() {
var $this = $(this),
value = '';
switch ($this.attr('type')) {
case 'text':
case 'number':
case 'email':
case 'url':
case 'range':
case 'password':
value = ($(this).data('default')) ? $(this).data('default') : '' ;
$(this).val(value);
break;
case 'checkbox':
case 'radio':
if ($(this).is('fieldset[data-default] input[type=' + $this.attr('type') + ']')) {
var $fieldset = $this.parents('fieldset[data-default]');
value = $fieldset.data('default');
$fieldset.find('input[type=' + $this.attr('type') + ']').prop('checked', false);
$fieldset.find('input[type=' + $this.attr('type') + '][value=' + value + ']').prop('checked', true);
} else {
if ($(this).data('default') === 'checked') {
$(this).prop('checked', true);
} else {
$(this).prop('checked', false);
}
}
break;
default:
if ($this.is('textarea')) {
value = ($(this).data('default')) ? $(this).data('default') : '' ;
$(this).val(value);
}
}
});
};
// Privates
var _form = {
init: function(options) {
// Update params
__.defaults = $.extend({}, defaults, options);
// Parse the form fields
__.defaults.fields = _form.set.fields(__.defaults.fields);
// Live validation
switch (__.defaults.live.toLowerCase()) {
case 'change': case 'keyup': case 'blur':
__.defaults.live = __.defaults.live.toLowerCase();
break;
case 'onchange': case 'onkeyup': case 'onblur':
__.defaults.live = __.defaults.live.substring(2).toLowerCase();
break;
default:
__.defaults.live = '';
break;
}
if (__.defaults.live) {
__.params = $.extend({}, __.defaults, options);
__.defaults.fields.on(__.defaults.live, function() {
_this.validate($(this));
});
}
// Clear the form
_this.clear();
},
check: {
int: function(value) {
return /^[+-]?\d+$/i.test(value);
},
float: function(value) {
return /^[+-]?\d+\.\d+$/i.test(value);
},
number: function(value) {
return (this.int(value) || this.float(value));
},
email: function(value) {
return /^(("[\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.test(value);
},
url: function(value) {
return /^(?:http|ftp)s?:\/\/(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::\d+)?(?:\/?|[\/?]\S+)$/gi.test(value);
},
ip: function(value) {
return /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.) {3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/.test(value);
},
creditcard: function(value) {
return /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/.exec(value);
},
color: function(value) {
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value);
}
},
convert: {
toLower: function(object) {
if (object && object.is('input[type=text], input[type=url], input[type=email], textarea')) {
var newVal = object.val().toLowerCase();
object.val(newVal);
return newVal;
}
return null;
},
toInt: function(string) {
return (string && $.isNumeric(string)) ? parseInt(string) : (string && -1 < string.indexOf('px')) ? this.toInt(string.slice(0, -2)) : 0 ;
},
toFloat: function(string) {
return (string && $.isNumeric(string)) ? parseFloat(string) : 0.0 ;
}
},
set: {
fields: function(fields) {
if (typeof fields === 'string' || fields instanceof String) {
fields = $(fields);
}
__.params.fields = fields.not('[disabled=disabled]').not('[data-ignore=true]');
return __.params.fields;
},
valid: function() {
__.valid = _form.on('success');
return __.valid;
},
invalid: function() {
__.valid = _form.on('fail');
return __.valid;
}
},
on: function(event) {
var result = true;
switch (event) {
case 'begin':
if ($.isFunction(__.params.onBegin)) { result = __.params.onBegin(arguments[1].toArray()); }
break;
case 'before':
if ($.isFunction(__.params.onBefore)) { result = __.params.onBefore(arguments[1]); }
break;
case 'after':
if ($.isFunction(__.params.onAfter)) { result = __.params.onAfter(arguments[1], arguments[2]); } else { return arguments[2]; }
break;
case 'fail':
if ($.isFunction(__.params.onFail)) { result = __.params.onFail(__.params.fields.filter('[error=true]').toArray(), __.errors); } else { return false; }
break;
case 'success':
if ($.isFunction(__.params.onSuccess)) { result = __.params.onSuccess(__.fields.toArray()); }
break;
case 'errorMessage':
if ($.isFunction(__.params.onErrorMessage)) { result = __.params.onErrorMessage(arguments[1], arguments[2], arguments[3]); } else { return arguments[2]; }
break;
case 'validFeedback':
if ($.isFunction(__.params.onValidFeedback)) { result = __.params.onValidFeedback(arguments[1]); } else { return arguments[1]; }
break;
}
return result;
},
trim: function() {
var $this = $(this);
$this.val($.trim($this.val()));
},
escape: {
html: function() {
var $this = $(this);
$this.val($this.val().replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''));
}
},
process: function($this) {
var isValid = true;
// Validate for required state
if ($this.is('[required]')) {
if (!$this.is('[data-group]')) {
// Check if has a value
isValid = ($this.is('input[type=checkbox], input[type=radio]')) ? $this.is(':checked') : (isValid && $this.val()) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'required', 'required'); }
} else {
// Validate for required group
if ($this.is(__.params.fields.filter('[required][data-group=' + $this.attr('data-group') + ']:first'))) {
isValid = false;
__.params.fields.filter('[required][data-group=' + $this.attr('data-group') + ']').each(function() {
var $this = $(this);
isValid = ($this.is('input[type=checkbox], input[type=radio]')) ? (isValid || $this.is(':checked')) ? true : false : (isValid || $this.val()) ? true : false ;
});
}
// Handle errors
if (!isValid) { _form.error($this, 'required_group', 'required-group'); }
}
}
if ($this.val()) {
// Validate email
if (isValid && $this.is('input[type=email]:not([pattern])')) {
_form.convert.toLower($this);
isValid = (isValid && _form.check.email($this.val())) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'email', 'email'); }
}
// Validate URL
if (isValid && $this.is('input[type=url]:not([pattern]):not([data-validate="ip"])')) {
isValid = (isValid && _form.check.url($this.val())) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'url', 'url'); }
}
// Validate IP address
if (isValid && ($this.is('input[type=text][data-validate="ip"]:not([pattern])') || $this.is('input[type=url][data-validate="ip"]:not([pattern])'))) {
isValid = (isValid && _form.check.ip($this.val())) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'ip', 'ip'); }
}
// Validate credit card
if (isValid && ($this.is('input[type=text][data-validate="credit-card"]:not([pattern])') || $this.is('input[type=number][data-validate="credit-card"]:not([pattern])'))) {
var cardnumber = ($this.val()).replace(/[ -]/g, '');
if (__.params.trim) { $this.val(cardnumber); }
var check = _form.check.creditcard(cardnumber);
var types = ['Visa', 'MasterCard', 'Discover', 'American Express', 'Diners Club', 'JCB'];
if (isValid && check) {
for (var i = 1; i < check.length; i++) {
if (check[i]) {
isValid = true;
$this.attr('data-crefit-type', types[i - 1]);
break;
} else {
$this.removeAttr('data-crefit-type');
isValid = false;
}
}
} else {
isValid = false;
}
// Handle errors
if (!isValid) { _form.error($this, 'credit', 'cc'); }
}
// Validate color
if (isValid && $this.is('input[type=color]:not([pattern])')) {
isValid = (isValid && _form.check.color($this.val())) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'color', 'color'); }
}
// Validate pattern defined content
if (isValid && $this.is('input[pattern]')) {
if (window.navigator.userAgent.indexOf('MSIE ') > 0) {
var re = new RegExp($this.attr('pattern'), 'g');
isValid = (isValid && re.test($this.val())) ? true : false ;
} else {
isValid = (isValid && $this.prop('validity').valid) ? true : false ;
}
// Handle errors
if (!isValid) { _form.error($this, 'pattern', 'pattern'); }
}
}
// Validate min & max states
if (isValid && $this.is('fieldset input[type=checkbox]')) {
var $group = $this.parents('fieldset');
// Validate for minimum state
if ($group.is('[min]') && _form.check.number($group.attr('min'))) {
isValid = (isValid && $group.find('input[type=checkbox]:checked').size() >= _form.convert.toInt($group.attr('min'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($group, 'min_selection', 'min'); }
}
// Validate for maximum state
if ($group.is('[max]') && _form.check.number($group.attr('max'))) {
isValid = (isValid && $group.find('input[type=checkbox]:checked').size() <= _form.convert.toInt($group.attr('max'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($group, 'max_selection', 'max'); }
}
} else {
// Validate for minimum state
if (isValid && $this.is('[min]')) {
// Check textual content for min length
if ($this.is('input[type=text], input[type=email], input[type=url], input[type=password], input[type=tel], input[type=search], textarea') && _form.check.number($this.attr('min'))) {
isValid = (isValid && ($this.val()).length >= _form.convert.toInt($this.attr('min'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'min_length', 'min'); }
}
// Check numeric content for min number
if ($this.is('input[type=number]')) {
isValid = (isValid && _form.convert.toFloat($this.val()) >= _form.convert.toFloat($this.attr('min'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'min', 'min'); }
}
// Check range field content
if ($this.is('input[type=range]')) {
isValid = (isValid && _form.convert.toFloat($this.val()) >= _form.convert.toFloat($this.attr('data-min'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'min', 'min'); }
}
}
// Validate for maximum state
if (isValid && $this.is('[max]')) {
// Check textual content for max length
if ($this.is('input[type=text], input[type=email], input[type=url], input[type=password], input[type=tel], textarea') && _form.check.number($this.attr('max'))) {
isValid = (isValid && ($this.val()).length <= _form.convert.toInt($this.attr('max'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'max_length', 'max'); }
}
// Check numeric content for max number
if ($this.is('input[type=number]')) {
isValid = (isValid && _form.convert.toFloat($this.val()) <= _form.convert.toFloat($this.attr('max'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'max', 'max'); }
}
// Check range field content
if ($this.is('input[type=range]')) {
isValid = (isValid && _form.convert.toFloat($this.val()) <= _form.convert.toFloat($this.attr('data-max'))) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'max', 'max'); }
}
}
}
// Validate required radio buttons
if (isValid && $this.is('input[type=radio][required]')) {
isValid = (isValid && $this.is(':checked')) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'radio', 'radio'); }
}else if (isValid && $this.is('fieldset[required] input[type=radio]')) {
var $fieldset = $this.parents('fieldset');
isValid = (isValid && $fieldset.find('input[type=radio]:checked').size()) ? true : false ;
// Handle errors
if (!isValid) { _form.error($fieldset, 'required', 'required'); }
}
// Validate confirm fields
if (isValid && $this.is('input[data-match]') && $('#' + $this.attr('data-match'))) {
isValid = (isValid && $this.val() === $('#' + $this.attr('data-match')).val()) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, 'match', 'match'); }
}
// Custom validators
$.each(__.validators, function(index, validator) {
if (isValid && (!validator.field || (validator.field && $this.is(validator.field)))) {
isValid = (validator.handler($this.val(), $this)) ? true : false ;
// Handle errors
if (!isValid) { _form.error($this, '!', validator.error); }
}
});
// After proccessing a field
isValid = _form.on('after', $this, isValid);
if (isValid) {
$this.removeAttr('error');
_form.feedback($this);
} else {
$this.attr('error', 'true');
}
return isValid;
},
error: function($this, key, attribute) {
if (__.params.error && __.params.error.enabled) {
var msg = '';
// Grab the correct error message
if (key === '!') {
msg = attribute;
} else if ($this.data(attribute + '-error-msg')) {
msg = $this.data(attribute + '-error-msg') || '';
} else if ($this.data('error-msg')) {
msg = $this.data('error-msg') || '';
} else if (__.params.error.messages[ key ]) {
msg = __.params.error.messages[ key ] || '';
} else {
msg = __.params.error.messages.invalid || '';
}
// Fire the error message event
msg = _form.on('errorMessage', $this, msg, key);
// Handle the error message
if (msg && __.errors.indexOf(msg) === -1) {
__.errors.push(msg);
}
}
},
feedback: function($this) {
if (__.params.feedback && __.params.feedback.enabled) {
_form.on('validFeedback', $this);
}
}
};
_form.init(options);
return _this;
};
// Collection method.
$.fn.vForm = function(options, submit) {
var result = [];
this.each(function() {
var $this = $(this),
params = ($.isPlainObject(options)) ? $.extend({}, {
fields: 'input, textarea, select'
}, options) : { fields: 'input, textarea, select' };
if (typeof params.fields === 'string' || params.fields instanceof String || params.fields instanceof jQuery) {
params.fields = $(params.fields, $this);
}
var _form = new window.vForm(params);
result.push(_form);
// novalidate
if ($this.is('form')) {
$this.attr('novalidate', 'novalidate');
if ($.isFunction(options)) {
$this.on('submit', { form: _form }, options);
} else if ($.isFunction(submit)) {
$this.on('submit', { form: _form }, submit);
}
}
});
if (result.length === 1) {
result = result[0];
}
return result;
};
}(jQuery));