12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138 |
- ;(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,
- remoteDataType: 'cors'
- },
- 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] || this.validator.options.remoteDataType || '').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;
- }));
|