jquery.validator.m.js 72 KB

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