/*! nice-validator 1.1.1 * (c) 2012-2017 jony zhang , 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 = '' + opt.arrow; if (opt.result) { $.each(opt.result, function(i, obj){ html += '' + opt.icon + '' + obj.msg + ''; }); } else { html += opt.icon + '' + opt.msg + ''; } html += ''; return html; }, msgwrapper: 'span', msgarrow: '', msgicon: '', 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) { if ( events && events[name] && $.map(events[name], function(e){ return ~e.namespace.indexof(namespace) ? 1 : null; }).length ) { return true; } return false; } }, _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' && !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; } } 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 ( _checkable(el) ) { var $parent = $el.parent(); $msgbox.appendto( $parent.is('label') ? $parent.parent() : $parent ); } else { if (container) { $msgbox.appendto(container); } 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 = null; } }, /** * 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(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; } } } 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[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, 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; } msg = me.messages.match[type].replace( '{1}', me._getdisplay( element, field2.display || key ) ); switch (type) { case 'lt': return (+a < +b) || msg; case 'lte': return (+a <= +b) || msg; case 'gte': return (+a >= +b) || msg; case 'gt': return (+a > +b) || msg; case 'neq': return (a !== b) || msg; default: return (a === b) || msg; } }, /** * 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; } }, /** * 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, ...) */ 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 ) 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; }));