|
|
/*! nice-validator 1.1.5
|
|
|
* (c) 2012-2020 Jony Zhang <niceue@live.com>, MIT Licensed
|
|
|
* https://github.com/niceue/nice-validator
|
|
|
*/
|
|
|
;(function(factory) {
|
|
|
typeof module === 'object' && module.exports ? module.exports = factory( require( 'jquery' ) ) :
|
|
|
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
|
|
|
factory(jQuery);
|
|
|
}(function($, undefined) {
|
|
|
'use strict';
|
|
|
|
|
|
var NS = 'validator',
|
|
|
CLS_NS = '.' + NS,
|
|
|
CLS_NS_RULE = '.rule',
|
|
|
CLS_NS_FIELD = '.field',
|
|
|
CLS_NS_FORM = '.form',
|
|
|
CLS_WRAPPER = 'nice-' + NS,
|
|
|
CLS_MSG_BOX = 'msg-box',
|
|
|
ARIA_INVALID = 'aria-invalid',
|
|
|
DATA_RULE = 'data-rule',
|
|
|
DATA_MSG = 'data-msg',
|
|
|
DATA_TIP = 'data-tip',
|
|
|
DATA_OK = 'data-ok',
|
|
|
DATA_TIMELY = 'data-timely',
|
|
|
DATA_TARGET = 'data-target',
|
|
|
DATA_DISPLAY = 'data-display',
|
|
|
DATA_MUST = 'data-must',
|
|
|
NOVALIDATE = 'novalidate',
|
|
|
INPUT_SELECTOR = ':verifiable',
|
|
|
|
|
|
rRules = /(&)?(!)?\b(\w+)(?:\[\s*(.*?\]?)\s*\]|\(\s*(.*?\)?)\s*\))?\s*(;|\|)?/g,
|
|
|
rRule = /(\w+)(?:\[\s*(.*?\]?)\s*\]|\(\s*(.*?\)?)\s*\))?/,
|
|
|
rDisplay = /(?:([^:;\(\[]*):)?(.*)/,
|
|
|
rDoubleBytes = /[^\x00-\xff]/g,
|
|
|
rPos = /top|right|bottom|left/,
|
|
|
rAjaxType = /(?:(cors|jsonp):)?(?:(post|get):)?(.+)/i,
|
|
|
rUnsafe = /[<>'"`\\]|&#x?\d+[A-F]?;?|%3[A-F]/gmi,
|
|
|
|
|
|
noop = $.noop,
|
|
|
proxy = $.proxy,
|
|
|
trim = $.trim,
|
|
|
isFunction = $.isFunction,
|
|
|
isString = function(s) {
|
|
|
return typeof s === 'string';
|
|
|
},
|
|
|
isObject = function(o) {
|
|
|
return o && Object.prototype.toString.call(o) === '[object Object]';
|
|
|
},
|
|
|
isIE = document.documentMode || +(navigator.userAgent.match(/MSIE (\d+)/) && RegExp.$1),
|
|
|
attr = function(el, key, value) {
|
|
|
if (!el || !el.tagName) return null;
|
|
|
if (value !== undefined) {
|
|
|
if (value === null) el.removeAttribute(key);
|
|
|
else el.setAttribute(key, '' + value);
|
|
|
} else {
|
|
|
return el.getAttribute(key);
|
|
|
}
|
|
|
},
|
|
|
novalidateonce,
|
|
|
preinitialized = {},
|
|
|
|
|
|
defaults = {
|
|
|
debug: 0,
|
|
|
theme: 'default',
|
|
|
ignore: '',
|
|
|
focusInvalid: true,
|
|
|
focusCleanup: false,
|
|
|
stopOnError: false,
|
|
|
beforeSubmit: null,
|
|
|
valid: null,
|
|
|
invalid: null,
|
|
|
validation: null,
|
|
|
formClass: 'n-default',
|
|
|
validClass: 'n-valid',
|
|
|
invalidClass: 'n-invalid',
|
|
|
bindClassTo: null
|
|
|
},
|
|
|
fieldDefaults = {
|
|
|
timely: 1,
|
|
|
display: null,
|
|
|
target: null,
|
|
|
ignoreBlank: false,
|
|
|
showOk: true,
|
|
|
// Translate ajax response to validation result
|
|
|
dataFilter: function (data) {
|
|
|
if ( isString(data) || ( isObject(data) && ('error' in data || 'ok' in data) ) ) {
|
|
|
return data;
|
|
|
}
|
|
|
},
|
|
|
msgMaker: function(opt) {
|
|
|
var html;
|
|
|
html = '<span role="alert" class="msg-wrap n-'+ opt.type + '">' + opt.arrow;
|
|
|
if (opt.result) {
|
|
|
$.each(opt.result, function(i, obj){
|
|
|
html += '<span class="n-'+ obj.type +'">' + opt.icon + '<span class="n-msg">' + obj.msg + '</span></span>';
|
|
|
});
|
|
|
} else {
|
|
|
html += opt.icon + '<span class="n-msg">' + opt.msg + '</span>';
|
|
|
}
|
|
|
html += '</span>';
|
|
|
return html;
|
|
|
},
|
|
|
msgWrapper: 'span',
|
|
|
msgArrow: '',
|
|
|
msgIcon: '<span class="n-icon"></span>',
|
|
|
msgClass: 'n-right',
|
|
|
msgStyle: '',
|
|
|
msgShow: null,
|
|
|
msgHide: null
|
|
|
},
|
|
|
themes = {};
|
|
|
|
|
|
/** jQuery Plugin
|
|
|
* @param {Object} options
|
|
|
debug {Boolean} 0 Whether to enable debug mode
|
|
|
timely {Number} 1 Whether to enable timely validation
|
|
|
theme {String} 'default' Theme name
|
|
|
stopOnError {Boolean} false Whether to stop validate when found an error input
|
|
|
focusCleanup {Boolean} false Whether to clean up the field message when focus the field
|
|
|
focusInvalid {Boolean} true Whether to focus the field that is invalid
|
|
|
ignoreBlank {Boolean} false When the field has no value, whether to ignore validation
|
|
|
ignore {jqSelector} '' Ignored fields (Using jQuery selector)
|
|
|
|
|
|
beforeSubmit {Function} Do something before submit form
|
|
|
dataFilter {Function} Convert ajax results
|
|
|
valid {Function} Triggered when the form is valid
|
|
|
invalid {Function} Triggered when the form is invalid
|
|
|
validClass {String} 'n-valid' Add this class name to a valid field
|
|
|
invalidClass {String} 'n-invalid' Add this class name to a invalid field
|
|
|
bindClassTo {jqSelector} ':verifiable' Which element should the className binding to
|
|
|
|
|
|
display {Function} Callback function to get dynamic display
|
|
|
target {Function} Callback function to get dynamic target
|
|
|
msgShow {Function} Trigger this callback when show message
|
|
|
msgHide {Function} Trigger this callback when hide message
|
|
|
msgWrapper {String} 'span' Message wrapper tag name
|
|
|
msgMaker {Function} Callback function to make message HTML
|
|
|
msgArrow {String} Message arrow template
|
|
|
msgIcon {String} Message icon template
|
|
|
msgStyle {String} Custom message css style
|
|
|
msgClass {String} Additional added to the message class names
|
|
|
formClass {String} Additional added to the form class names
|
|
|
|
|
|
messages {Object} Custom messages for the current instance
|
|
|
rules {Object} Custom rules for the current instance
|
|
|
fields {Object} Field validation configuration
|
|
|
{String} key name|#id
|
|
|
{String|Object} value Rule string or an object which can pass more arguments
|
|
|
|
|
|
fields[key][rule] {String} Rule string
|
|
|
fields[key][display] {String|Function}
|
|
|
fields[key][tip] {String} Custom tip message
|
|
|
fields[key][ok] {String} Custom success message
|
|
|
fields[key][msg] {Object} Custom error message
|
|
|
fields[key][msgStyle] {String} Custom message style
|
|
|
fields[key][msgClass] {String} A className which added to message placeholder element
|
|
|
fields[key][msgWrapper] {String} Tag name of the message placeholder element
|
|
|
fields[key][msgMaker] {Function} A function to custom message HTML
|
|
|
fields[key][dataFilter] {Function} A function to convert ajax results
|
|
|
fields[key][valid] {Function} A function triggered when field is valid
|
|
|
fields[key][invalid] {Function} A function triggered when field is invalid
|
|
|
fields[key][must] {Boolean} If set true, we always check the field even has remote checking
|
|
|
fields[key][timely] {Boolean} Whether to enable timely validation
|
|
|
fields[key][target] {jqSelector} Define placement of a message
|
|
|
*/
|
|
|
$.fn.validator = function(options) {
|
|
|
var that = this,
|
|
|
args = arguments;
|
|
|
|
|
|
if (that.is(INPUT_SELECTOR)) return that;
|
|
|
if (!that.is('form')) that = this.find('form');
|
|
|
if (!that.length) that = this;
|
|
|
|
|
|
that.each(function() {
|
|
|
var instance = $(this).data(NS);
|
|
|
|
|
|
if (instance) {
|
|
|
if ( isString(options) ) {
|
|
|
if ( options.charAt(0) === '_' ) return;
|
|
|
instance[options].apply(instance, [].slice.call(args, 1));
|
|
|
}
|
|
|
else if (options) {
|
|
|
instance._reset(true);
|
|
|
instance._init(this, options);
|
|
|
}
|
|
|
} else {
|
|
|
new Validator(this, options);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return this;
|
|
|
};
|
|
|
|
|
|
|
|
|
// Validate a field, or an area
|
|
|
$.fn.isValid = function(callback, hideMsg) {
|
|
|
var me = _getInstance(this[0]),
|
|
|
hasCallback = isFunction(callback),
|
|
|
ret, opt;
|
|
|
|
|
|
if (!me) return true;
|
|
|
if (!hasCallback && hideMsg === undefined) hideMsg = callback;
|
|
|
me.checkOnly = !!hideMsg;
|
|
|
opt = me.options;
|
|
|
|
|
|
ret = me._multiValidate(
|
|
|
this.is(INPUT_SELECTOR) ? this : this.find(INPUT_SELECTOR),
|
|
|
function(isValid){
|
|
|
if (!isValid && opt.focusInvalid && !me.checkOnly) {
|
|
|
// navigate to the error element
|
|
|
me.$el.find('[' + ARIA_INVALID + ']:first').focus();
|
|
|
}
|
|
|
if (hasCallback) {
|
|
|
if (callback.length) {
|
|
|
callback(isValid);
|
|
|
} else if (isValid) {
|
|
|
callback();
|
|
|
}
|
|
|
}
|
|
|
me.checkOnly = false;
|
|
|
}
|
|
|
);
|
|
|
|
|
|
// If you pass a callback, we maintain the jQuery object chain
|
|
|
return hasCallback ? this : ret;
|
|
|
};
|
|
|
|
|
|
$.extend($.expr.pseudos || $.expr[':'], {
|
|
|
// A faster selector than ":input:not(:submit,:button,:reset,:image,:disabled,[contenteditable])"
|
|
|
verifiable: function(elem) {
|
|
|
var name = elem.nodeName.toLowerCase();
|
|
|
|
|
|
return ( name === 'input' && !({submit: 1, button: 1, reset: 1, image: 1})[elem.type] ||
|
|
|
name === 'select' ||
|
|
|
name === 'textarea' ||
|
|
|
elem.contentEditable === 'true'
|
|
|
) && !elem.disabled;
|
|
|
},
|
|
|
// any value, but not only whitespace
|
|
|
filled: function(elem) {
|
|
|
return !!trim($(elem).val());
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* Creates a new Validator
|
|
|
*
|
|
|
* @class
|
|
|
* @param {Element} element - form element
|
|
|
* @param {Object} options - options for validator
|
|
|
*/
|
|
|
function Validator(element, options) {
|
|
|
var me = this;
|
|
|
|
|
|
if ( !(me instanceof Validator) ) {
|
|
|
return new Validator(element, options);
|
|
|
}
|
|
|
|
|
|
if (Validator.pending) {
|
|
|
$(window).on('validatorready', init);
|
|
|
} else {
|
|
|
init();
|
|
|
}
|
|
|
|
|
|
function init() {
|
|
|
me.$el = $(element);
|
|
|
if (me.$el.length) {
|
|
|
me._init(me.$el[0], options);
|
|
|
}
|
|
|
else if (isString(element)) {
|
|
|
preinitialized[element] = options;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Validator.prototype = {
|
|
|
_init: function(element, options) {
|
|
|
var me = this,
|
|
|
opt, themeOpt, dataOpt;
|
|
|
|
|
|
// Initialization options
|
|
|
if ( isFunction(options) ) {
|
|
|
options = {
|
|
|
valid: options
|
|
|
};
|
|
|
}
|
|
|
options = me._opt = options || {};
|
|
|
dataOpt = attr(element, 'data-'+ NS +'-option');
|
|
|
dataOpt = me._dataOpt = dataOpt && dataOpt.charAt(0) === '{' ? (new Function('return ' + dataOpt))() : {};
|
|
|
themeOpt = me._themeOpt = themes[ options.theme || dataOpt.theme || defaults.theme ];
|
|
|
opt = me.options = $.extend({}, defaults, fieldDefaults, themeOpt, me.options, options, dataOpt);
|
|
|
|
|
|
me.rules = new Rules(opt.rules, true);
|
|
|
me.messages = new Messages(opt.messages, true);
|
|
|
me.Field = _createFieldFactory(me);
|
|
|
me.elements = me.elements || {};
|
|
|
me.deferred = {};
|
|
|
me.errors = {};
|
|
|
me.fields = {};
|
|
|
// Initialization fields
|
|
|
me._initFields(opt.fields);
|
|
|
|
|
|
// Initialization events and make a cache
|
|
|
if ( !me.$el.data(NS) ) {
|
|
|
me.$el.data(NS, me).addClass(CLS_WRAPPER +' '+ opt.formClass)
|
|
|
.on('form-submit-validate', function(e, a, $form, opts, veto) {
|
|
|
me.vetoed = veto.veto = !me.isValid;
|
|
|
me.ajaxFormOptions = opts;
|
|
|
})
|
|
|
.on('submit'+ CLS_NS +' validate'+ CLS_NS, proxy(me, '_submit'))
|
|
|
.on('reset'+ CLS_NS, proxy(me, '_reset'))
|
|
|
.on('showmsg'+ CLS_NS, proxy(me, '_showmsg'))
|
|
|
.on('hidemsg'+ CLS_NS, proxy(me, '_hidemsg'))
|
|
|
.on('focusin'+ CLS_NS + ' click'+ CLS_NS, INPUT_SELECTOR, proxy(me, '_focusin'))
|
|
|
.on('focusout'+ CLS_NS +' validate'+ CLS_NS, INPUT_SELECTOR, proxy(me, '_focusout'))
|
|
|
.on('keyup'+ CLS_NS +' input'+ CLS_NS + ' compositionstart compositionend', INPUT_SELECTOR, proxy(me, '_focusout'))
|
|
|
.on('click'+ CLS_NS, ':radio,:checkbox', 'click', proxy(me, '_focusout'))
|
|
|
.on('change'+ CLS_NS, 'select,input[type="file"]', 'change', proxy(me, '_focusout'));
|
|
|
|
|
|
// cache the novalidate attribute value
|
|
|
me._NOVALIDATE = attr(element, NOVALIDATE);
|
|
|
// Initialization is complete, stop off default HTML5 form validation
|
|
|
// If use "jQuery.attr('novalidate')" in IE7 will complain: "SCRIPT3: Member not found."
|
|
|
attr(element, NOVALIDATE, NOVALIDATE);
|
|
|
}
|
|
|
|
|
|
// Display all messages in target container
|
|
|
if ( isString(opt.target) ) {
|
|
|
me.$el.find(opt.target).addClass('msg-container');
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Guess whether the form use ajax submit
|
|
|
_guessAjax: function(form) {
|
|
|
var me = this;
|
|
|
|
|
|
if ( !(me.isAjaxSubmit = !!me.options.valid) ) {
|
|
|
// if there is a "valid.form" event
|
|
|
var events = ($._data || $.data)(form, 'events');
|
|
|
me.isAjaxSubmit = issetEvent(events, 'valid', 'form') || issetEvent(events, 'submit', 'form-plugin');
|
|
|
}
|
|
|
|
|
|
function issetEvent(events, name, namespace) {
|
|
|
return !!(
|
|
|
events && events[name]
|
|
|
&& $.map(events[name], function(e){
|
|
|
return ~e.namespace.indexOf(namespace) ? 1 : null;
|
|
|
}).length )
|
|
|
}
|
|
|
},
|
|
|
|
|
|
_initFields: function(fields) {
|
|
|
var me = this, k, arr, i,
|
|
|
clear = fields === null;
|
|
|
|
|
|
// Processing field information
|
|
|
if (clear) fields = me.fields;
|
|
|
|
|
|
if ( isObject(fields) ) {
|
|
|
for (k in fields) {
|
|
|
if (~k.indexOf(',')) {
|
|
|
arr = k.split(',');
|
|
|
i = arr.length;
|
|
|
while (i--) {
|
|
|
initField(trim(arr[i]), fields[k]);
|
|
|
}
|
|
|
} else {
|
|
|
initField(k, fields[k]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Parsing DOM rules
|
|
|
me.$el.find(INPUT_SELECTOR).each(function() {
|
|
|
me._parse(this);
|
|
|
});
|
|
|
|
|
|
function initField(k, v) {
|
|
|
// delete a field from settings
|
|
|
if ( v === null || clear ) {
|
|
|
var el = me.elements[k];
|
|
|
if (el) me._resetElement(el, true);
|
|
|
delete me.fields[k];
|
|
|
} else {
|
|
|
me.fields[k] = new me.Field(k, isString(v) ? {rule: v} : v, me.fields[k]);
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Parsing a field
|
|
|
_parse: function(el) {
|
|
|
var me = this,
|
|
|
field,
|
|
|
key = el.name,
|
|
|
display,
|
|
|
timely,
|
|
|
dataRule = attr(el, DATA_RULE);
|
|
|
|
|
|
dataRule && attr(el, DATA_RULE, null);
|
|
|
|
|
|
// If the field has passed the key as id mode, or it doesn't has a name
|
|
|
if ( el.id && (
|
|
|
('#' + el.id in me.fields) ||
|
|
|
!key ||
|
|
|
// If dataRule and element are diffrent from old's, we use ID mode.
|
|
|
(dataRule !== null && (field = me.fields[key]) && dataRule !== field.rule && el.id !== field.key)
|
|
|
)
|
|
|
) {
|
|
|
key = '#' + el.id;
|
|
|
}
|
|
|
// Generate id
|
|
|
if (!key) {
|
|
|
key = '#' + (el.id = 'N' + String(Math.random()).slice(-12));
|
|
|
}
|
|
|
|
|
|
field = me.getField(key, true);
|
|
|
// The priority of passing parameter by DOM is higher than by JS.
|
|
|
field.rule = dataRule || field.rule;
|
|
|
|
|
|
if (display = attr(el, DATA_DISPLAY)) {
|
|
|
field.display = display;
|
|
|
}
|
|
|
if (field.rule) {
|
|
|
if ( attr(el, DATA_MUST) !== null || /\b(?:match|checked)\b/.test(field.rule) ) {
|
|
|
field.must = true;
|
|
|
}
|
|
|
if ( /\brequired\b/.test(field.rule) ) {
|
|
|
field.required = true;
|
|
|
}
|
|
|
if (timely = attr(el, DATA_TIMELY)) {
|
|
|
field.timely = +timely;
|
|
|
} else if (field.timely > 3) {
|
|
|
attr(el, DATA_TIMELY, field.timely);
|
|
|
}
|
|
|
me._parseRule(field);
|
|
|
field.old = {};
|
|
|
}
|
|
|
if ( isString(field.target) ) {
|
|
|
attr(el, DATA_TARGET, field.target);
|
|
|
}
|
|
|
if ( isString(field.tip) ) {
|
|
|
attr(el, DATA_TIP, field.tip);
|
|
|
}
|
|
|
|
|
|
return me.fields[key] = field;
|
|
|
},
|
|
|
|
|
|
// Parsing field rules
|
|
|
_parseRule: function(field) {
|
|
|
var arr = rDisplay.exec(field.rule);
|
|
|
|
|
|
if (!arr) return;
|
|
|
// current rule index
|
|
|
field._i = 0;
|
|
|
if (arr[1]) {
|
|
|
field.display = arr[1];
|
|
|
}
|
|
|
if (arr[2]) {
|
|
|
field._rules = [];
|
|
|
arr[2].replace(rRules, function(){
|
|
|
var args = arguments;
|
|
|
args[4] = args[4] || args[5];
|
|
|
field._rules.push({
|
|
|
and: args[1] === '&',
|
|
|
not: args[2] === '!',
|
|
|
or: args[6] === '|',
|
|
|
method: args[3],
|
|
|
params: args[4] ? $.map( args[4].split(', '), trim ) : undefined
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Verify a zone
|
|
|
_multiValidate: function($inputs, doneCallback){
|
|
|
var me = this,
|
|
|
opt = me.options;
|
|
|
|
|
|
me.hasError = false;
|
|
|
|
|
|
if (opt.ignore) {
|
|
|
$inputs = $inputs.not(opt.ignore);
|
|
|
}
|
|
|
|
|
|
$inputs.each(function() {
|
|
|
me._validate(this);
|
|
|
if (me.hasError && opt.stopOnError) {
|
|
|
// stop the validation
|
|
|
return false;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// Need to wait for all fields validation complete, especially asynchronous validation
|
|
|
if (doneCallback) {
|
|
|
me.validating = true;
|
|
|
$.when.apply(
|
|
|
null,
|
|
|
$.map(me.deferred, function(v){return v;})
|
|
|
).done(function(){
|
|
|
doneCallback.call(me, !me.hasError);
|
|
|
me.validating = false;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// If the form does not contain asynchronous validation, the return value is correct.
|
|
|
// Otherwise, you should detect form validation result through "doneCallback".
|
|
|
return !$.isEmptyObject(me.deferred) ? undefined : !me.hasError;
|
|
|
},
|
|
|
|
|
|
// Validate the whole form
|
|
|
_submit: function(e) {
|
|
|
var me = this,
|
|
|
opt = me.options,
|
|
|
form = e.target,
|
|
|
canSubmit = e.type === 'submit' && form.tagName === 'FORM' && !e.isDefaultPrevented();
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (
|
|
|
novalidateonce && ~(novalidateonce = false) ||
|
|
|
// Prevent duplicate submission
|
|
|
me.submiting ||
|
|
|
// Receive the "validate" event only from the form.
|
|
|
e.type === 'validate' && me.$el[0] !== form ||
|
|
|
// trigger the beforeSubmit callback.
|
|
|
isFunction(opt.beforeSubmit) && opt.beforeSubmit.call(me, form) === false
|
|
|
) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (me.isAjaxSubmit === undefined) {
|
|
|
me._guessAjax(form);
|
|
|
}
|
|
|
|
|
|
me._debug('log', '\n<<< event: ' + e.type);
|
|
|
|
|
|
me._reset();
|
|
|
me.submiting = true;
|
|
|
|
|
|
me._multiValidate(
|
|
|
me.$el.find(INPUT_SELECTOR),
|
|
|
function(isValid){
|
|
|
var ret = (isValid || opt.debug === 2) ? 'valid' : 'invalid',
|
|
|
errors;
|
|
|
|
|
|
if (!isValid) {
|
|
|
if (opt.focusInvalid) {
|
|
|
// navigate to the error element
|
|
|
me.$el.find('[' + ARIA_INVALID + ']:first').focus();
|
|
|
}
|
|
|
errors = $.map(me.errors, function(err){return err;});
|
|
|
}
|
|
|
|
|
|
// releasing submit
|
|
|
me.submiting = false;
|
|
|
me.isValid = isValid;
|
|
|
|
|
|
// trigger callback and event
|
|
|
isFunction(opt[ret]) && opt[ret].call(me, form, errors);
|
|
|
me.$el.trigger(ret + CLS_NS_FORM, [form, errors]);
|
|
|
|
|
|
me._debug('log', '>>> ' + ret);
|
|
|
|
|
|
if (!isValid) return;
|
|
|
// For jquery.form plugin
|
|
|
if (me.vetoed) {
|
|
|
$(form).ajaxSubmit(me.ajaxFormOptions);
|
|
|
}
|
|
|
else if (canSubmit && !me.isAjaxSubmit) {
|
|
|
document.createElement('form').submit.call(form);
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
},
|
|
|
|
|
|
_reset: function(e) {
|
|
|
var me = this;
|
|
|
|
|
|
me.errors = {};
|
|
|
if (e) {
|
|
|
me.reseting = true;
|
|
|
me.$el.find(INPUT_SELECTOR).each( function(){
|
|
|
me._resetElement(this);
|
|
|
});
|
|
|
delete me.reseting;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
_resetElement: function(el, all) {
|
|
|
this._setClass(el, null);
|
|
|
this.hideMsg(el);
|
|
|
},
|
|
|
|
|
|
// Handle events: "focusin/click"
|
|
|
_focusin: function(e) {
|
|
|
var me = this,
|
|
|
opt = me.options,
|
|
|
el = e.target,
|
|
|
timely,
|
|
|
msg;
|
|
|
|
|
|
if ( me.validating || ( e.type==='click' && document.activeElement === el ) ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (opt.focusCleanup) {
|
|
|
if ( attr(el, ARIA_INVALID) === 'true' ) {
|
|
|
me._setClass(el, null);
|
|
|
me.hideMsg(el);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
msg = attr(el, DATA_TIP);
|
|
|
|
|
|
if (msg) {
|
|
|
me.showMsg(el, {
|
|
|
type: 'tip',
|
|
|
msg: msg
|
|
|
});
|
|
|
} else {
|
|
|
if (attr(el, DATA_RULE)) {
|
|
|
me._parse(el);
|
|
|
}
|
|
|
if (timely = attr(el, DATA_TIMELY)) {
|
|
|
if ( timely === 8 || timely === 9 ) {
|
|
|
me._focusout(e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Handle events: "focusout/validate/keyup/click/change/input/compositionstart/compositionend"
|
|
|
_focusout: function(e) {
|
|
|
var me = this,
|
|
|
opt = me.options,
|
|
|
el = e.target,
|
|
|
etype = e.type,
|
|
|
etype0,
|
|
|
focusin = etype === 'focusin',
|
|
|
special = etype === 'validate',
|
|
|
elem,
|
|
|
field,
|
|
|
old,
|
|
|
value,
|
|
|
timestamp,
|
|
|
key, specialKey,
|
|
|
timely,
|
|
|
timer = 0;
|
|
|
|
|
|
if (etype === 'compositionstart') {
|
|
|
me.pauseValidate = true;
|
|
|
}
|
|
|
if (etype === 'compositionend') {
|
|
|
me.pauseValidate = false;
|
|
|
}
|
|
|
if (me.pauseValidate) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// For checkbox and radio
|
|
|
elem = el.name && _checkable(el) ? me.$el.find('input[name="'+ el.name +'"]').get(0) : el;
|
|
|
// Get field
|
|
|
if (!(field = me.getField(elem)) || !field.rule) {
|
|
|
return;
|
|
|
}
|
|
|
// Cache event type
|
|
|
etype0 = field._e;
|
|
|
field._e = etype;
|
|
|
timely = field.timely;
|
|
|
|
|
|
if (!special) {
|
|
|
if (!timely || (_checkable(el) && etype !== 'click')) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
value = field.getValue();
|
|
|
|
|
|
// not validate field unless fill a value
|
|
|
if ( field.ignoreBlank && !value && !focusin ) {
|
|
|
me.hideMsg(el);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if ( etype === 'focusout' ) {
|
|
|
if (etype0 === 'change') {
|
|
|
return;
|
|
|
}
|
|
|
if ( timely === 2 || timely === 8 ) {
|
|
|
old = field.old;
|
|
|
if (value && old) {
|
|
|
if (field.isValid && !old.showOk) {
|
|
|
me.hideMsg(el);
|
|
|
} else {
|
|
|
me._makeMsg(el, field, old);
|
|
|
}
|
|
|
} else {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if ( timely < 2 && !e.data ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// mark timestamp to reduce the frequency of the received event
|
|
|
timestamp = +new Date();
|
|
|
if ( timestamp - (el._ts || 0) < 100 ) {
|
|
|
return;
|
|
|
}
|
|
|
el._ts = timestamp;
|
|
|
|
|
|
// handle keyup
|
|
|
if ( etype === 'keyup' ) {
|
|
|
if (etype0 === 'input') {
|
|
|
return;
|
|
|
}
|
|
|
key = e.keyCode;
|
|
|
specialKey = {
|
|
|
8: 1, // Backspace
|
|
|
9: 1, // Tab
|
|
|
16: 1, // Shift
|
|
|
32: 1, // Space
|
|
|
46: 1 // Delete
|
|
|
};
|
|
|
|
|
|
// only gets focus, no validation
|
|
|
if ( key === 9 && !value ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// do not validate, if triggered by these keys
|
|
|
if ( key < 48 && !specialKey[key] ) {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
if ( !focusin ) {
|
|
|
// keyboard events, reducing the frequency of validation
|
|
|
timer = timely <100 ? (etype === 'click' || el.tagName === 'SELECT') ? 0 : 400 : timely;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// if the current field is ignored
|
|
|
if ( opt.ignore && $(el).is(opt.ignore) ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
clearTimeout(field._t);
|
|
|
|
|
|
if (timer) {
|
|
|
field._t = setTimeout(function() {
|
|
|
me._validate(el, field);
|
|
|
}, timer);
|
|
|
} else {
|
|
|
if (special) field.old = {};
|
|
|
me._validate(el, field);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
_setClass: function(el, isValid) {
|
|
|
var $el = $(el), opt = this.options;
|
|
|
if (opt.bindClassTo) {
|
|
|
$el = $el.closest(opt.bindClassTo);
|
|
|
}
|
|
|
$el.removeClass( opt.invalidClass + ' ' + opt.validClass );
|
|
|
if (isValid !== null) {
|
|
|
$el.addClass( isValid ? opt.validClass : opt.invalidClass );
|
|
|
}
|
|
|
},
|
|
|
|
|
|
_showmsg: function(e, type, msg) {
|
|
|
var me = this,
|
|
|
el = e.target;
|
|
|
|
|
|
if ( me.$el.is(el) ) {
|
|
|
if (isObject(type)) {
|
|
|
me.showMsg(type)
|
|
|
}
|
|
|
else if ( type === 'tip' ) {
|
|
|
me.$el.find(INPUT_SELECTOR +'['+ DATA_TIP +']', el).each(function(){
|
|
|
me.showMsg(this, {type: type, msg: msg});
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
me.showMsg(el, {type: type, msg: msg});
|
|
|
}
|
|
|
},
|
|
|
|
|
|
_hidemsg: function(e) {
|
|
|
var $el = $(e.target);
|
|
|
|
|
|
if ( $el.is(INPUT_SELECTOR) ) {
|
|
|
this.hideMsg($el);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Validated a field
|
|
|
_validatedField: function(el, field, ret) {
|
|
|
var me = this,
|
|
|
opt = me.options,
|
|
|
isValid = field.isValid = ret.isValid = !!ret.isValid,
|
|
|
callback = isValid ? 'valid' : 'invalid';
|
|
|
|
|
|
ret.key = field.key;
|
|
|
ret.ruleName = field._r;
|
|
|
ret.id = el.id;
|
|
|
ret.value = field.value;
|
|
|
|
|
|
me.elements[field.key] = ret.element = el;
|
|
|
me.isValid = me.$el[0].isValid = isValid ? me.isFormValid() : isValid;
|
|
|
|
|
|
if (isValid) {
|
|
|
ret.type = 'ok';
|
|
|
} else {
|
|
|
if (me.submiting) {
|
|
|
me.errors[field.key] = ret.msg;
|
|
|
}
|
|
|
me.hasError = true;
|
|
|
}
|
|
|
|
|
|
// cache result
|
|
|
field.old = ret;
|
|
|
|
|
|
// trigger callback
|
|
|
isFunction(field[callback]) && field[callback].call(me, el, ret);
|
|
|
isFunction(opt.validation) && opt.validation.call(me, el, ret);
|
|
|
|
|
|
// trigger event
|
|
|
$(el).attr( ARIA_INVALID, isValid ? null : true )
|
|
|
.trigger( callback + CLS_NS_FIELD, [ret, me] );
|
|
|
me.$el.triggerHandler('validation', [ret, me]);
|
|
|
|
|
|
if (me.checkOnly) return;
|
|
|
// set className
|
|
|
me._setClass(el, ret.skip || ret.type === 'tip' ? null : isValid);
|
|
|
me._makeMsg.apply(me, arguments);
|
|
|
},
|
|
|
|
|
|
_makeMsg: function(el, field, ret) {
|
|
|
// show or hide the message
|
|
|
if (field.msgMaker) {
|
|
|
ret = $.extend({}, ret);
|
|
|
if (field._e === 'focusin') {
|
|
|
ret.type = 'tip';
|
|
|
}
|
|
|
this[ ret.showOk || ret.msg || ret.type === 'tip' ? 'showMsg' : 'hideMsg' ](el, ret, field);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Validated a rule
|
|
|
_validatedRule: function(el, field, ret, msgOpt) {
|
|
|
field = field || me.getField(el);
|
|
|
msgOpt = msgOpt || {};
|
|
|
|
|
|
var me = this,
|
|
|
msg,
|
|
|
rule,
|
|
|
method = field._r,
|
|
|
timely = field.timely,
|
|
|
special = timely === 9 || timely === 8,
|
|
|
transfer,
|
|
|
temp,
|
|
|
isValid = false;
|
|
|
|
|
|
// use null to break validation from a field
|
|
|
if (ret === null) {
|
|
|
me._validatedField(el, field, {isValid: true, skip: true});
|
|
|
field._i = 0;
|
|
|
return;
|
|
|
}
|
|
|
else if (ret === undefined) {
|
|
|
transfer = true;
|
|
|
}
|
|
|
else if (ret === true || ret === '') {
|
|
|
isValid = true;
|
|
|
}
|
|
|
else if (isString(ret)) {
|
|
|
msg = ret;
|
|
|
}
|
|
|
else if (isObject(ret)) {
|
|
|
if (ret.error) {
|
|
|
msg = ret.error;
|
|
|
} else {
|
|
|
msg = ret.ok;
|
|
|
isValid = true;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
isValid = !!ret
|
|
|
}
|
|
|
|
|
|
rule = field._rules[field._i];
|
|
|
if (rule.not) {
|
|
|
msg = undefined;
|
|
|
isValid = method === 'required' || !isValid;
|
|
|
}
|
|
|
if (rule.or) {
|
|
|
if (isValid) {
|
|
|
while ( field._i < field._rules.length && field._rules[field._i].or ) {
|
|
|
field._i++;
|
|
|
}
|
|
|
} else {
|
|
|
transfer = true;
|
|
|
}
|
|
|
}
|
|
|
else if (rule.and) {
|
|
|
if (!field.isValid) transfer = true;
|
|
|
}
|
|
|
|
|
|
if (transfer) {
|
|
|
isValid = true;
|
|
|
}
|
|
|
// message analysis, and throw rule level event
|
|
|
else {
|
|
|
if (isValid) {
|
|
|
if (field.showOk !== false) {
|
|
|
temp = attr(el, DATA_OK);
|
|
|
msg = temp === null ? isString(field.ok) ? field.ok : msg : temp;
|
|
|
if (!isString(msg) && isString(field.showOk)) {
|
|
|
msg = field.showOk;
|
|
|
}
|
|
|
if (isString(msg)) {
|
|
|
msgOpt.showOk = isValid;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (!isValid || special) {
|
|
|
/* rule message priority:
|
|
|
1. custom DOM message
|
|
|
2. custom field message;
|
|
|
3. global defined message;
|
|
|
4. rule returned message;
|
|
|
5. default message;
|
|
|
*/
|
|
|
msg = (_getDataMsg(el, field, msg || rule.msg || me.messages[method]) || me.messages.fallback).replace(/\{0\|?([^\}]*)\}/, function(m, defaultDisplay){
|
|
|
return me._getDisplay(el, field.display) || defaultDisplay || me.messages[0];
|
|
|
});
|
|
|
}
|
|
|
if (!isValid) field.isValid = isValid;
|
|
|
msgOpt.msg = msg;
|
|
|
$(el).trigger( (isValid ? 'valid' : 'invalid') + CLS_NS_RULE, [method, msg]);
|
|
|
}
|
|
|
|
|
|
if (special && (!transfer || rule.and)) {
|
|
|
if (!isValid && !field._m) field._m = msg;
|
|
|
field._v = field._v || [];
|
|
|
field._v.push({
|
|
|
type: isValid ? !transfer ? 'ok' : 'tip' : 'error',
|
|
|
msg: msg || rule.msg
|
|
|
});
|
|
|
}
|
|
|
|
|
|
me._debug('log', ' ' + field._i + ': ' + method + ' => ' + (isValid || msg));
|
|
|
|
|
|
// the current rule has passed, continue to validate
|
|
|
if ( (isValid || special) && field._i < field._rules.length - 1) {
|
|
|
field._i++;
|
|
|
me._checkRule(el, field);
|
|
|
}
|
|
|
// field was invalid, or all fields was valid
|
|
|
else {
|
|
|
field._i = 0;
|
|
|
|
|
|
if (special) {
|
|
|
msgOpt.isValid = field.isValid;
|
|
|
msgOpt.result = field._v;
|
|
|
msgOpt.msg = field._m || '';
|
|
|
if (!field.value && (field._e === 'focusin')) {
|
|
|
msgOpt.type = 'tip';
|
|
|
}
|
|
|
} else {
|
|
|
msgOpt.isValid = isValid;
|
|
|
}
|
|
|
|
|
|
me._validatedField(el, field, msgOpt);
|
|
|
delete field._m;
|
|
|
delete field._v;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Verify a rule form a field
|
|
|
_checkRule: function(el, field) {
|
|
|
var me = this,
|
|
|
ret,
|
|
|
fn,
|
|
|
old,
|
|
|
key = field.key,
|
|
|
rule = field._rules[field._i],
|
|
|
method = rule.method,
|
|
|
params = rule.params;
|
|
|
|
|
|
// request has been sent, wait it
|
|
|
if (me.submiting && me.deferred[key]) {
|
|
|
return;
|
|
|
}
|
|
|
old = field.old;
|
|
|
field._r = method;
|
|
|
|
|
|
if (old && !field.must && !rule.must && rule.result !== undefined &&
|
|
|
old.ruleName === method && old.id === el.id &&
|
|
|
field.value && old.value === field.value )
|
|
|
{
|
|
|
// get result from cache
|
|
|
ret = rule.result;
|
|
|
}
|
|
|
else {
|
|
|
// get result from current rule
|
|
|
fn = _getDataRule(el, method) || me.rules[method] || noop;
|
|
|
ret = fn.call(field, el, params, field);
|
|
|
if (fn.msg) rule.msg = fn.msg;
|
|
|
}
|
|
|
|
|
|
// asynchronous validation
|
|
|
if (isObject(ret) && isFunction(ret.then)) {
|
|
|
me.deferred[key] = ret;
|
|
|
|
|
|
// whether the field valid is unknown
|
|
|
field.isValid = undefined;
|
|
|
|
|
|
// show loading message
|
|
|
!me.checkOnly && me.showMsg(el, {
|
|
|
type: 'loading',
|
|
|
msg: me.messages.loading
|
|
|
}, field);
|
|
|
|
|
|
// waiting to parse the response data
|
|
|
ret.then(
|
|
|
function(d, textStatus, jqXHR) {
|
|
|
var data = trim(jqXHR.responseText),
|
|
|
result,
|
|
|
dataFilter = field.dataFilter;
|
|
|
|
|
|
// detect if data is json or jsonp format
|
|
|
if (/jsonp?/.test(this.dataType)) {
|
|
|
data = d;
|
|
|
} else if (data.charAt(0) === '{') {
|
|
|
data = $.parseJSON(data);
|
|
|
}
|
|
|
|
|
|
// filter data
|
|
|
result = dataFilter.call(this, data, field);
|
|
|
if (result === undefined) result = dataFilter.call(this, data.data, field);
|
|
|
|
|
|
rule.data = this.data;
|
|
|
rule.result = field.old ? result : undefined;
|
|
|
me._validatedRule(el, field, result);
|
|
|
},
|
|
|
function(jqXHR, textStatus){
|
|
|
me._validatedRule(el, field, me.messages[textStatus] || textStatus);
|
|
|
}
|
|
|
).always(function(){
|
|
|
delete me.deferred[key];
|
|
|
});
|
|
|
}
|
|
|
// other result
|
|
|
else {
|
|
|
me._validatedRule(el, field, ret);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// Processing the validation
|
|
|
_validate: function(el, field) {
|
|
|
var me = this;
|
|
|
|
|
|
// doesn't validate the element that has "disabled" or "novalidate" attribute
|
|
|
if ( el.disabled || attr(el, NOVALIDATE) !== null ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
field = field || me.getField(el);
|
|
|
if (!field) return;
|
|
|
if (!field._rules) me._parse(el);
|
|
|
if (!field._rules) return;
|
|
|
|
|
|
me._debug('info', field.key);
|
|
|
|
|
|
field.isValid = true;
|
|
|
field.element = el;
|
|
|
// Cache the value
|
|
|
field.value = field.getValue();
|
|
|
|
|
|
// if the field is not required, and that has a blank value
|
|
|
if (!field.required && !field.must && !field.value) {
|
|
|
if (!_checkable(el)) {
|
|
|
me._validatedField(el, field, {isValid: true});
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
me._checkRule(el, field);
|
|
|
return field.isValid;
|
|
|
},
|
|
|
|
|
|
_debug: function(type, messages) {
|
|
|
if (window.console && this.options.debug) {
|
|
|
console[type](messages);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Detecting whether the value of an element that matches a rule
|
|
|
*
|
|
|
* @method test
|
|
|
* @param {Element} el - input element
|
|
|
* @param {String} rule - rule name
|
|
|
*/
|
|
|
test: function(el, rule) {
|
|
|
var me = this,
|
|
|
ret,
|
|
|
parts = rRule.exec(rule),
|
|
|
field,
|
|
|
method,
|
|
|
params;
|
|
|
|
|
|
if (parts) {
|
|
|
method = parts[1];
|
|
|
if (method in me.rules) {
|
|
|
params = parts[2] || parts[3];
|
|
|
params = params ? params.split(', ') : undefined;
|
|
|
field = me.getField(el, true);
|
|
|
field._r = method;
|
|
|
field.value = field.getValue();
|
|
|
ret = me.rules[method].call(field, el, params);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return ret === true || ret === undefined || ret === null;
|
|
|
},
|
|
|
|
|
|
_getDisplay: function(el, str) {
|
|
|
return !isString(str) ? isFunction(str) ? str.call(this, el) : '' : str;
|
|
|
},
|
|
|
|
|
|
_getMsgOpt: function(obj, field) {
|
|
|
var opt = field ? field : this.options;
|
|
|
return $.extend({
|
|
|
type: 'error',
|
|
|
pos: _getPos(opt.msgClass),
|
|
|
target: opt.target,
|
|
|
wrapper: opt.msgWrapper,
|
|
|
style: opt.msgStyle,
|
|
|
cls: opt.msgClass,
|
|
|
arrow: opt.msgArrow,
|
|
|
icon: opt.msgIcon
|
|
|
}, isString(obj) ? {msg: obj} : obj);
|
|
|
},
|
|
|
|
|
|
_getMsgDOM: function(el, msgOpt) {
|
|
|
var $el = $(el), $msgbox, datafor, tgt, container;
|
|
|
|
|
|
if ( $el.is(INPUT_SELECTOR) ) {
|
|
|
tgt = msgOpt.target || attr(el, DATA_TARGET);
|
|
|
if (tgt) {
|
|
|
tgt = !isFunction(tgt) ? tgt.charAt(0) === '#' ? $(tgt) : this.$el.find(tgt) : tgt.call(this, el);
|
|
|
if (tgt.length) {
|
|
|
if ( tgt.is(INPUT_SELECTOR) ) {
|
|
|
$el = tgt
|
|
|
el = tgt.get(0);
|
|
|
} else if ( tgt.hasClass(CLS_MSG_BOX) ) {
|
|
|
$msgbox = tgt;
|
|
|
} else {
|
|
|
container = tgt;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (!$msgbox) {
|
|
|
datafor = (!_checkable(el) || !el.name) && el.id ? el.id : el.name;
|
|
|
$msgbox = (container || this.$el).find(msgOpt.wrapper + '.' + CLS_MSG_BOX + '[for="' + datafor + '"]');
|
|
|
}
|
|
|
} else {
|
|
|
$msgbox = $el;
|
|
|
}
|
|
|
|
|
|
// Create new message box
|
|
|
if (!msgOpt.hide && !$msgbox.length) {
|
|
|
$msgbox = $('<'+ msgOpt.wrapper + '>').attr({
|
|
|
'class': CLS_MSG_BOX + (msgOpt.cls ? ' ' + msgOpt.cls : ''),
|
|
|
'style': msgOpt.style || undefined,
|
|
|
'for': datafor
|
|
|
});
|
|
|
|
|
|
if (container) {
|
|
|
$msgbox.appendTo(container);
|
|
|
} else {
|
|
|
if ( _checkable(el) ) {
|
|
|
var $parent = $el.parent();
|
|
|
$msgbox.appendTo( $parent.is('label') ? $parent.parent() : $parent );
|
|
|
} else {
|
|
|
$msgbox[!msgOpt.pos || msgOpt.pos === 'right' ? 'insertAfter' : 'insertBefore']($el);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $msgbox;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Show validation message
|
|
|
*
|
|
|
* @method showMsg
|
|
|
* @param {Element} el - input element
|
|
|
* @param {Object} msgOpt
|
|
|
*/
|
|
|
showMsg: function(el, msgOpt, /*INTERNAL*/ field) {
|
|
|
if (!el) return;
|
|
|
var me = this,
|
|
|
opt = me.options,
|
|
|
msgShow,
|
|
|
msgMaker,
|
|
|
temp,
|
|
|
$msgbox;
|
|
|
|
|
|
if (isObject(el) && !el.jquery && !msgOpt) {
|
|
|
$.each(el, function(key, msg) {
|
|
|
var el = me.elements[key] || me.$el.find(_key2selector(key))[0];
|
|
|
me.showMsg(el, msg);
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if ($(el).is(INPUT_SELECTOR)) {
|
|
|
field = field || me.getField(el);
|
|
|
}
|
|
|
|
|
|
if (!(msgMaker = (field || opt).msgMaker)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
msgOpt = me._getMsgOpt(msgOpt, field);
|
|
|
el = (el.name && _checkable(el) ? me.$el.find('input[name="'+ el.name +'"]') : $(el)).get(0);
|
|
|
|
|
|
// ok or tip
|
|
|
if (!msgOpt.msg && msgOpt.type !== 'error') {
|
|
|
temp = attr(el, 'data-' + msgOpt.type);
|
|
|
if (temp !== null) msgOpt.msg = temp;
|
|
|
}
|
|
|
|
|
|
if ( !isString(msgOpt.msg) ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
$msgbox = me._getMsgDOM(el, msgOpt);
|
|
|
|
|
|
!rPos.test($msgbox[0].className) && $msgbox.addClass(msgOpt.cls);
|
|
|
if ( isIE === 6 && msgOpt.pos === 'bottom' ) {
|
|
|
$msgbox[0].style.marginTop = $(el).outerHeight() + 'px';
|
|
|
}
|
|
|
$msgbox.html( msgMaker.call(me, msgOpt) )[0].style.display = '';
|
|
|
|
|
|
if (isFunction(msgShow = field && field.msgShow || opt.msgShow)) {
|
|
|
msgShow.call(me, $msgbox, msgOpt.type);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Hide validation message
|
|
|
*
|
|
|
* @method hideMsg
|
|
|
* @param {Element} el - input element
|
|
|
* @param {Object} msgOpt optional
|
|
|
*/
|
|
|
hideMsg: function(el, msgOpt, /*INTERNAL*/ field) {
|
|
|
var me = this,
|
|
|
opt = me.options,
|
|
|
msgHide,
|
|
|
$msgbox;
|
|
|
|
|
|
el = $(el).get(0);
|
|
|
if ($(el).is(INPUT_SELECTOR)) {
|
|
|
field = field || me.getField(el);
|
|
|
if (field) {
|
|
|
if (field.isValid || me.reseting) attr(el, ARIA_INVALID, null);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
msgOpt = me._getMsgOpt(msgOpt, field);
|
|
|
msgOpt.hide = true;
|
|
|
|
|
|
$msgbox = me._getMsgDOM(el, msgOpt);
|
|
|
if (!$msgbox.length) return;
|
|
|
|
|
|
if ( isFunction(msgHide = field && field.msgHide || opt.msgHide) ) {
|
|
|
msgHide.call(me, $msgbox, msgOpt.type);
|
|
|
} else {
|
|
|
$msgbox[0].style.display = 'none';
|
|
|
$msgbox[0].innerHTML = '';
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Get field information
|
|
|
*
|
|
|
* @method getField
|
|
|
* @param {Element} - input element
|
|
|
* @return {Object} field
|
|
|
*/
|
|
|
getField: function(el, must) {
|
|
|
var me = this,
|
|
|
key,
|
|
|
field;
|
|
|
|
|
|
if (isString(el)) {
|
|
|
key = el;
|
|
|
el = undefined;
|
|
|
} else {
|
|
|
if (attr(el, DATA_RULE)) {
|
|
|
return me._parse(el);
|
|
|
}
|
|
|
if (el.id && '#' + el.id in me.fields || !el.name) {
|
|
|
key = '#' + el.id;
|
|
|
} else {
|
|
|
key = el.name;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ( (field = me.fields[key]) || must && (field = new me.Field(key)) ) {
|
|
|
field.element = el;
|
|
|
}
|
|
|
|
|
|
return field;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Config a field
|
|
|
*
|
|
|
* @method: setField
|
|
|
* @param {String} key
|
|
|
* @param {Object} obj
|
|
|
*/
|
|
|
setField: function(key, obj) {
|
|
|
var fields = {};
|
|
|
|
|
|
if (!key) return;
|
|
|
|
|
|
// update this field
|
|
|
if (isString(key)) {
|
|
|
fields[key] = obj;
|
|
|
}
|
|
|
// update fields
|
|
|
else {
|
|
|
fields = key;
|
|
|
}
|
|
|
|
|
|
this._initFields(fields);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Detecting whether the form is valid
|
|
|
*
|
|
|
* @method isFormValid
|
|
|
* @return {Boolean}
|
|
|
*/
|
|
|
isFormValid: function() {
|
|
|
var fields = this.fields, k, field;
|
|
|
for (k in fields) {
|
|
|
field = fields[k];
|
|
|
if (!field._rules || !field.required && !field.must && !field.value) continue;
|
|
|
if (!field.isValid) return false;
|
|
|
}
|
|
|
return true;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Prevent submission form
|
|
|
*
|
|
|
* @method holdSubmit
|
|
|
* @param {Boolean} hold - If set to false, will release the hold
|
|
|
*/
|
|
|
holdSubmit: function(hold) {
|
|
|
this.submiting = hold === undefined || hold;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Clean validation messages
|
|
|
*
|
|
|
* @method cleanUp
|
|
|
*/
|
|
|
cleanUp: function() {
|
|
|
this._reset(1);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* Destroy the validation
|
|
|
*
|
|
|
* @method destroy
|
|
|
*/
|
|
|
destroy: function() {
|
|
|
this._reset(1);
|
|
|
this.$el.off(CLS_NS).removeData(NS);
|
|
|
attr(this.$el[0], NOVALIDATE, this._NOVALIDATE);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Create Field Factory
|
|
|
*
|
|
|
* @class
|
|
|
* @param {Object} context
|
|
|
* @return {Function} Factory
|
|
|
*/
|
|
|
function _createFieldFactory(context) {
|
|
|
function FieldFactory() {
|
|
|
var options = this.options;
|
|
|
for (var i in options) {
|
|
|
if (i in fieldDefaults) this[i] = options[i];
|
|
|
}
|
|
|
$.extend(this, {
|
|
|
_valHook: function() {
|
|
|
return this.element.contentEditable === 'true' ? 'text' : 'val';
|
|
|
},
|
|
|
getValue: function() {
|
|
|
var elem = this.element;
|
|
|
if (elem.type === 'number' && elem.validity && elem.validity.badInput) {
|
|
|
return 'NaN';
|
|
|
}
|
|
|
return $(elem)[this._valHook()]();
|
|
|
},
|
|
|
setValue: function(value) {
|
|
|
$(this.element)[this._valHook()](this.value = value);
|
|
|
},
|
|
|
// Get a range of validation messages
|
|
|
getRangeMsg: function(value, params, suffix) {
|
|
|
if (!params) return;
|
|
|
|
|
|
var me = this,
|
|
|
msg = me.messages[me._r] || '',
|
|
|
result,
|
|
|
p = params[0].split('~'),
|
|
|
e = params[1] === 'false',
|
|
|
a = p[0],
|
|
|
b = p[1],
|
|
|
c = 'rg',
|
|
|
args = [''],
|
|
|
isNumber = trim(value) && +value === +value;
|
|
|
|
|
|
function compare(large, small) {
|
|
|
return !e ? large >= small : large > small;
|
|
|
}
|
|
|
|
|
|
if (p.length === 2) {
|
|
|
if (a && b) {
|
|
|
if (isNumber && compare(value, +a) && compare(+b, value)) {
|
|
|
result = true;
|
|
|
}
|
|
|
args = args.concat(p);
|
|
|
c = e ? 'gtlt' : 'rg';
|
|
|
}
|
|
|
else if (a && !b) {
|
|
|
if (isNumber && compare(value, +a)) {
|
|
|
result = true;
|
|
|
}
|
|
|
args.push(a);
|
|
|
c = e ? 'gt' : 'gte';
|
|
|
}
|
|
|
else if (!a && b) {
|
|
|
if (isNumber && compare(+b, value)) {
|
|
|
result = true;
|
|
|
}
|
|
|
args.push(b);
|
|
|
c = e ? 'lt' : 'lte';
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if (value === +a) {
|
|
|
result = true;
|
|
|
}
|
|
|
args.push(a);
|
|
|
c = 'eq';
|
|
|
}
|
|
|
|
|
|
if (msg) {
|
|
|
if (suffix && msg[c + suffix]) {
|
|
|
c += suffix;
|
|
|
}
|
|
|
args[0] = msg[c];
|
|
|
}
|
|
|
|
|
|
return result || me._rules && ( me._rules[me._i].msg = me.renderMsg.apply(null, args) );
|
|
|
},
|
|
|
// Render message template
|
|
|
renderMsg: function() {
|
|
|
var args = arguments,
|
|
|
tpl = args[0],
|
|
|
i = args.length;
|
|
|
|
|
|
if (!tpl) return;
|
|
|
|
|
|
while (--i) {
|
|
|
tpl = tpl.replace('{' + i + '}', args[i]);
|
|
|
}
|
|
|
|
|
|
return tpl;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
function Field(key, obj, oldField) {
|
|
|
this.key = key;
|
|
|
this.validator = context;
|
|
|
$.extend(this, oldField, obj);
|
|
|
}
|
|
|
|
|
|
FieldFactory.prototype = context;
|
|
|
Field.prototype = new FieldFactory();
|
|
|
|
|
|
return Field;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Create Rules
|
|
|
*
|
|
|
* @class
|
|
|
* @param {Object} obj rules
|
|
|
* @param {Object} context context
|
|
|
*/
|
|
|
function Rules(obj, context) {
|
|
|
if (!isObject(obj)) return;
|
|
|
|
|
|
var k, that = context ? context === true ? this : context : Rules.prototype;
|
|
|
|
|
|
for (k in obj) {
|
|
|
if (_checkRuleName(k)) {
|
|
|
that[k] = _getRule(obj[k]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Create Messages
|
|
|
*
|
|
|
* @class
|
|
|
* @param {Object} obj rules
|
|
|
* @param {Object} context context
|
|
|
*/
|
|
|
function Messages(obj, context) {
|
|
|
if (!isObject(obj)) return;
|
|
|
|
|
|
var k, that = context ? context === true ? this : context : Messages.prototype;
|
|
|
|
|
|
for (k in obj) {
|
|
|
that[k] = obj[k];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Rule converted factory
|
|
|
function _getRule(fn) {
|
|
|
switch ($.type(fn)) {
|
|
|
case 'function':
|
|
|
return fn;
|
|
|
case 'array':
|
|
|
var f = function() {
|
|
|
return fn[0].test(this.value) || fn[1] || false;
|
|
|
};
|
|
|
f.msg = fn[1];
|
|
|
return f;
|
|
|
case 'regexp':
|
|
|
return function() {
|
|
|
return fn.test(this.value);
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Get instance by an element
|
|
|
function _getInstance(el) {
|
|
|
var wrap, k, options;
|
|
|
|
|
|
if (!el || !el.tagName) return;
|
|
|
|
|
|
switch (el.tagName) {
|
|
|
case 'INPUT':
|
|
|
case 'SELECT':
|
|
|
case 'TEXTAREA':
|
|
|
case 'BUTTON':
|
|
|
case 'FIELDSET':
|
|
|
wrap = el.form || $(el).closest('.' + CLS_WRAPPER);
|
|
|
break;
|
|
|
case 'FORM':
|
|
|
wrap = el;
|
|
|
break;
|
|
|
default:
|
|
|
wrap = $(el).closest('.' + CLS_WRAPPER);
|
|
|
}
|
|
|
|
|
|
for (k in preinitialized) {
|
|
|
if ($(wrap).is(k)) {
|
|
|
options = preinitialized[k];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $(wrap).data(NS) || $(wrap)[NS](options).data(NS);
|
|
|
}
|
|
|
|
|
|
// Get custom rules on the node
|
|
|
function _getDataRule(el, method) {
|
|
|
var fn = trim(attr(el, DATA_RULE + '-' + method));
|
|
|
|
|
|
if ( fn && (fn = new Function('return ' + fn)()) ) {
|
|
|
return _getRule(fn);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Get custom messages on the node
|
|
|
function _getDataMsg(el, field, m) {
|
|
|
var msg = field.msg,
|
|
|
item = field._r;
|
|
|
|
|
|
if ( isObject(msg) ) msg = msg[item];
|
|
|
if ( !isString(msg) ) {
|
|
|
msg = attr(el, DATA_MSG + '-' + item) || attr(el, DATA_MSG) || ( m ? isString(m) ? m : m[item] : '');
|
|
|
}
|
|
|
|
|
|
return msg;
|
|
|
}
|
|
|
|
|
|
// Get message position
|
|
|
function _getPos(str) {
|
|
|
var pos;
|
|
|
|
|
|
if (str) pos = rPos.exec(str);
|
|
|
return pos && pos[0];
|
|
|
}
|
|
|
|
|
|
// Check whether the element is checkbox or radio
|
|
|
function _checkable(el) {
|
|
|
return el.tagName === 'INPUT' && el.type === 'checkbox' || el.type === 'radio';
|
|
|
}
|
|
|
|
|
|
// Parse date string to timestamp
|
|
|
function _parseDate(str) {
|
|
|
return Date.parse(str.replace(/\.|\-/g, '/'));
|
|
|
}
|
|
|
|
|
|
// Rule name only allows alphanumeric characters and underscores
|
|
|
function _checkRuleName(name) {
|
|
|
return /^\w+$/.test(name);
|
|
|
}
|
|
|
|
|
|
// Translate field key to jQuery selector.
|
|
|
function _key2selector(key) {
|
|
|
var isID = key.charAt(0) === '#';
|
|
|
key = key.replace(/([:.{(|)}/\[\]])/g, '\\$1');
|
|
|
return isID ? key : '[name="'+ key +'"]:first';
|
|
|
}
|
|
|
|
|
|
|
|
|
// Fixed a issue cause by refresh page in IE.
|
|
|
$(window).on('beforeunload', function(){
|
|
|
this.focus();
|
|
|
});
|
|
|
|
|
|
$(document)
|
|
|
.on('click', ':submit', function(){
|
|
|
var input = this, attrNode;
|
|
|
if (!input.form) return;
|
|
|
// Shim for "formnovalidate"
|
|
|
attrNode = input.getAttributeNode('formnovalidate');
|
|
|
if (attrNode && attrNode.nodeValue !== null || attr(input, NOVALIDATE)!== null) {
|
|
|
novalidateonce = true;
|
|
|
}
|
|
|
})
|
|
|
// Automatic initializing form validation
|
|
|
.on('focusin submit validate', 'form,.'+CLS_WRAPPER, function(e) {
|
|
|
if ( attr(this, NOVALIDATE) !== null ) return;
|
|
|
var $form = $(this), me;
|
|
|
|
|
|
if ( !$form.data(NS) && (me = _getInstance(this)) ) {
|
|
|
if ( !$.isEmptyObject(me.fields) ) {
|
|
|
// Execute event handler
|
|
|
if (e.type === 'focusin') {
|
|
|
me._focusin(e);
|
|
|
} else {
|
|
|
me._submit(e);
|
|
|
}
|
|
|
} else {
|
|
|
attr(this, NOVALIDATE, NOVALIDATE);
|
|
|
$form.off(CLS_NS).removeData(NS);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
new Messages({
|
|
|
fallback: 'This field is not valid.',
|
|
|
loading: 'Validating...'
|
|
|
});
|
|
|
|
|
|
|
|
|
// Built-in rules (global)
|
|
|
new Rules({
|
|
|
|
|
|
/**
|
|
|
* required
|
|
|
*
|
|
|
* @example:
|
|
|
required
|
|
|
required(jqSelector)
|
|
|
required(anotherRule)
|
|
|
required(not, -1)
|
|
|
required(from, .contact)
|
|
|
*/
|
|
|
required: function(element, params) {
|
|
|
var me = this,
|
|
|
val = trim(me.value),
|
|
|
isValid = true;
|
|
|
|
|
|
if (params) {
|
|
|
if ( params.length === 1 ) {
|
|
|
if ( !_checkRuleName(params[0]) ) {
|
|
|
if (!val && !$(params[0], me.$el).length ) {
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
else if ( me.rules[params[0]] ) {
|
|
|
if ( !val && !me.test(element, params[0]) ) {
|
|
|
return null;
|
|
|
}
|
|
|
me._r = 'required'
|
|
|
}
|
|
|
}
|
|
|
else if ( params[0] === 'not' ) {
|
|
|
$.each(params.slice(1), function() {
|
|
|
return (isValid = val !== trim(this));
|
|
|
});
|
|
|
}
|
|
|
else if ( params[0] === 'from' ) {
|
|
|
var $elements = me.$el.find(params[1]),
|
|
|
VALIDATED = '_validated_',
|
|
|
ret;
|
|
|
|
|
|
isValid = $elements.filter(function(){
|
|
|
var field = me.getField(this);
|
|
|
return field && !!trim(field.getValue());
|
|
|
}).length >= (params[2] || 1);
|
|
|
|
|
|
if (isValid) {
|
|
|
if (!val) ret = null;
|
|
|
} else {
|
|
|
ret = _getDataMsg($elements[0], me) || false;
|
|
|
}
|
|
|
|
|
|
if ( !$(element).data(VALIDATED) ) {
|
|
|
$elements.data(VALIDATED, 1).each(function(){
|
|
|
if (element !== this) {
|
|
|
me._validate(this);
|
|
|
}
|
|
|
}).removeData(VALIDATED);
|
|
|
}
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return isValid && !!val;
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* integer
|
|
|
*
|
|
|
* @example:
|
|
|
integer
|
|
|
integer[+]
|
|
|
integer[+0]
|
|
|
integer[-]
|
|
|
integer[-0]
|
|
|
*/
|
|
|
integer: function(element, params) {
|
|
|
var re, z = '0|',
|
|
|
p = '[1-9]\\d*',
|
|
|
key = params ? params[0] : '*';
|
|
|
|
|
|
switch (key) {
|
|
|
case '+':
|
|
|
re = p;
|
|
|
break;
|
|
|
case '-':
|
|
|
re = '-' + p;
|
|
|
break;
|
|
|
case '+0':
|
|
|
re = z + p;
|
|
|
break;
|
|
|
case '-0':
|
|
|
re = z + '-' + p;
|
|
|
break;
|
|
|
default:
|
|
|
re = z + '-?' + p;
|
|
|
}
|
|
|
re = '^(?:' + re + ')$';
|
|
|
|
|
|
return new RegExp(re).test(this.value) || (this.messages.integer && this.messages.integer[key]);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* match another field
|
|
|
*
|
|
|
* @example:
|
|
|
match[password] Match the password field (two values must be the same)
|
|
|
match[eq, password] Ditto
|
|
|
match[neq, count] The value must be not equal to the value of the count field
|
|
|
match[lt, count] The value must be less than the value of the count field
|
|
|
match[lte, count] The value must be less than or equal to the value of the count field
|
|
|
match[gt, count] The value must be greater than the value of the count field
|
|
|
match[gte, count] The value must be greater than or equal to the value of the count field
|
|
|
match[gte, startDate, date]
|
|
|
match[gte, startTime, time]
|
|
|
**/
|
|
|
match: function(element, params) {
|
|
|
if (!params) return;
|
|
|
|
|
|
var me = this,
|
|
|
isValid = true,
|
|
|
a, b,
|
|
|
key, msg, type = 'eq', parser,
|
|
|
selector2, elem2, field2;
|
|
|
|
|
|
if (params.length === 1) {
|
|
|
key = params[0];
|
|
|
} else {
|
|
|
type = params[0];
|
|
|
key = params[1];
|
|
|
}
|
|
|
|
|
|
selector2 = _key2selector(key);
|
|
|
elem2 = me.$el.find(selector2)[0];
|
|
|
// If the compared field is not exist
|
|
|
if (!elem2) return;
|
|
|
field2 = me.getField(elem2);
|
|
|
a = me.value;
|
|
|
b = field2.getValue();
|
|
|
|
|
|
if (!me._match) {
|
|
|
me.$el.on('valid'+CLS_NS_FIELD+CLS_NS, selector2, function(){
|
|
|
$(element).trigger('validate');
|
|
|
});
|
|
|
me._match = field2._match = 1;
|
|
|
}
|
|
|
|
|
|
// If both fields are blank
|
|
|
if (!me.required && a === '' && b === '') {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
parser = params[2];
|
|
|
if (parser) {
|
|
|
if (/^date(time)?$/i.test(parser)) {
|
|
|
a = _parseDate(a);
|
|
|
b = _parseDate(b);
|
|
|
} else if (parser === 'time') {
|
|
|
a = +a.replace(/:/g, '');
|
|
|
b = +b.replace(/:/g, '');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If the compared field is incorrect, we only ensure that this field is correct.
|
|
|
if (type !== 'eq' && !isNaN(+a) && isNaN(+b)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
switch (type) {
|
|
|
case 'lt':
|
|
|
isValid = +a < +b; break;
|
|
|
case 'lte':
|
|
|
isValid = +a <= +b; break;
|
|
|
case 'gte':
|
|
|
isValid = +a >= +b; break;
|
|
|
case 'gt':
|
|
|
isValid = +a > +b; break;
|
|
|
case 'neq':
|
|
|
isValid = a !== b; break;
|
|
|
default:
|
|
|
isValid = a === b;
|
|
|
}
|
|
|
|
|
|
return isValid || (
|
|
|
isObject(me.messages.match)
|
|
|
&& me.messages.match[type].replace( '{1}', me._getDisplay( elem2, field2.display || key ) )
|
|
|
);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* range numbers
|
|
|
*
|
|
|
* @example:
|
|
|
range[0~99] Number 0-99
|
|
|
range[0~] Number greater than or equal to 0
|
|
|
range[~100] Number less than or equal to 100
|
|
|
**/
|
|
|
range: function(element, params) {
|
|
|
return this.getRangeMsg(this.value, params);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* how many checkbox or radio inputs that checked
|
|
|
*
|
|
|
* @example:
|
|
|
checked; no empty, same to required
|
|
|
checked[1~3] 1-3 items
|
|
|
checked[1~] greater than 1 item
|
|
|
checked[~3] less than 3 items
|
|
|
checked[3] 3 items
|
|
|
**/
|
|
|
checked: function(element, params) {
|
|
|
if ( !_checkable(element) ) return;
|
|
|
|
|
|
var me = this,
|
|
|
elem, count;
|
|
|
|
|
|
if (element.name) {
|
|
|
count = me.$el.find('input[name="' + element.name + '"]').filter(function() {
|
|
|
var el = this;
|
|
|
if (!elem && _checkable(el)) elem = el;
|
|
|
return !el.disabled && el.checked;
|
|
|
}).length;
|
|
|
} else {
|
|
|
elem = element;
|
|
|
count = elem.checked;
|
|
|
}
|
|
|
|
|
|
if (params) {
|
|
|
return me.getRangeMsg(count, params);
|
|
|
} else {
|
|
|
return !!count || _getDataMsg(elem, me, '') || me.messages.required || false;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* length of a characters (You can pass the second parameter "true", will calculate the length in bytes)
|
|
|
*
|
|
|
* @example:
|
|
|
length[6~16] 6-16 characters
|
|
|
length[6~] Greater than 6 characters
|
|
|
length[~16] Less than 16 characters
|
|
|
length[~16, true] Less than 16 characters, non-ASCII characters calculating two-character
|
|
|
**/
|
|
|
length: function(element, params) {
|
|
|
var value = this.value,
|
|
|
len = (params[1] === 'true' ? value.replace(rDoubleBytes, 'xx') : value).length;
|
|
|
|
|
|
return this.getRangeMsg(len, params, (params[1] ? '_2' : ''));
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* remote validation
|
|
|
*
|
|
|
* @description
|
|
|
* remote([get:]url [, name1, [name2 ...]]);
|
|
|
* Adaptation three kinds of results (Front for the successful, followed by a failure):
|
|
|
1. text:
|
|
|
'' 'Error Message'
|
|
|
2. json:
|
|
|
{"ok": ""} {"error": "Error Message"}
|
|
|
3. json wrapper:
|
|
|
{"status": 1, "data": {"ok": ""}} {"status": 1, "data": {"error": "Error Message"}}
|
|
|
* @example
|
|
|
The simplest: remote(path/to/server);
|
|
|
With parameters: remote(path/to/server, name1, name2, ...);
|
|
|
By GET: remote(get:path/to/server, name1, name2, ...);
|
|
|
Name proxy: remote(path/to/server, name1, proxyname2:name2, proxyname3:#id3, ...)
|
|
|
Query String remote(path/to/server, foo=1&bar=2, name1, name2, ...)
|
|
|
CORS remote(cors:path/to/server)
|
|
|
JSONP remote(jsonp:path/to/server)
|
|
|
*/
|
|
|
remote: function(element, params) {
|
|
|
if (!params) return;
|
|
|
|
|
|
var me = this,
|
|
|
arr = rAjaxType.exec(params[0]),
|
|
|
rule = me._rules[me._i],
|
|
|
data = {},
|
|
|
queryString = '',
|
|
|
url = arr[3],
|
|
|
type = arr[2] || 'POST', // GET / POST
|
|
|
rType = (arr[1]||'').toLowerCase(), // CORS / JSONP
|
|
|
dataType;
|
|
|
|
|
|
rule.must = true;
|
|
|
data[element.name] = me.value;
|
|
|
|
|
|
// There are extra fields
|
|
|
if (params[1]) {
|
|
|
$.map(params.slice(1), function(name) {
|
|
|
var arr, key;
|
|
|
if (~name.indexOf('=')) {
|
|
|
queryString += '&' + name;
|
|
|
} else {
|
|
|
arr = name.split(':');
|
|
|
name = trim(arr[0]);
|
|
|
key = trim(arr[1]) || name;
|
|
|
data[ name ] = me.$el.find( _key2selector(key) ).val();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
data = $.param(data) + queryString;
|
|
|
if (!me.must && rule.data && rule.data === data) {
|
|
|
return rule.result;
|
|
|
}
|
|
|
|
|
|
// Cross-domain request, force jsonp dataType
|
|
|
if (rType !== 'cors' && /^https?:/.test(url) && !~url.indexOf(location.host)) {
|
|
|
dataType = 'jsonp';
|
|
|
}
|
|
|
|
|
|
// Asynchronous validation need return jqXHR objects
|
|
|
return $.ajax({
|
|
|
url: url,
|
|
|
type: type,
|
|
|
data: data,
|
|
|
dataType: dataType
|
|
|
});
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* filter characters, direct filtration without prompting error (support custom regular expressions)
|
|
|
*
|
|
|
* @example
|
|
|
* filter filtering unsafe characters
|
|
|
* filter(regexp) filtering the "regexp" matched characters
|
|
|
*/
|
|
|
filter: function(element, params) {
|
|
|
var value = this.value,
|
|
|
temp = value.replace( params ? (new RegExp('[' + params[0] + ']', 'gm')) : rUnsafe, '' );
|
|
|
if (temp !== value) this.setValue(temp);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Config global options
|
|
|
*
|
|
|
* @static config
|
|
|
* @param {Object} options
|
|
|
*/
|
|
|
Validator.config = function(key, value) {
|
|
|
if (isObject(key)) {
|
|
|
$.each(key, _config);
|
|
|
}
|
|
|
else if (isString(key)) {
|
|
|
_config(key, value);
|
|
|
}
|
|
|
|
|
|
function _config(k, o) {
|
|
|
if (k === 'rules') {
|
|
|
new Rules(o);
|
|
|
}
|
|
|
else if (k === 'messages') {
|
|
|
new Messages(o);
|
|
|
}
|
|
|
else if (k in fieldDefaults) {
|
|
|
fieldDefaults[k] = o;
|
|
|
}
|
|
|
else {
|
|
|
defaults[k] = o;
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Config themes
|
|
|
*
|
|
|
* @static setTheme
|
|
|
* @param {String|Object} name
|
|
|
* @param {Object} obj
|
|
|
* @example
|
|
|
.setTheme( themeName, themeOptions )
|
|
|
.setTheme( multiThemes )
|
|
|
*/
|
|
|
Validator.setTheme = function(name, obj) {
|
|
|
if ( isObject(name) ) {
|
|
|
$.extend(true, themes, name);
|
|
|
}
|
|
|
else if ( isString(name) && isObject(obj) ) {
|
|
|
themes[name] = $.extend(themes[name], obj);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Resource loader
|
|
|
*
|
|
|
* @static load
|
|
|
* @param {String} str
|
|
|
* @example
|
|
|
.load('local=zh-CN') // load: local/zh-CN.js and jquery.validator.css
|
|
|
.load('local=zh-CN&css=') // load: local/zh-CN.js
|
|
|
.load('local&css') // load: local/en.js (set <html lang="en">) and jquery.validator.css
|
|
|
.load('local') // dito
|
|
|
*/
|
|
|
Validator.load = function(str) {
|
|
|
if (!str) return;
|
|
|
var doc = document,
|
|
|
params = {},
|
|
|
node = doc.scripts[0],
|
|
|
dir, el, ONLOAD;
|
|
|
|
|
|
str.replace(/([^?=&]+)=([^&#]*)/g, function(m, key, value){
|
|
|
params[key] = value;
|
|
|
});
|
|
|
|
|
|
dir = params.dir || Validator.dir;
|
|
|
|
|
|
if (!Validator.css && params.css !== '') {
|
|
|
el = doc.createElement('link');
|
|
|
el.rel = 'stylesheet';
|
|
|
el.href = Validator.css = dir + 'jquery.validator.css';
|
|
|
node.parentNode.insertBefore(el, node);
|
|
|
}
|
|
|
if (!Validator.local && ~str.indexOf('local') && params.local !== '') {
|
|
|
Validator.local = (params.local || doc.documentElement.lang || 'en').replace('_','-');
|
|
|
Validator.pending = 1;
|
|
|
el = doc.createElement('script');
|
|
|
el.src = dir + 'local/' + Validator.local + '.js';
|
|
|
ONLOAD = 'onload' in el ? 'onload' : 'onreadystatechange';
|
|
|
el[ONLOAD] = function() {
|
|
|
if (!el.readyState || /loaded|complete/.test(el.readyState)) {
|
|
|
el = el[ONLOAD] = null;
|
|
|
delete Validator.pending;
|
|
|
$(window).triggerHandler('validatorready');
|
|
|
}
|
|
|
};
|
|
|
node.parentNode.insertBefore(el, node);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// Auto loading resources
|
|
|
(function(){
|
|
|
var scripts = document.scripts,
|
|
|
i = scripts.length, node, arr,
|
|
|
re = /(.*validator(?:\.min)?.js)(\?.*(?:local|css|dir)(?:=[\w\-]*)?)?/;
|
|
|
|
|
|
while (i-- && !arr) {
|
|
|
node = scripts[i];
|
|
|
arr = (node.hasAttribute ? node.src : node.getAttribute('src',4)||'').match(re);
|
|
|
}
|
|
|
|
|
|
if (!arr) return;
|
|
|
Validator.dir = arr[1].split('/').slice(0, -1).join('/')+'/';
|
|
|
Validator.load(arr[2]);
|
|
|
})();
|
|
|
|
|
|
return $[NS] = Validator;
|
|
|
}));
|
|
|
|