jquery.validator.js 72 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141
  1. /*! nice-validator 1.1.5
  2. * (c) 2012-2020 Jony Zhang <niceue@live.com>, MIT Licensed
  3. * https://github.com/niceue/nice-validator
  4. */
  5. ;(function(factory) {
  6. typeof module === 'object' && module.exports ? module.exports = factory( require( 'jquery' ) ) :
  7. typeof define === 'function' && define.amd ? define(['jquery'], factory) :
  8. factory(jQuery);
  9. }(function($, undefined) {
  10. 'use strict';
  11. var NS = 'validator',
  12. CLS_NS = '.' + NS,
  13. CLS_NS_RULE = '.rule',
  14. CLS_NS_FIELD = '.field',
  15. CLS_NS_FORM = '.form',
  16. CLS_WRAPPER = 'nice-' + NS,
  17. CLS_MSG_BOX = 'msg-box',
  18. ARIA_INVALID = 'aria-invalid',
  19. DATA_RULE = 'data-rule',
  20. DATA_MSG = 'data-msg',
  21. DATA_TIP = 'data-tip',
  22. DATA_OK = 'data-ok',
  23. DATA_TIMELY = 'data-timely',
  24. DATA_TARGET = 'data-target',
  25. DATA_DISPLAY = 'data-display',
  26. DATA_MUST = 'data-must',
  27. NOVALIDATE = 'novalidate',
  28. INPUT_SELECTOR = ':verifiable',
  29. rRules = /(&)?(!)?\b(\w+)(?:\[\s*(.*?\]?)\s*\]|\(\s*(.*?\)?)\s*\))?\s*(;|\|)?/g,
  30. rRule = /(\w+)(?:\[\s*(.*?\]?)\s*\]|\(\s*(.*?\)?)\s*\))?/,
  31. rDisplay = /(?:([^:;\(\[]*):)?(.*)/,
  32. rDoubleBytes = /[^\x00-\xff]/g,
  33. rPos = /top|right|bottom|left/,
  34. rAjaxType = /(?:(cors|jsonp):)?(?:(post|get):)?(.+)/i,
  35. rUnsafe = /[<>'"`\\]|&#x?\d+[A-F]?;?|%3[A-F]/gmi,
  36. noop = $.noop,
  37. proxy = $.proxy,
  38. trim = $.trim,
  39. isFunction = $.isFunction,
  40. isString = function(s) {
  41. return typeof s === 'string';
  42. },
  43. isObject = function(o) {
  44. return o && Object.prototype.toString.call(o) === '[object Object]';
  45. },
  46. isIE = document.documentMode || +(navigator.userAgent.match(/MSIE (\d+)/) && RegExp.$1),
  47. attr = function(el, key, value) {
  48. if (!el || !el.tagName) return null;
  49. if (value !== undefined) {
  50. if (value === null) el.removeAttribute(key);
  51. else el.setAttribute(key, '' + value);
  52. } else {
  53. return el.getAttribute(key);
  54. }
  55. },
  56. novalidateonce,
  57. preinitialized = {},
  58. defaults = {
  59. debug: 0,
  60. theme: 'default',
  61. ignore: '',
  62. focusInvalid: true,
  63. focusCleanup: false,
  64. stopOnError: false,
  65. beforeSubmit: null,
  66. valid: null,
  67. invalid: null,
  68. validation: null,
  69. formClass: 'n-default',
  70. validClass: 'n-valid',
  71. invalidClass: 'n-invalid',
  72. bindClassTo: null
  73. },
  74. fieldDefaults = {
  75. timely: 1,
  76. display: null,
  77. target: null,
  78. ignoreBlank: false,
  79. showOk: true,
  80. // Translate ajax response to validation result
  81. dataFilter: function (data) {
  82. if ( isString(data) || ( isObject(data) && ('error' in data || 'ok' in data) ) ) {
  83. return data;
  84. }
  85. },
  86. msgMaker: function(opt) {
  87. var html;
  88. html = '<span role="alert" class="msg-wrap n-'+ opt.type + '">' + opt.arrow;
  89. if (opt.result) {
  90. $.each(opt.result, function(i, obj){
  91. html += '<span class="n-'+ obj.type +'">' + opt.icon + '<span class="n-msg">' + obj.msg + '</span></span>';
  92. });
  93. } else {
  94. html += opt.icon + '<span class="n-msg">' + opt.msg + '</span>';
  95. }
  96. html += '</span>';
  97. return html;
  98. },
  99. msgWrapper: 'span',
  100. msgArrow: '',
  101. msgIcon: '<span class="n-icon"></span>',
  102. msgClass: 'n-right',
  103. msgStyle: '',
  104. msgShow: null,
  105. msgHide: null
  106. },
  107. themes = {};
  108. /** jQuery Plugin
  109. * @param {Object} options
  110. debug {Boolean} 0 Whether to enable debug mode
  111. timely {Number} 1 Whether to enable timely validation
  112. theme {String} 'default' Theme name
  113. stopOnError {Boolean} false Whether to stop validate when found an error input
  114. focusCleanup {Boolean} false Whether to clean up the field message when focus the field
  115. focusInvalid {Boolean} true Whether to focus the field that is invalid
  116. ignoreBlank {Boolean} false When the field has no value, whether to ignore validation
  117. ignore {jqSelector} '' Ignored fields (Using jQuery selector)
  118. beforeSubmit {Function} Do something before submit form
  119. dataFilter {Function} Convert ajax results
  120. valid {Function} Triggered when the form is valid
  121. invalid {Function} Triggered when the form is invalid
  122. validClass {String} 'n-valid' Add this class name to a valid field
  123. invalidClass {String} 'n-invalid' Add this class name to a invalid field
  124. bindClassTo {jqSelector} ':verifiable' Which element should the className binding to
  125. display {Function} Callback function to get dynamic display
  126. target {Function} Callback function to get dynamic target
  127. msgShow {Function} Trigger this callback when show message
  128. msgHide {Function} Trigger this callback when hide message
  129. msgWrapper {String} 'span' Message wrapper tag name
  130. msgMaker {Function} Callback function to make message HTML
  131. msgArrow {String} Message arrow template
  132. msgIcon {String} Message icon template
  133. msgStyle {String} Custom message css style
  134. msgClass {String} Additional added to the message class names
  135. formClass {String} Additional added to the form class names
  136. messages {Object} Custom messages for the current instance
  137. rules {Object} Custom rules for the current instance
  138. fields {Object} Field validation configuration
  139. {String} key name|#id
  140. {String|Object} value Rule string or an object which can pass more arguments
  141. fields[key][rule] {String} Rule string
  142. fields[key][display] {String|Function}
  143. fields[key][tip] {String} Custom tip message
  144. fields[key][ok] {String} Custom success message
  145. fields[key][msg] {Object} Custom error message
  146. fields[key][msgStyle] {String} Custom message style
  147. fields[key][msgClass] {String} A className which added to message placeholder element
  148. fields[key][msgWrapper] {String} Tag name of the message placeholder element
  149. fields[key][msgMaker] {Function} A function to custom message HTML
  150. fields[key][dataFilter] {Function} A function to convert ajax results
  151. fields[key][valid] {Function} A function triggered when field is valid
  152. fields[key][invalid] {Function} A function triggered when field is invalid
  153. fields[key][must] {Boolean} If set true, we always check the field even has remote checking
  154. fields[key][timely] {Boolean} Whether to enable timely validation
  155. fields[key][target] {jqSelector} Define placement of a message
  156. */
  157. $.fn.validator = function(options) {
  158. var that = this,
  159. args = arguments;
  160. if (that.is(INPUT_SELECTOR)) return that;
  161. if (!that.is('form')) that = this.find('form');
  162. if (!that.length) that = this;
  163. that.each(function() {
  164. var instance = $(this).data(NS);
  165. if (instance) {
  166. if ( isString(options) ) {
  167. if ( options.charAt(0) === '_' ) return;
  168. instance[options].apply(instance, [].slice.call(args, 1));
  169. }
  170. else if (options) {
  171. instance._reset(true);
  172. instance._init(this, options);
  173. }
  174. } else {
  175. new Validator(this, options);
  176. }
  177. });
  178. return this;
  179. };
  180. // Validate a field, or an area
  181. $.fn.isValid = function(callback, hideMsg) {
  182. var me = _getInstance(this[0]),
  183. hasCallback = isFunction(callback),
  184. ret, opt;
  185. if (!me) return true;
  186. if (!hasCallback && hideMsg === undefined) hideMsg = callback;
  187. me.checkOnly = !!hideMsg;
  188. opt = me.options;
  189. ret = me._multiValidate(
  190. this.is(INPUT_SELECTOR) ? this : this.find(INPUT_SELECTOR),
  191. function(isValid){
  192. if (!isValid && opt.focusInvalid && !me.checkOnly) {
  193. // navigate to the error element
  194. me.$el.find('[' + ARIA_INVALID + ']:first').focus();
  195. }
  196. if (hasCallback) {
  197. if (callback.length) {
  198. callback(isValid);
  199. } else if (isValid) {
  200. callback();
  201. }
  202. }
  203. me.checkOnly = false;
  204. }
  205. );
  206. // If you pass a callback, we maintain the jQuery object chain
  207. return hasCallback ? this : ret;
  208. };
  209. $.extend($.expr.pseudos || $.expr[':'], {
  210. // A faster selector than ":input:not(:submit,:button,:reset,:image,:disabled,[contenteditable])"
  211. verifiable: function(elem) {
  212. var name = elem.nodeName.toLowerCase();
  213. return ( name === 'input' && !({submit: 1, button: 1, reset: 1, image: 1})[elem.type] ||
  214. name === 'select' ||
  215. name === 'textarea' ||
  216. elem.contentEditable === 'true'
  217. ) && !elem.disabled;
  218. },
  219. // any value, but not only whitespace
  220. filled: function(elem) {
  221. return !!trim($(elem).val());
  222. }
  223. });
  224. /**
  225. * Creates a new Validator
  226. *
  227. * @class
  228. * @param {Element} element - form element
  229. * @param {Object} options - options for validator
  230. */
  231. function Validator(element, options) {
  232. var me = this;
  233. if ( !(me instanceof Validator) ) {
  234. return new Validator(element, options);
  235. }
  236. if (Validator.pending) {
  237. $(window).on('validatorready', init);
  238. } else {
  239. init();
  240. }
  241. function init() {
  242. me.$el = $(element);
  243. if (me.$el.length) {
  244. me._init(me.$el[0], options);
  245. }
  246. else if (isString(element)) {
  247. preinitialized[element] = options;
  248. }
  249. }
  250. }
  251. Validator.prototype = {
  252. _init: function(element, options) {
  253. var me = this,
  254. opt, themeOpt, dataOpt;
  255. // Initialization options
  256. if ( isFunction(options) ) {
  257. options = {
  258. valid: options
  259. };
  260. }
  261. options = me._opt = options || {};
  262. dataOpt = attr(element, 'data-'+ NS +'-option');
  263. dataOpt = me._dataOpt = dataOpt && dataOpt.charAt(0) === '{' ? (new Function('return ' + dataOpt))() : {};
  264. themeOpt = me._themeOpt = themes[ options.theme || dataOpt.theme || defaults.theme ];
  265. opt = me.options = $.extend({}, defaults, fieldDefaults, themeOpt, me.options, options, dataOpt);
  266. me.rules = new Rules(opt.rules, true);
  267. me.messages = new Messages(opt.messages, true);
  268. me.Field = _createFieldFactory(me);
  269. me.elements = me.elements || {};
  270. me.deferred = {};
  271. me.errors = {};
  272. me.fields = {};
  273. // Initialization fields
  274. me._initFields(opt.fields);
  275. // Initialization events and make a cache
  276. if ( !me.$el.data(NS) ) {
  277. me.$el.data(NS, me).addClass(CLS_WRAPPER +' '+ opt.formClass)
  278. .on('form-submit-validate', function(e, a, $form, opts, veto) {
  279. me.vetoed = veto.veto = !me.isValid;
  280. me.ajaxFormOptions = opts;
  281. })
  282. .on('submit'+ CLS_NS +' validate'+ CLS_NS, proxy(me, '_submit'))
  283. .on('reset'+ CLS_NS, proxy(me, '_reset'))
  284. .on('showmsg'+ CLS_NS, proxy(me, '_showmsg'))
  285. .on('hidemsg'+ CLS_NS, proxy(me, '_hidemsg'))
  286. .on('focusin'+ CLS_NS + ' click'+ CLS_NS, INPUT_SELECTOR, proxy(me, '_focusin'))
  287. .on('focusout'+ CLS_NS +' validate'+ CLS_NS, INPUT_SELECTOR, proxy(me, '_focusout'))
  288. .on('keyup'+ CLS_NS +' input'+ CLS_NS + ' compositionstart compositionend', INPUT_SELECTOR, proxy(me, '_focusout'))
  289. .on('click'+ CLS_NS, ':radio,:checkbox', 'click', proxy(me, '_focusout'))
  290. .on('change'+ CLS_NS, 'select,input[type="file"]', 'change', proxy(me, '_focusout'));
  291. // cache the novalidate attribute value
  292. me._NOVALIDATE = attr(element, NOVALIDATE);
  293. // Initialization is complete, stop off default HTML5 form validation
  294. // If use "jQuery.attr('novalidate')" in IE7 will complain: "SCRIPT3: Member not found."
  295. attr(element, NOVALIDATE, NOVALIDATE);
  296. }
  297. // Display all messages in target container
  298. if ( isString(opt.target) ) {
  299. me.$el.find(opt.target).addClass('msg-container');
  300. }
  301. },
  302. // Guess whether the form use ajax submit
  303. _guessAjax: function(form) {
  304. var me = this;
  305. if ( !(me.isAjaxSubmit = !!me.options.valid) ) {
  306. // if there is a "valid.form" event
  307. var events = ($._data || $.data)(form, 'events');
  308. me.isAjaxSubmit = issetEvent(events, 'valid', 'form') || issetEvent(events, 'submit', 'form-plugin');
  309. }
  310. function issetEvent(events, name, namespace) {
  311. return !!(
  312. events && events[name]
  313. && $.map(events[name], function(e){
  314. return ~e.namespace.indexOf(namespace) ? 1 : null;
  315. }).length )
  316. }
  317. },
  318. _initFields: function(fields) {
  319. var me = this, k, arr, i,
  320. clear = fields === null;
  321. // Processing field information
  322. if (clear) fields = me.fields;
  323. if ( isObject(fields) ) {
  324. for (k in fields) {
  325. if (~k.indexOf(',')) {
  326. arr = k.split(',');
  327. i = arr.length;
  328. while (i--) {
  329. initField(trim(arr[i]), fields[k]);
  330. }
  331. } else {
  332. initField(k, fields[k]);
  333. }
  334. }
  335. }
  336. // Parsing DOM rules
  337. me.$el.find(INPUT_SELECTOR).each(function() {
  338. me._parse(this);
  339. });
  340. function initField(k, v) {
  341. // delete a field from settings
  342. if ( v === null || clear ) {
  343. var el = me.elements[k];
  344. if (el) me._resetElement(el, true);
  345. delete me.fields[k];
  346. } else {
  347. me.fields[k] = new me.Field(k, isString(v) ? {rule: v} : v, me.fields[k]);
  348. }
  349. }
  350. },
  351. // Parsing a field
  352. _parse: function(el) {
  353. var me = this,
  354. field,
  355. key = el.name,
  356. display,
  357. timely,
  358. dataRule = attr(el, DATA_RULE);
  359. dataRule && attr(el, DATA_RULE, null);
  360. // If the field has passed the key as id mode, or it doesn't has a name
  361. if ( el.id && (
  362. ('#' + el.id in me.fields) ||
  363. !key ||
  364. // If dataRule and element are diffrent from old's, we use ID mode.
  365. (dataRule !== null && (field = me.fields[key]) && dataRule !== field.rule && el.id !== field.key)
  366. )
  367. ) {
  368. key = '#' + el.id;
  369. }
  370. // Generate id
  371. if (!key) {
  372. key = '#' + (el.id = 'N' + String(Math.random()).slice(-12));
  373. }
  374. field = me.getField(key, true);
  375. // The priority of passing parameter by DOM is higher than by JS.
  376. field.rule = dataRule || field.rule;
  377. if (display = attr(el, DATA_DISPLAY)) {
  378. field.display = display;
  379. }
  380. if (field.rule) {
  381. if ( attr(el, DATA_MUST) !== null || /\b(?:match|checked)\b/.test(field.rule) ) {
  382. field.must = true;
  383. }
  384. if ( /\brequired\b/.test(field.rule) ) {
  385. field.required = true;
  386. }
  387. if (timely = attr(el, DATA_TIMELY)) {
  388. field.timely = +timely;
  389. } else if (field.timely > 3) {
  390. attr(el, DATA_TIMELY, field.timely);
  391. }
  392. me._parseRule(field);
  393. field.old = {};
  394. }
  395. if ( isString(field.target) ) {
  396. attr(el, DATA_TARGET, field.target);
  397. }
  398. if ( isString(field.tip) ) {
  399. attr(el, DATA_TIP, field.tip);
  400. }
  401. return me.fields[key] = field;
  402. },
  403. // Parsing field rules
  404. _parseRule: function(field) {
  405. var arr = rDisplay.exec(field.rule);
  406. if (!arr) return;
  407. // current rule index
  408. field._i = 0;
  409. if (arr[1]) {
  410. field.display = arr[1];
  411. }
  412. if (arr[2]) {
  413. field._rules = [];
  414. arr[2].replace(rRules, function(){
  415. var args = arguments;
  416. args[4] = args[4] || args[5];
  417. field._rules.push({
  418. and: args[1] === '&',
  419. not: args[2] === '!',
  420. or: args[6] === '|',
  421. method: args[3],
  422. params: args[4] ? $.map( args[4].split(', '), trim ) : undefined
  423. });
  424. });
  425. }
  426. },
  427. // Verify a zone
  428. _multiValidate: function($inputs, doneCallback){
  429. var me = this,
  430. opt = me.options;
  431. me.hasError = false;
  432. if (opt.ignore) {
  433. $inputs = $inputs.not(opt.ignore);
  434. }
  435. $inputs.each(function() {
  436. me._validate(this);
  437. if (me.hasError && opt.stopOnError) {
  438. // stop the validation
  439. return false;
  440. }
  441. });
  442. // Need to wait for all fields validation complete, especially asynchronous validation
  443. if (doneCallback) {
  444. me.validating = true;
  445. $.when.apply(
  446. null,
  447. $.map(me.deferred, function(v){return v;})
  448. ).done(function(){
  449. doneCallback.call(me, !me.hasError);
  450. me.validating = false;
  451. });
  452. }
  453. // If the form does not contain asynchronous validation, the return value is correct.
  454. // Otherwise, you should detect form validation result through "doneCallback".
  455. return !$.isEmptyObject(me.deferred) ? undefined : !me.hasError;
  456. },
  457. // Validate the whole form
  458. _submit: function(e) {
  459. var me = this,
  460. opt = me.options,
  461. form = e.target,
  462. canSubmit = e.type === 'submit' && form.tagName === 'FORM' && !e.isDefaultPrevented();
  463. e.preventDefault();
  464. if (
  465. novalidateonce && ~(novalidateonce = false) ||
  466. // Prevent duplicate submission
  467. me.submiting ||
  468. // Receive the "validate" event only from the form.
  469. e.type === 'validate' && me.$el[0] !== form ||
  470. // trigger the beforeSubmit callback.
  471. isFunction(opt.beforeSubmit) && opt.beforeSubmit.call(me, form) === false
  472. ) {
  473. return;
  474. }
  475. if (me.isAjaxSubmit === undefined) {
  476. me._guessAjax(form);
  477. }
  478. me._debug('log', '\n<<< event: ' + e.type);
  479. me._reset();
  480. me.submiting = true;
  481. me._multiValidate(
  482. me.$el.find(INPUT_SELECTOR),
  483. function(isValid){
  484. var ret = (isValid || opt.debug === 2) ? 'valid' : 'invalid',
  485. errors;
  486. if (!isValid) {
  487. if (opt.focusInvalid) {
  488. // navigate to the error element
  489. me.$el.find('[' + ARIA_INVALID + ']:first').focus();
  490. }
  491. errors = $.map(me.errors, function(err){return err;});
  492. }
  493. // releasing submit
  494. me.submiting = false;
  495. me.isValid = isValid;
  496. // trigger callback and event
  497. isFunction(opt[ret]) && opt[ret].call(me, form, errors);
  498. me.$el.trigger(ret + CLS_NS_FORM, [form, errors]);
  499. me._debug('log', '>>> ' + ret);
  500. if (!isValid) return;
  501. // For jquery.form plugin
  502. if (me.vetoed) {
  503. $(form).ajaxSubmit(me.ajaxFormOptions);
  504. }
  505. else if (canSubmit && !me.isAjaxSubmit) {
  506. document.createElement('form').submit.call(form);
  507. }
  508. }
  509. );
  510. },
  511. _reset: function(e) {
  512. var me = this;
  513. me.errors = {};
  514. if (e) {
  515. me.reseting = true;
  516. me.$el.find(INPUT_SELECTOR).each( function(){
  517. me._resetElement(this);
  518. });
  519. delete me.reseting;
  520. }
  521. },
  522. _resetElement: function(el, all) {
  523. this._setClass(el, null);
  524. this.hideMsg(el);
  525. },
  526. // Handle events: "focusin/click"
  527. _focusin: function(e) {
  528. var me = this,
  529. opt = me.options,
  530. el = e.target,
  531. timely,
  532. msg;
  533. if ( me.validating || ( e.type==='click' && document.activeElement === el ) ) {
  534. return;
  535. }
  536. if (opt.focusCleanup) {
  537. if ( attr(el, ARIA_INVALID) === 'true' ) {
  538. me._setClass(el, null);
  539. me.hideMsg(el);
  540. }
  541. }
  542. msg = attr(el, DATA_TIP);
  543. if (msg) {
  544. me.showMsg(el, {
  545. type: 'tip',
  546. msg: msg
  547. });
  548. } else {
  549. if (attr(el, DATA_RULE)) {
  550. me._parse(el);
  551. }
  552. if (timely = attr(el, DATA_TIMELY)) {
  553. if ( timely === 8 || timely === 9 ) {
  554. me._focusout(e);
  555. }
  556. }
  557. }
  558. },
  559. // Handle events: "focusout/validate/keyup/click/change/input/compositionstart/compositionend"
  560. _focusout: function(e) {
  561. var me = this,
  562. opt = me.options,
  563. el = e.target,
  564. etype = e.type,
  565. etype0,
  566. focusin = etype === 'focusin',
  567. special = etype === 'validate',
  568. elem,
  569. field,
  570. old,
  571. value,
  572. timestamp,
  573. key, specialKey,
  574. timely,
  575. timer = 0;
  576. if (etype === 'compositionstart') {
  577. me.pauseValidate = true;
  578. }
  579. if (etype === 'compositionend') {
  580. me.pauseValidate = false;
  581. }
  582. if (me.pauseValidate) {
  583. return;
  584. }
  585. // For checkbox and radio
  586. elem = el.name && _checkable(el) ? me.$el.find('input[name="'+ el.name +'"]').get(0) : el;
  587. // Get field
  588. if (!(field = me.getField(elem)) || !field.rule) {
  589. return;
  590. }
  591. // Cache event type
  592. etype0 = field._e;
  593. field._e = etype;
  594. timely = field.timely;
  595. if (!special) {
  596. if (!timely || (_checkable(el) && etype !== 'click')) {
  597. return;
  598. }
  599. value = field.getValue();
  600. // not validate field unless fill a value
  601. if ( field.ignoreBlank && !value && !focusin ) {
  602. me.hideMsg(el);
  603. return;
  604. }
  605. if ( etype === 'focusout' ) {
  606. if (etype0 === 'change') {
  607. return;
  608. }
  609. if ( timely === 2 || timely === 8 ) {
  610. old = field.old;
  611. if (value && old) {
  612. if (field.isValid && !old.showOk) {
  613. me.hideMsg(el);
  614. } else {
  615. me._makeMsg(el, field, old);
  616. }
  617. } else {
  618. return;
  619. }
  620. }
  621. }
  622. else {
  623. if ( timely < 2 && !e.data ) {
  624. return;
  625. }
  626. // mark timestamp to reduce the frequency of the received event
  627. timestamp = +new Date();
  628. if ( timestamp - (el._ts || 0) < 100 ) {
  629. return;
  630. }
  631. el._ts = timestamp;
  632. // handle keyup
  633. if ( etype === 'keyup' ) {
  634. if (etype0 === 'input') {
  635. return;
  636. }
  637. key = e.keyCode;
  638. specialKey = {
  639. 8: 1, // Backspace
  640. 9: 1, // Tab
  641. 16: 1, // Shift
  642. 32: 1, // Space
  643. 46: 1 // Delete
  644. };
  645. // only gets focus, no validation
  646. if ( key === 9 && !value ) {
  647. return;
  648. }
  649. // do not validate, if triggered by these keys
  650. if ( key < 48 && !specialKey[key] ) {
  651. return;
  652. }
  653. }
  654. if ( !focusin ) {
  655. // keyboard events, reducing the frequency of validation
  656. timer = timely <100 ? (etype === 'click' || el.tagName === 'SELECT') ? 0 : 400 : timely;
  657. }
  658. }
  659. }
  660. // if the current field is ignored
  661. if ( opt.ignore && $(el).is(opt.ignore) ) {
  662. return;
  663. }
  664. clearTimeout(field._t);
  665. if (timer) {
  666. field._t = setTimeout(function() {
  667. me._validate(el, field);
  668. }, timer);
  669. } else {
  670. if (special) field.old = {};
  671. me._validate(el, field);
  672. }
  673. },
  674. _setClass: function(el, isValid) {
  675. var $el = $(el), opt = this.options;
  676. if (opt.bindClassTo) {
  677. $el = $el.closest(opt.bindClassTo);
  678. }
  679. $el.removeClass( opt.invalidClass + ' ' + opt.validClass );
  680. if (isValid !== null) {
  681. $el.addClass( isValid ? opt.validClass : opt.invalidClass );
  682. }
  683. },
  684. _showmsg: function(e, type, msg) {
  685. var me = this,
  686. el = e.target;
  687. if ( me.$el.is(el) ) {
  688. if (isObject(type)) {
  689. me.showMsg(type)
  690. }
  691. else if ( type === 'tip' ) {
  692. me.$el.find(INPUT_SELECTOR +'['+ DATA_TIP +']', el).each(function(){
  693. me.showMsg(this, {type: type, msg: msg});
  694. });
  695. }
  696. }
  697. else {
  698. me.showMsg(el, {type: type, msg: msg});
  699. }
  700. },
  701. _hidemsg: function(e) {
  702. var $el = $(e.target);
  703. if ( $el.is(INPUT_SELECTOR) ) {
  704. this.hideMsg($el);
  705. }
  706. },
  707. // Validated a field
  708. _validatedField: function(el, field, ret) {
  709. var me = this,
  710. opt = me.options,
  711. isValid = field.isValid = ret.isValid = !!ret.isValid,
  712. callback = isValid ? 'valid' : 'invalid';
  713. ret.key = field.key;
  714. ret.ruleName = field._r;
  715. ret.id = el.id;
  716. ret.value = field.value;
  717. me.elements[field.key] = ret.element = el;
  718. me.isValid = me.$el[0].isValid = isValid ? me.isFormValid() : isValid;
  719. if (isValid) {
  720. ret.type = 'ok';
  721. } else {
  722. if (me.submiting) {
  723. me.errors[field.key] = ret.msg;
  724. }
  725. me.hasError = true;
  726. }
  727. // cache result
  728. field.old = ret;
  729. // trigger callback
  730. isFunction(field[callback]) && field[callback].call(me, el, ret);
  731. isFunction(opt.validation) && opt.validation.call(me, el, ret);
  732. // trigger event
  733. $(el).attr( ARIA_INVALID, isValid ? null : true )
  734. .trigger( callback + CLS_NS_FIELD, [ret, me] );
  735. me.$el.triggerHandler('validation', [ret, me]);
  736. if (me.checkOnly) return;
  737. // set className
  738. me._setClass(el, ret.skip || ret.type === 'tip' ? null : isValid);
  739. me._makeMsg.apply(me, arguments);
  740. },
  741. _makeMsg: function(el, field, ret) {
  742. // show or hide the message
  743. if (field.msgMaker) {
  744. ret = $.extend({}, ret);
  745. if (field._e === 'focusin') {
  746. ret.type = 'tip';
  747. }
  748. this[ ret.showOk || ret.msg || ret.type === 'tip' ? 'showMsg' : 'hideMsg' ](el, ret, field);
  749. }
  750. },
  751. // Validated a rule
  752. _validatedRule: function(el, field, ret, msgOpt) {
  753. field = field || me.getField(el);
  754. msgOpt = msgOpt || {};
  755. var me = this,
  756. msg,
  757. rule,
  758. method = field._r,
  759. timely = field.timely,
  760. special = timely === 9 || timely === 8,
  761. transfer,
  762. temp,
  763. isValid = false;
  764. // use null to break validation from a field
  765. if (ret === null) {
  766. me._validatedField(el, field, {isValid: true, skip: true});
  767. field._i = 0;
  768. return;
  769. }
  770. else if (ret === undefined) {
  771. transfer = true;
  772. }
  773. else if (ret === true || ret === '') {
  774. isValid = true;
  775. }
  776. else if (isString(ret)) {
  777. msg = ret;
  778. }
  779. else if (isObject(ret)) {
  780. if (ret.error) {
  781. msg = ret.error;
  782. } else {
  783. msg = ret.ok;
  784. isValid = true;
  785. }
  786. }
  787. else {
  788. isValid = !!ret
  789. }
  790. rule = field._rules[field._i];
  791. if (rule.not) {
  792. msg = undefined;
  793. isValid = method === 'required' || !isValid;
  794. }
  795. if (rule.or) {
  796. if (isValid) {
  797. while ( field._i < field._rules.length && field._rules[field._i].or ) {
  798. field._i++;
  799. }
  800. } else {
  801. transfer = true;
  802. }
  803. }
  804. else if (rule.and) {
  805. if (!field.isValid) transfer = true;
  806. }
  807. if (transfer) {
  808. isValid = true;
  809. }
  810. // message analysis, and throw rule level event
  811. else {
  812. if (isValid) {
  813. if (field.showOk !== false) {
  814. temp = attr(el, DATA_OK);
  815. msg = temp === null ? isString(field.ok) ? field.ok : msg : temp;
  816. if (!isString(msg) && isString(field.showOk)) {
  817. msg = field.showOk;
  818. }
  819. if (isString(msg)) {
  820. msgOpt.showOk = isValid;
  821. }
  822. }
  823. }
  824. if (!isValid || special) {
  825. /* rule message priority:
  826. 1. custom DOM message
  827. 2. custom field message;
  828. 3. global defined message;
  829. 4. rule returned message;
  830. 5. default message;
  831. */
  832. msg = (_getDataMsg(el, field, msg || rule.msg || me.messages[method]) || me.messages.fallback).replace(/\{0\|?([^\}]*)\}/, function(m, defaultDisplay){
  833. return me._getDisplay(el, field.display) || defaultDisplay || me.messages[0];
  834. });
  835. }
  836. if (!isValid) field.isValid = isValid;
  837. msgOpt.msg = msg;
  838. $(el).trigger( (isValid ? 'valid' : 'invalid') + CLS_NS_RULE, [method, msg]);
  839. }
  840. if (special && (!transfer || rule.and)) {
  841. if (!isValid && !field._m) field._m = msg;
  842. field._v = field._v || [];
  843. field._v.push({
  844. type: isValid ? !transfer ? 'ok' : 'tip' : 'error',
  845. msg: msg || rule.msg
  846. });
  847. }
  848. me._debug('log', ' ' + field._i + ': ' + method + ' => ' + (isValid || msg));
  849. // the current rule has passed, continue to validate
  850. if ( (isValid || special) && field._i < field._rules.length - 1) {
  851. field._i++;
  852. me._checkRule(el, field);
  853. }
  854. // field was invalid, or all fields was valid
  855. else {
  856. field._i = 0;
  857. if (special) {
  858. msgOpt.isValid = field.isValid;
  859. msgOpt.result = field._v;
  860. msgOpt.msg = field._m || '';
  861. if (!field.value && (field._e === 'focusin')) {
  862. msgOpt.type = 'tip';
  863. }
  864. } else {
  865. msgOpt.isValid = isValid;
  866. }
  867. me._validatedField(el, field, msgOpt);
  868. delete field._m;
  869. delete field._v;
  870. }
  871. },
  872. // Verify a rule form a field
  873. _checkRule: function(el, field) {
  874. var me = this,
  875. ret,
  876. fn,
  877. old,
  878. key = field.key,
  879. rule = field._rules[field._i],
  880. method = rule.method,
  881. params = rule.params;
  882. // request has been sent, wait it
  883. if (me.submiting && me.deferred[key]) {
  884. return;
  885. }
  886. old = field.old;
  887. field._r = method;
  888. if (old && !field.must && !rule.must && rule.result !== undefined &&
  889. old.ruleName === method && old.id === el.id &&
  890. field.value && old.value === field.value )
  891. {
  892. // get result from cache
  893. ret = rule.result;
  894. }
  895. else {
  896. // get result from current rule
  897. fn = _getDataRule(el, method) || me.rules[method] || noop;
  898. ret = fn.call(field, el, params, field);
  899. if (fn.msg) rule.msg = fn.msg;
  900. }
  901. // asynchronous validation
  902. if (isObject(ret) && isFunction(ret.then)) {
  903. me.deferred[key] = ret;
  904. // whether the field valid is unknown
  905. field.isValid = undefined;
  906. // show loading message
  907. !me.checkOnly && me.showMsg(el, {
  908. type: 'loading',
  909. msg: me.messages.loading
  910. }, field);
  911. // waiting to parse the response data
  912. ret.then(
  913. function(d, textStatus, jqXHR) {
  914. var data = trim(jqXHR.responseText),
  915. result,
  916. dataFilter = field.dataFilter;
  917. // detect if data is json or jsonp format
  918. if (/jsonp?/.test(this.dataType)) {
  919. data = d;
  920. } else if (data.charAt(0) === '{') {
  921. data = $.parseJSON(data);
  922. }
  923. // filter data
  924. result = dataFilter.call(this, data, field);
  925. if (result === undefined) result = dataFilter.call(this, data.data, field);
  926. rule.data = this.data;
  927. rule.result = field.old ? result : undefined;
  928. me._validatedRule(el, field, result);
  929. },
  930. function(jqXHR, textStatus){
  931. me._validatedRule(el, field, me.messages[textStatus] || textStatus);
  932. }
  933. ).always(function(){
  934. delete me.deferred[key];
  935. });
  936. }
  937. // other result
  938. else {
  939. me._validatedRule(el, field, ret);
  940. }
  941. },
  942. // Processing the validation
  943. _validate: function(el, field) {
  944. var me = this;
  945. // doesn't validate the element that has "disabled" or "novalidate" attribute
  946. if ( el.disabled || attr(el, NOVALIDATE) !== null ) {
  947. return;
  948. }
  949. field = field || me.getField(el);
  950. if (!field) return;
  951. if (!field._rules) me._parse(el);
  952. if (!field._rules) return;
  953. me._debug('info', field.key);
  954. field.isValid = true;
  955. field.element = el;
  956. // Cache the value
  957. field.value = field.getValue();
  958. // if the field is not required, and that has a blank value
  959. if (!field.required && !field.must && !field.value) {
  960. if (!_checkable(el)) {
  961. me._validatedField(el, field, {isValid: true});
  962. return true;
  963. }
  964. }
  965. me._checkRule(el, field);
  966. return field.isValid;
  967. },
  968. _debug: function(type, messages) {
  969. if (window.console && this.options.debug) {
  970. console[type](messages);
  971. }
  972. },
  973. /**
  974. * Detecting whether the value of an element that matches a rule
  975. *
  976. * @method test
  977. * @param {Element} el - input element
  978. * @param {String} rule - rule name
  979. */
  980. test: function(el, rule) {
  981. var me = this,
  982. ret,
  983. parts = rRule.exec(rule),
  984. field,
  985. method,
  986. params;
  987. if (parts) {
  988. method = parts[1];
  989. if (method in me.rules) {
  990. params = parts[2] || parts[3];
  991. params = params ? params.split(', ') : undefined;
  992. field = me.getField(el, true);
  993. field._r = method;
  994. field.value = field.getValue();
  995. ret = me.rules[method].call(field, el, params);
  996. }
  997. }
  998. return ret === true || ret === undefined || ret === null;
  999. },
  1000. _getDisplay: function(el, str) {
  1001. return !isString(str) ? isFunction(str) ? str.call(this, el) : '' : str;
  1002. },
  1003. _getMsgOpt: function(obj, field) {
  1004. var opt = field ? field : this.options;
  1005. return $.extend({
  1006. type: 'error',
  1007. pos: _getPos(opt.msgClass),
  1008. target: opt.target,
  1009. wrapper: opt.msgWrapper,
  1010. style: opt.msgStyle,
  1011. cls: opt.msgClass,
  1012. arrow: opt.msgArrow,
  1013. icon: opt.msgIcon
  1014. }, isString(obj) ? {msg: obj} : obj);
  1015. },
  1016. _getMsgDOM: function(el, msgOpt) {
  1017. var $el = $(el), $msgbox, datafor, tgt, container;
  1018. if ( $el.is(INPUT_SELECTOR) ) {
  1019. tgt = msgOpt.target || attr(el, DATA_TARGET);
  1020. if (tgt) {
  1021. tgt = !isFunction(tgt) ? tgt.charAt(0) === '#' ? $(tgt) : this.$el.find(tgt) : tgt.call(this, el);
  1022. if (tgt.length) {
  1023. if ( tgt.is(INPUT_SELECTOR) ) {
  1024. $el = tgt
  1025. el = tgt.get(0);
  1026. } else if ( tgt.hasClass(CLS_MSG_BOX) ) {
  1027. $msgbox = tgt;
  1028. } else {
  1029. container = tgt;
  1030. }
  1031. }
  1032. }
  1033. if (!$msgbox) {
  1034. datafor = (!_checkable(el) || !el.name) && el.id ? el.id : el.name;
  1035. $msgbox = (container || this.$el).find(msgOpt.wrapper + '.' + CLS_MSG_BOX + '[for="' + datafor + '"]');
  1036. }
  1037. } else {
  1038. $msgbox = $el;
  1039. }
  1040. // Create new message box
  1041. if (!msgOpt.hide && !$msgbox.length) {
  1042. $msgbox = $('<'+ msgOpt.wrapper + '>').attr({
  1043. 'class': CLS_MSG_BOX + (msgOpt.cls ? ' ' + msgOpt.cls : ''),
  1044. 'style': msgOpt.style || undefined,
  1045. 'for': datafor
  1046. });
  1047. if (container) {
  1048. $msgbox.appendTo(container);
  1049. } else {
  1050. if ( _checkable(el) ) {
  1051. var $parent = $el.parent();
  1052. $msgbox.appendTo( $parent.is('label') ? $parent.parent() : $parent );
  1053. } else {
  1054. $msgbox[!msgOpt.pos || msgOpt.pos === 'right' ? 'insertAfter' : 'insertBefore']($el);
  1055. }
  1056. }
  1057. }
  1058. return $msgbox;
  1059. },
  1060. /**
  1061. * Show validation message
  1062. *
  1063. * @method showMsg
  1064. * @param {Element} el - input element
  1065. * @param {Object} msgOpt
  1066. */
  1067. showMsg: function(el, msgOpt, /*INTERNAL*/ field) {
  1068. if (!el) return;
  1069. var me = this,
  1070. opt = me.options,
  1071. msgShow,
  1072. msgMaker,
  1073. temp,
  1074. $msgbox;
  1075. if (isObject(el) && !el.jquery && !msgOpt) {
  1076. $.each(el, function(key, msg) {
  1077. var el = me.elements[key] || me.$el.find(_key2selector(key))[0];
  1078. me.showMsg(el, msg);
  1079. });
  1080. return;
  1081. }
  1082. if ($(el).is(INPUT_SELECTOR)) {
  1083. field = field || me.getField(el);
  1084. }
  1085. if (!(msgMaker = (field || opt).msgMaker)) {
  1086. return;
  1087. }
  1088. msgOpt = me._getMsgOpt(msgOpt, field);
  1089. el = (el.name && _checkable(el) ? me.$el.find('input[name="'+ el.name +'"]') : $(el)).get(0);
  1090. // ok or tip
  1091. if (!msgOpt.msg && msgOpt.type !== 'error') {
  1092. temp = attr(el, 'data-' + msgOpt.type);
  1093. if (temp !== null) msgOpt.msg = temp;
  1094. }
  1095. if ( !isString(msgOpt.msg) ) {
  1096. return;
  1097. }
  1098. $msgbox = me._getMsgDOM(el, msgOpt);
  1099. !rPos.test($msgbox[0].className) && $msgbox.addClass(msgOpt.cls);
  1100. if ( isIE === 6 && msgOpt.pos === 'bottom' ) {
  1101. $msgbox[0].style.marginTop = $(el).outerHeight() + 'px';
  1102. }
  1103. $msgbox.html( msgMaker.call(me, msgOpt) )[0].style.display = '';
  1104. if (isFunction(msgShow = field && field.msgShow || opt.msgShow)) {
  1105. msgShow.call(me, $msgbox, msgOpt.type);
  1106. }
  1107. },
  1108. /**
  1109. * Hide validation message
  1110. *
  1111. * @method hideMsg
  1112. * @param {Element} el - input element
  1113. * @param {Object} msgOpt optional
  1114. */
  1115. hideMsg: function(el, msgOpt, /*INTERNAL*/ field) {
  1116. var me = this,
  1117. opt = me.options,
  1118. msgHide,
  1119. $msgbox;
  1120. el = $(el).get(0);
  1121. if ($(el).is(INPUT_SELECTOR)) {
  1122. field = field || me.getField(el);
  1123. if (field) {
  1124. if (field.isValid || me.reseting) attr(el, ARIA_INVALID, null);
  1125. }
  1126. }
  1127. msgOpt = me._getMsgOpt(msgOpt, field);
  1128. msgOpt.hide = true;
  1129. $msgbox = me._getMsgDOM(el, msgOpt);
  1130. if (!$msgbox.length) return;
  1131. if ( isFunction(msgHide = field && field.msgHide || opt.msgHide) ) {
  1132. msgHide.call(me, $msgbox, msgOpt.type);
  1133. } else {
  1134. $msgbox[0].style.display = 'none';
  1135. $msgbox[0].innerHTML = '';
  1136. }
  1137. },
  1138. /**
  1139. * Get field information
  1140. *
  1141. * @method getField
  1142. * @param {Element} - input element
  1143. * @return {Object} field
  1144. */
  1145. getField: function(el, must) {
  1146. var me = this,
  1147. key,
  1148. field;
  1149. if (isString(el)) {
  1150. key = el;
  1151. el = undefined;
  1152. } else {
  1153. if (attr(el, DATA_RULE)) {
  1154. return me._parse(el);
  1155. }
  1156. if (el.id && '#' + el.id in me.fields || !el.name) {
  1157. key = '#' + el.id;
  1158. } else {
  1159. key = el.name;
  1160. }
  1161. }
  1162. if ( (field = me.fields[key]) || must && (field = new me.Field(key)) ) {
  1163. field.element = el;
  1164. }
  1165. return field;
  1166. },
  1167. /**
  1168. * Config a field
  1169. *
  1170. * @method: setField
  1171. * @param {String} key
  1172. * @param {Object} obj
  1173. */
  1174. setField: function(key, obj) {
  1175. var fields = {};
  1176. if (!key) return;
  1177. // update this field
  1178. if (isString(key)) {
  1179. fields[key] = obj;
  1180. }
  1181. // update fields
  1182. else {
  1183. fields = key;
  1184. }
  1185. this._initFields(fields);
  1186. },
  1187. /**
  1188. * Detecting whether the form is valid
  1189. *
  1190. * @method isFormValid
  1191. * @return {Boolean}
  1192. */
  1193. isFormValid: function() {
  1194. var fields = this.fields, k, field;
  1195. for (k in fields) {
  1196. field = fields[k];
  1197. if (!field._rules || !field.required && !field.must && !field.value) continue;
  1198. if (!field.isValid) return false;
  1199. }
  1200. return true;
  1201. },
  1202. /**
  1203. * Prevent submission form
  1204. *
  1205. * @method holdSubmit
  1206. * @param {Boolean} hold - If set to false, will release the hold
  1207. */
  1208. holdSubmit: function(hold) {
  1209. this.submiting = hold === undefined || hold;
  1210. },
  1211. /**
  1212. * Clean validation messages
  1213. *
  1214. * @method cleanUp
  1215. */
  1216. cleanUp: function() {
  1217. this._reset(1);
  1218. },
  1219. /**
  1220. * Destroy the validation
  1221. *
  1222. * @method destroy
  1223. */
  1224. destroy: function() {
  1225. this._reset(1);
  1226. this.$el.off(CLS_NS).removeData(NS);
  1227. attr(this.$el[0], NOVALIDATE, this._NOVALIDATE);
  1228. }
  1229. };
  1230. /**
  1231. * Create Field Factory
  1232. *
  1233. * @class
  1234. * @param {Object} context
  1235. * @return {Function} Factory
  1236. */
  1237. function _createFieldFactory(context) {
  1238. function FieldFactory() {
  1239. var options = this.options;
  1240. for (var i in options) {
  1241. if (i in fieldDefaults) this[i] = options[i];
  1242. }
  1243. $.extend(this, {
  1244. _valHook: function() {
  1245. return this.element.contentEditable === 'true' ? 'text' : 'val';
  1246. },
  1247. getValue: function() {
  1248. var elem = this.element;
  1249. if (elem.type === 'number' && elem.validity && elem.validity.badInput) {
  1250. return 'NaN';
  1251. }
  1252. return $(elem)[this._valHook()]();
  1253. },
  1254. setValue: function(value) {
  1255. $(this.element)[this._valHook()](this.value = value);
  1256. },
  1257. // Get a range of validation messages
  1258. getRangeMsg: function(value, params, suffix) {
  1259. if (!params) return;
  1260. var me = this,
  1261. msg = me.messages[me._r] || '',
  1262. result,
  1263. p = params[0].split('~'),
  1264. e = params[1] === 'false',
  1265. a = p[0],
  1266. b = p[1],
  1267. c = 'rg',
  1268. args = [''],
  1269. isNumber = trim(value) && +value === +value;
  1270. function compare(large, small) {
  1271. return !e ? large >= small : large > small;
  1272. }
  1273. if (p.length === 2) {
  1274. if (a && b) {
  1275. if (isNumber && compare(value, +a) && compare(+b, value)) {
  1276. result = true;
  1277. }
  1278. args = args.concat(p);
  1279. c = e ? 'gtlt' : 'rg';
  1280. }
  1281. else if (a && !b) {
  1282. if (isNumber && compare(value, +a)) {
  1283. result = true;
  1284. }
  1285. args.push(a);
  1286. c = e ? 'gt' : 'gte';
  1287. }
  1288. else if (!a && b) {
  1289. if (isNumber && compare(+b, value)) {
  1290. result = true;
  1291. }
  1292. args.push(b);
  1293. c = e ? 'lt' : 'lte';
  1294. }
  1295. }
  1296. else {
  1297. if (value === +a) {
  1298. result = true;
  1299. }
  1300. args.push(a);
  1301. c = 'eq';
  1302. }
  1303. if (msg) {
  1304. if (suffix && msg[c + suffix]) {
  1305. c += suffix;
  1306. }
  1307. args[0] = msg[c];
  1308. }
  1309. return result || me._rules && ( me._rules[me._i].msg = me.renderMsg.apply(null, args) );
  1310. },
  1311. // Render message template
  1312. renderMsg: function() {
  1313. var args = arguments,
  1314. tpl = args[0],
  1315. i = args.length;
  1316. if (!tpl) return;
  1317. while (--i) {
  1318. tpl = tpl.replace('{' + i + '}', args[i]);
  1319. }
  1320. return tpl;
  1321. }
  1322. });
  1323. }
  1324. function Field(key, obj, oldField) {
  1325. this.key = key;
  1326. this.validator = context;
  1327. $.extend(this, oldField, obj);
  1328. }
  1329. FieldFactory.prototype = context;
  1330. Field.prototype = new FieldFactory();
  1331. return Field;
  1332. }
  1333. /**
  1334. * Create Rules
  1335. *
  1336. * @class
  1337. * @param {Object} obj rules
  1338. * @param {Object} context context
  1339. */
  1340. function Rules(obj, context) {
  1341. if (!isObject(obj)) return;
  1342. var k, that = context ? context === true ? this : context : Rules.prototype;
  1343. for (k in obj) {
  1344. if (_checkRuleName(k)) {
  1345. that[k] = _getRule(obj[k]);
  1346. }
  1347. }
  1348. }
  1349. /**
  1350. * Create Messages
  1351. *
  1352. * @class
  1353. * @param {Object} obj rules
  1354. * @param {Object} context context
  1355. */
  1356. function Messages(obj, context) {
  1357. if (!isObject(obj)) return;
  1358. var k, that = context ? context === true ? this : context : Messages.prototype;
  1359. for (k in obj) {
  1360. that[k] = obj[k];
  1361. }
  1362. }
  1363. // Rule converted factory
  1364. function _getRule(fn) {
  1365. switch ($.type(fn)) {
  1366. case 'function':
  1367. return fn;
  1368. case 'array':
  1369. var f = function() {
  1370. return fn[0].test(this.value) || fn[1] || false;
  1371. };
  1372. f.msg = fn[1];
  1373. return f;
  1374. case 'regexp':
  1375. return function() {
  1376. return fn.test(this.value);
  1377. };
  1378. }
  1379. }
  1380. // Get instance by an element
  1381. function _getInstance(el) {
  1382. var wrap, k, options;
  1383. if (!el || !el.tagName) return;
  1384. switch (el.tagName) {
  1385. case 'INPUT':
  1386. case 'SELECT':
  1387. case 'TEXTAREA':
  1388. case 'BUTTON':
  1389. case 'FIELDSET':
  1390. wrap = el.form || $(el).closest('.' + CLS_WRAPPER);
  1391. break;
  1392. case 'FORM':
  1393. wrap = el;
  1394. break;
  1395. default:
  1396. wrap = $(el).closest('.' + CLS_WRAPPER);
  1397. }
  1398. for (k in preinitialized) {
  1399. if ($(wrap).is(k)) {
  1400. options = preinitialized[k];
  1401. break;
  1402. }
  1403. }
  1404. return $(wrap).data(NS) || $(wrap)[NS](options).data(NS);
  1405. }
  1406. // Get custom rules on the node
  1407. function _getDataRule(el, method) {
  1408. var fn = trim(attr(el, DATA_RULE + '-' + method));
  1409. if ( fn && (fn = new Function('return ' + fn)()) ) {
  1410. return _getRule(fn);
  1411. }
  1412. }
  1413. // Get custom messages on the node
  1414. function _getDataMsg(el, field, m) {
  1415. var msg = field.msg,
  1416. item = field._r;
  1417. if ( isObject(msg) ) msg = msg[item];
  1418. if ( !isString(msg) ) {
  1419. msg = attr(el, DATA_MSG + '-' + item) || attr(el, DATA_MSG) || ( m ? isString(m) ? m : m[item] : '');
  1420. }
  1421. return msg;
  1422. }
  1423. // Get message position
  1424. function _getPos(str) {
  1425. var pos;
  1426. if (str) pos = rPos.exec(str);
  1427. return pos && pos[0];
  1428. }
  1429. // Check whether the element is checkbox or radio
  1430. function _checkable(el) {
  1431. return el.tagName === 'INPUT' && el.type === 'checkbox' || el.type === 'radio';
  1432. }
  1433. // Parse date string to timestamp
  1434. function _parseDate(str) {
  1435. return Date.parse(str.replace(/\.|\-/g, '/'));
  1436. }
  1437. // Rule name only allows alphanumeric characters and underscores
  1438. function _checkRuleName(name) {
  1439. return /^\w+$/.test(name);
  1440. }
  1441. // Translate field key to jQuery selector.
  1442. function _key2selector(key) {
  1443. var isID = key.charAt(0) === '#';
  1444. key = key.replace(/([:.{(|)}/\[\]])/g, '\\$1');
  1445. return isID ? key : '[name="'+ key +'"]:first';
  1446. }
  1447. // Fixed a issue cause by refresh page in IE.
  1448. $(window).on('beforeunload', function(){
  1449. this.focus();
  1450. });
  1451. $(document)
  1452. .on('click', ':submit', function(){
  1453. var input = this, attrNode;
  1454. if (!input.form) return;
  1455. // Shim for "formnovalidate"
  1456. attrNode = input.getAttributeNode('formnovalidate');
  1457. if (attrNode && attrNode.nodeValue !== null || attr(input, NOVALIDATE)!== null) {
  1458. novalidateonce = true;
  1459. }
  1460. })
  1461. // Automatic initializing form validation
  1462. .on('focusin submit validate', 'form,.'+CLS_WRAPPER, function(e) {
  1463. if ( attr(this, NOVALIDATE) !== null ) return;
  1464. var $form = $(this), me;
  1465. if ( !$form.data(NS) && (me = _getInstance(this)) ) {
  1466. if ( !$.isEmptyObject(me.fields) ) {
  1467. // Execute event handler
  1468. if (e.type === 'focusin') {
  1469. me._focusin(e);
  1470. } else {
  1471. me._submit(e);
  1472. }
  1473. } else {
  1474. attr(this, NOVALIDATE, NOVALIDATE);
  1475. $form.off(CLS_NS).removeData(NS);
  1476. }
  1477. }
  1478. });
  1479. new Messages({
  1480. fallback: 'This field is not valid.',
  1481. loading: 'Validating...'
  1482. });
  1483. // Built-in rules (global)
  1484. new Rules({
  1485. /**
  1486. * required
  1487. *
  1488. * @example:
  1489. required
  1490. required(jqSelector)
  1491. required(anotherRule)
  1492. required(not, -1)
  1493. required(from, .contact)
  1494. */
  1495. required: function(element, params) {
  1496. var me = this,
  1497. val = trim(me.value),
  1498. isValid = true;
  1499. if (params) {
  1500. if ( params.length === 1 ) {
  1501. if ( !_checkRuleName(params[0]) ) {
  1502. if (!val && !$(params[0], me.$el).length ) {
  1503. return null;
  1504. }
  1505. }
  1506. else if ( me.rules[params[0]] ) {
  1507. if ( !val && !me.test(element, params[0]) ) {
  1508. return null;
  1509. }
  1510. me._r = 'required'
  1511. }
  1512. }
  1513. else if ( params[0] === 'not' ) {
  1514. $.each(params.slice(1), function() {
  1515. return (isValid = val !== trim(this));
  1516. });
  1517. }
  1518. else if ( params[0] === 'from' ) {
  1519. var $elements = me.$el.find(params[1]),
  1520. VALIDATED = '_validated_',
  1521. ret;
  1522. isValid = $elements.filter(function(){
  1523. var field = me.getField(this);
  1524. return field && !!trim(field.getValue());
  1525. }).length >= (params[2] || 1);
  1526. if (isValid) {
  1527. if (!val) ret = null;
  1528. } else {
  1529. ret = _getDataMsg($elements[0], me) || false;
  1530. }
  1531. if ( !$(element).data(VALIDATED) ) {
  1532. $elements.data(VALIDATED, 1).each(function(){
  1533. if (element !== this) {
  1534. me._validate(this);
  1535. }
  1536. }).removeData(VALIDATED);
  1537. }
  1538. return ret;
  1539. }
  1540. }
  1541. return isValid && !!val;
  1542. },
  1543. /**
  1544. * integer
  1545. *
  1546. * @example:
  1547. integer
  1548. integer[+]
  1549. integer[+0]
  1550. integer[-]
  1551. integer[-0]
  1552. */
  1553. integer: function(element, params) {
  1554. var re, z = '0|',
  1555. p = '[1-9]\\d*',
  1556. key = params ? params[0] : '*';
  1557. switch (key) {
  1558. case '+':
  1559. re = p;
  1560. break;
  1561. case '-':
  1562. re = '-' + p;
  1563. break;
  1564. case '+0':
  1565. re = z + p;
  1566. break;
  1567. case '-0':
  1568. re = z + '-' + p;
  1569. break;
  1570. default:
  1571. re = z + '-?' + p;
  1572. }
  1573. re = '^(?:' + re + ')$';
  1574. return new RegExp(re).test(this.value) || (this.messages.integer && this.messages.integer[key]);
  1575. },
  1576. /**
  1577. * match another field
  1578. *
  1579. * @example:
  1580. match[password] Match the password field (two values ​​must be the same)
  1581. match[eq, password] Ditto
  1582. match[neq, count] The value must be not equal to the value of the count field
  1583. match[lt, count] The value must be less than the value of the count field
  1584. match[lte, count] The value must be less than or equal to the value of the count field
  1585. match[gt, count] The value must be greater than the value of the count field
  1586. match[gte, count] The value must be greater than or equal to the value of the count field
  1587. match[gte, startDate, date]
  1588. match[gte, startTime, time]
  1589. **/
  1590. match: function(element, params) {
  1591. if (!params) return;
  1592. var me = this,
  1593. isValid = true,
  1594. a, b,
  1595. key, msg, type = 'eq', parser,
  1596. selector2, elem2, field2;
  1597. if (params.length === 1) {
  1598. key = params[0];
  1599. } else {
  1600. type = params[0];
  1601. key = params[1];
  1602. }
  1603. selector2 = _key2selector(key);
  1604. elem2 = me.$el.find(selector2)[0];
  1605. // If the compared field is not exist
  1606. if (!elem2) return;
  1607. field2 = me.getField(elem2);
  1608. a = me.value;
  1609. b = field2.getValue();
  1610. if (!me._match) {
  1611. me.$el.on('valid'+CLS_NS_FIELD+CLS_NS, selector2, function(){
  1612. $(element).trigger('validate');
  1613. });
  1614. me._match = field2._match = 1;
  1615. }
  1616. // If both fields are blank
  1617. if (!me.required && a === '' && b === '') {
  1618. return null;
  1619. }
  1620. parser = params[2];
  1621. if (parser) {
  1622. if (/^date(time)?$/i.test(parser)) {
  1623. a = _parseDate(a);
  1624. b = _parseDate(b);
  1625. } else if (parser === 'time') {
  1626. a = +a.replace(/:/g, '');
  1627. b = +b.replace(/:/g, '');
  1628. }
  1629. }
  1630. // If the compared field is incorrect, we only ensure that this field is correct.
  1631. if (type !== 'eq' && !isNaN(+a) && isNaN(+b)) {
  1632. return true;
  1633. }
  1634. switch (type) {
  1635. case 'lt':
  1636. isValid = +a < +b; break;
  1637. case 'lte':
  1638. isValid = +a <= +b; break;
  1639. case 'gte':
  1640. isValid = +a >= +b; break;
  1641. case 'gt':
  1642. isValid = +a > +b; break;
  1643. case 'neq':
  1644. isValid = a !== b; break;
  1645. default:
  1646. isValid = a === b;
  1647. }
  1648. return isValid || (
  1649. isObject(me.messages.match)
  1650. && me.messages.match[type].replace( '{1}', me._getDisplay( elem2, field2.display || key ) )
  1651. );
  1652. },
  1653. /**
  1654. * range numbers
  1655. *
  1656. * @example:
  1657. range[0~99] Number 0-99
  1658. range[0~] Number greater than or equal to 0
  1659. range[~100] Number less than or equal to 100
  1660. **/
  1661. range: function(element, params) {
  1662. return this.getRangeMsg(this.value, params);
  1663. },
  1664. /**
  1665. * how many checkbox or radio inputs that checked
  1666. *
  1667. * @example:
  1668. checked; no empty, same to required
  1669. checked[1~3] 1-3 items
  1670. checked[1~] greater than 1 item
  1671. checked[~3] less than 3 items
  1672. checked[3] 3 items
  1673. **/
  1674. checked: function(element, params) {
  1675. if ( !_checkable(element) ) return;
  1676. var me = this,
  1677. elem, count;
  1678. if (element.name) {
  1679. count = me.$el.find('input[name="' + element.name + '"]').filter(function() {
  1680. var el = this;
  1681. if (!elem && _checkable(el)) elem = el;
  1682. return !el.disabled && el.checked;
  1683. }).length;
  1684. } else {
  1685. elem = element;
  1686. count = elem.checked;
  1687. }
  1688. if (params) {
  1689. return me.getRangeMsg(count, params);
  1690. } else {
  1691. return !!count || _getDataMsg(elem, me, '') || me.messages.required || false;
  1692. }
  1693. },
  1694. /**
  1695. * length of a characters (You can pass the second parameter "true", will calculate the length in bytes)
  1696. *
  1697. * @example:
  1698. length[6~16] 6-16 characters
  1699. length[6~] Greater than 6 characters
  1700. length[~16] Less than 16 characters
  1701. length[~16, true] Less than 16 characters, non-ASCII characters calculating two-character
  1702. **/
  1703. length: function(element, params) {
  1704. var value = this.value,
  1705. len = (params[1] === 'true' ? value.replace(rDoubleBytes, 'xx') : value).length;
  1706. return this.getRangeMsg(len, params, (params[1] ? '_2' : ''));
  1707. },
  1708. /**
  1709. * remote validation
  1710. *
  1711. * @description
  1712. * remote([get:]url [, name1, [name2 ...]]);
  1713. * Adaptation three kinds of results (Front for the successful, followed by a failure):
  1714. 1. text:
  1715. '' 'Error Message'
  1716. 2. json:
  1717. {"ok": ""} {"error": "Error Message"}
  1718. 3. json wrapper:
  1719. {"status": 1, "data": {"ok": ""}} {"status": 1, "data": {"error": "Error Message"}}
  1720. * @example
  1721. The simplest: remote(path/to/server);
  1722. With parameters: remote(path/to/server, name1, name2, ...);
  1723. By GET: remote(get:path/to/server, name1, name2, ...);
  1724. Name proxy: remote(path/to/server, name1, proxyname2:name2, proxyname3:#id3, ...)
  1725. Query String remote(path/to/server, foo=1&bar=2, name1, name2, ...)
  1726. CORS remote(cors:path/to/server)
  1727. JSONP remote(jsonp:path/to/server)
  1728. */
  1729. remote: function(element, params) {
  1730. if (!params) return;
  1731. var me = this,
  1732. arr = rAjaxType.exec(params[0]),
  1733. rule = me._rules[me._i],
  1734. data = {},
  1735. queryString = '',
  1736. url = arr[3],
  1737. type = arr[2] || 'POST', // GET / POST
  1738. rType = (arr[1]||'').toLowerCase(), // CORS / JSONP
  1739. dataType;
  1740. rule.must = true;
  1741. data[element.name] = me.value;
  1742. // There are extra fields
  1743. if (params[1]) {
  1744. $.map(params.slice(1), function(name) {
  1745. var arr, key;
  1746. if (~name.indexOf('=')) {
  1747. queryString += '&' + name;
  1748. } else {
  1749. arr = name.split(':');
  1750. name = trim(arr[0]);
  1751. key = trim(arr[1]) || name;
  1752. data[ name ] = me.$el.find( _key2selector(key) ).val();
  1753. }
  1754. });
  1755. }
  1756. data = $.param(data) + queryString;
  1757. if (!me.must && rule.data && rule.data === data) {
  1758. return rule.result;
  1759. }
  1760. // Cross-domain request, force jsonp dataType
  1761. if (rType !== 'cors' && /^https?:/.test(url) && !~url.indexOf(location.host)) {
  1762. dataType = 'jsonp';
  1763. }
  1764. // Asynchronous validation need return jqXHR objects
  1765. return $.ajax({
  1766. url: url,
  1767. type: type,
  1768. data: data,
  1769. dataType: dataType
  1770. });
  1771. },
  1772. /**
  1773. * filter characters, direct filtration without prompting error (support custom regular expressions)
  1774. *
  1775. * @example
  1776. * filter filtering unsafe characters
  1777. * filter(regexp) filtering the "regexp" matched characters
  1778. */
  1779. filter: function(element, params) {
  1780. var value = this.value,
  1781. temp = value.replace( params ? (new RegExp('[' + params[0] + ']', 'gm')) : rUnsafe, '' );
  1782. if (temp !== value) this.setValue(temp);
  1783. }
  1784. });
  1785. /**
  1786. * Config global options
  1787. *
  1788. * @static config
  1789. * @param {Object} options
  1790. */
  1791. Validator.config = function(key, value) {
  1792. if (isObject(key)) {
  1793. $.each(key, _config);
  1794. }
  1795. else if (isString(key)) {
  1796. _config(key, value);
  1797. }
  1798. function _config(k, o) {
  1799. if (k === 'rules') {
  1800. new Rules(o);
  1801. }
  1802. else if (k === 'messages') {
  1803. new Messages(o);
  1804. }
  1805. else if (k in fieldDefaults) {
  1806. fieldDefaults[k] = o;
  1807. }
  1808. else {
  1809. defaults[k] = o;
  1810. }
  1811. }
  1812. };
  1813. /**
  1814. * Config themes
  1815. *
  1816. * @static setTheme
  1817. * @param {String|Object} name
  1818. * @param {Object} obj
  1819. * @example
  1820. .setTheme( themeName, themeOptions )
  1821. .setTheme( multiThemes )
  1822. */
  1823. Validator.setTheme = function(name, obj) {
  1824. if ( isObject(name) ) {
  1825. $.extend(true, themes, name);
  1826. }
  1827. else if ( isString(name) && isObject(obj) ) {
  1828. themes[name] = $.extend(themes[name], obj);
  1829. }
  1830. };
  1831. /**
  1832. * Resource loader
  1833. *
  1834. * @static load
  1835. * @param {String} str
  1836. * @example
  1837. .load('local=zh-CN') // load: local/zh-CN.js and jquery.validator.css
  1838. .load('local=zh-CN&css=') // load: local/zh-CN.js
  1839. .load('local&css') // load: local/en.js (set <html lang="en">) and jquery.validator.css
  1840. .load('local') // dito
  1841. */
  1842. Validator.load = function(str) {
  1843. if (!str) return;
  1844. var doc = document,
  1845. params = {},
  1846. node = doc.scripts[0],
  1847. dir, el, ONLOAD;
  1848. str.replace(/([^?=&]+)=([^&#]*)/g, function(m, key, value){
  1849. params[key] = value;
  1850. });
  1851. dir = params.dir || Validator.dir;
  1852. if (!Validator.css && params.css !== '') {
  1853. el = doc.createElement('link');
  1854. el.rel = 'stylesheet';
  1855. el.href = Validator.css = dir + 'jquery.validator.css';
  1856. node.parentNode.insertBefore(el, node);
  1857. }
  1858. if (!Validator.local && ~str.indexOf('local') && params.local !== '') {
  1859. Validator.local = (params.local || doc.documentElement.lang || 'en').replace('_','-');
  1860. Validator.pending = 1;
  1861. el = doc.createElement('script');
  1862. el.src = dir + 'local/' + Validator.local + '.js';
  1863. ONLOAD = 'onload' in el ? 'onload' : 'onreadystatechange';
  1864. el[ONLOAD] = function() {
  1865. if (!el.readyState || /loaded|complete/.test(el.readyState)) {
  1866. el = el[ONLOAD] = null;
  1867. delete Validator.pending;
  1868. $(window).triggerHandler('validatorready');
  1869. }
  1870. };
  1871. node.parentNode.insertBefore(el, node);
  1872. }
  1873. };
  1874. // Auto loading resources
  1875. (function(){
  1876. var scripts = document.scripts,
  1877. i = scripts.length, node, arr,
  1878. re = /(.*validator(?:\.min)?.js)(\?.*(?:local|css|dir)(?:=[\w\-]*)?)?/;
  1879. while (i-- && !arr) {
  1880. node = scripts[i];
  1881. arr = (node.hasAttribute ? node.src : node.getAttribute('src',4)||'').match(re);
  1882. }
  1883. if (!arr) return;
  1884. Validator.dir = arr[1].split('/').slice(0, -1).join('/')+'/';
  1885. Validator.load(arr[2]);
  1886. })();
  1887. return $[NS] = Validator;
  1888. }));