jquery.validator.js 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139
  1. /*! nice-validator 1.1.3
  2. * (c) 2012-2017 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. if ( events && events[name] &&
  312. $.map(events[name], function(e){
  313. return ~e.namespace.indexOf(namespace) ? 1 : null;
  314. }).length
  315. ) {
  316. return true;
  317. }
  318. return false;
  319. }
  320. },
  321. _initFields: function(fields) {
  322. var me = this, k, arr, i,
  323. clear = fields === null;
  324. // Processing field information
  325. if (clear) fields = me.fields;
  326. if ( isObject(fields) ) {
  327. for (k in fields) {
  328. if (~k.indexOf(',')) {
  329. arr = k.split(',');
  330. i = arr.length;
  331. while (i--) {
  332. initField(trim(arr[i]), fields[k]);
  333. }
  334. } else {
  335. initField(k, fields[k]);
  336. }
  337. }
  338. }
  339. // Parsing DOM rules
  340. me.$el.find(INPUT_SELECTOR).each(function() {
  341. me._parse(this);
  342. });
  343. function initField(k, v) {
  344. // delete a field from settings
  345. if ( v === null || clear ) {
  346. var el = me.elements[k];
  347. if (el) me._resetElement(el, true);
  348. delete me.fields[k];
  349. } else {
  350. me.fields[k] = new me.Field(k, isString(v) ? {rule: v} : v, me.fields[k]);
  351. }
  352. }
  353. },
  354. // Parsing a field
  355. _parse: function(el) {
  356. var me = this,
  357. field,
  358. key = el.name,
  359. display,
  360. timely,
  361. dataRule = attr(el, DATA_RULE);
  362. dataRule && attr(el, DATA_RULE, null);
  363. // If the field has passed the key as id mode, or it doesn't has a name
  364. if ( el.id && (
  365. ('#' + el.id in me.fields) ||
  366. !key ||
  367. // If dataRule and element are diffrent from old's, we use ID mode.
  368. (dataRule !== null && (field = me.fields[key]) && dataRule !== field.rule && el.id !== field.key)
  369. )
  370. ) {
  371. key = '#' + el.id;
  372. }
  373. // Generate id
  374. if (!key) {
  375. key = '#' + (el.id = 'N' + String(Math.random()).slice(-12));
  376. }
  377. field = me.getField(key, true);
  378. // The priority of passing parameter by DOM is higher than by JS.
  379. field.rule = dataRule || field.rule;
  380. if (display = attr(el, DATA_DISPLAY)) {
  381. field.display = display;
  382. }
  383. if (field.rule) {
  384. if ( attr(el, DATA_MUST) !== null || /\b(?:match|checked)\b/.test(field.rule) ) {
  385. field.must = true;
  386. }
  387. if ( /\brequired\b/.test(field.rule) ) {
  388. field.required = true;
  389. }
  390. if (timely = attr(el, DATA_TIMELY)) {
  391. field.timely = +timely;
  392. } else if (field.timely > 3) {
  393. attr(el, DATA_TIMELY, field.timely);
  394. }
  395. me._parseRule(field);
  396. field.old = {};
  397. }
  398. if ( isString(field.target) ) {
  399. attr(el, DATA_TARGET, field.target);
  400. }
  401. if ( isString(field.tip) ) {
  402. attr(el, DATA_TIP, field.tip);
  403. }
  404. return me.fields[key] = field;
  405. },
  406. // Parsing field rules
  407. _parseRule: function(field) {
  408. var arr = rDisplay.exec(field.rule);
  409. if (!arr) return;
  410. // current rule index
  411. field._i = 0;
  412. if (arr[1]) {
  413. field.display = arr[1];
  414. }
  415. if (arr[2]) {
  416. field._rules = [];
  417. arr[2].replace(rRules, function(){
  418. var args = arguments;
  419. args[4] = args[4] || args[5];
  420. field._rules.push({
  421. and: args[1] === "&",
  422. not: args[2] === "!",
  423. or: args[6] === "|",
  424. method: args[3],
  425. params: args[4] ? $.map( args[4].split(', '), trim ) : undefined
  426. });
  427. });
  428. }
  429. },
  430. // Verify a zone
  431. _multiValidate: function($inputs, doneCallback){
  432. var me = this,
  433. opt = me.options;
  434. me.hasError = false;
  435. if (opt.ignore) {
  436. $inputs = $inputs.not(opt.ignore);
  437. }
  438. $inputs.each(function() {
  439. me._validate(this);
  440. if (me.hasError && opt.stopOnError) {
  441. // stop the validation
  442. return false;
  443. }
  444. });
  445. // Need to wait for all fields validation complete, especially asynchronous validation
  446. if (doneCallback) {
  447. me.validating = true;
  448. $.when.apply(
  449. null,
  450. $.map(me.deferred, function(v){return v;})
  451. ).done(function(){
  452. doneCallback.call(me, !me.hasError);
  453. me.validating = false;
  454. });
  455. }
  456. // If the form does not contain asynchronous validation, the return value is correct.
  457. // Otherwise, you should detect form validation result through "doneCallback".
  458. return !$.isEmptyObject(me.deferred) ? undefined : !me.hasError;
  459. },
  460. // Validate the whole form
  461. _submit: function(e) {
  462. var me = this,
  463. opt = me.options,
  464. form = e.target,
  465. canSubmit = e.type === 'submit' && form.tagName === 'FORM' && !e.isDefaultPrevented();
  466. e.preventDefault();
  467. if (
  468. novalidateonce && ~(novalidateonce = false) ||
  469. // Prevent duplicate submission
  470. me.submiting ||
  471. // Receive the "validate" event only from the form.
  472. e.type === 'validate' && me.$el[0] !== form ||
  473. // trigger the beforeSubmit callback.
  474. isFunction(opt.beforeSubmit) && opt.beforeSubmit.call(me, form) === false
  475. ) {
  476. return;
  477. }
  478. if (me.isAjaxSubmit === undefined) {
  479. me._guessAjax(form);
  480. }
  481. me._debug('log', '\n<<< event: ' + e.type);
  482. me._reset();
  483. me.submiting = true;
  484. me._multiValidate(
  485. me.$el.find(INPUT_SELECTOR),
  486. function(isValid){
  487. var ret = (isValid || opt.debug === 2) ? 'valid' : 'invalid',
  488. errors;
  489. if (!isValid) {
  490. if (opt.focusInvalid) {
  491. // navigate to the error element
  492. me.$el.find('[' + ARIA_INVALID + ']:first').focus();
  493. }
  494. errors = $.map(me.errors, function(err){return err;});
  495. }
  496. // releasing submit
  497. me.submiting = false;
  498. me.isValid = isValid;
  499. // trigger callback and event
  500. isFunction(opt[ret]) && opt[ret].call(me, form, errors);
  501. me.$el.trigger(ret + CLS_NS_FORM, [form, errors]);
  502. me._debug('log', '>>> ' + ret);
  503. if (!isValid) return;
  504. // For jquery.form plugin
  505. if (me.vetoed) {
  506. $(form).ajaxSubmit(me.ajaxFormOptions);
  507. }
  508. else if (canSubmit && !me.isAjaxSubmit) {
  509. document.createElement('form').submit.call(form);
  510. }
  511. }
  512. );
  513. },
  514. _reset: function(e) {
  515. var me = this;
  516. me.errors = {};
  517. if (e) {
  518. me.reseting = true;
  519. me.$el.find(INPUT_SELECTOR).each( function(){
  520. me._resetElement(this);
  521. });
  522. delete me.reseting;
  523. }
  524. },
  525. _resetElement: function(el, all) {
  526. this._setClass(el, null);
  527. this.hideMsg(el);
  528. },
  529. // Handle events: "focusin/click"
  530. _focusin: function(e) {
  531. var me = this,
  532. opt = me.options,
  533. el = e.target,
  534. timely,
  535. msg;
  536. if ( me.validating || ( e.type==='click' && document.activeElement === el ) ) {
  537. return;
  538. }
  539. if (opt.focusCleanup) {
  540. if ( attr(el, ARIA_INVALID) === 'true' ) {
  541. me._setClass(el, null);
  542. me.hideMsg(el);
  543. }
  544. }
  545. msg = attr(el, DATA_TIP);
  546. if (msg) {
  547. me.showMsg(el, {
  548. type: 'tip',
  549. msg: msg
  550. });
  551. } else {
  552. if (attr(el, DATA_RULE)) {
  553. me._parse(el);
  554. }
  555. if (timely = attr(el, DATA_TIMELY)) {
  556. if ( timely === 8 || timely === 9 ) {
  557. me._focusout(e);
  558. }
  559. }
  560. }
  561. },
  562. // Handle events: "focusout/validate/keyup/click/change/input/compositionstart/compositionend"
  563. _focusout: function(e) {
  564. var me = this,
  565. opt = me.options,
  566. el = e.target,
  567. etype = e.type,
  568. etype0,
  569. focusin = etype === 'focusin',
  570. special = etype === 'validate',
  571. elem,
  572. field,
  573. old,
  574. value,
  575. timestamp,
  576. key, specialKey,
  577. timely,
  578. timer = 0;
  579. if (etype === 'compositionstart') {
  580. me.pauseValidate = true;
  581. }
  582. if (etype === 'compositionend') {
  583. me.pauseValidate = false;
  584. }
  585. if (me.pauseValidate) {
  586. return;
  587. }
  588. // For checkbox and radio
  589. elem = el.name && _checkable(el) ? me.$el.find('input[name="'+ el.name +'"]').get(0) : el;
  590. // Get field
  591. if (!(field = me.getField(elem)) || !field.rule) {
  592. return;
  593. }
  594. // Cache event type
  595. etype0 = field._e;
  596. field._e = etype;
  597. timely = field.timely;
  598. if (!special) {
  599. if (!timely || (_checkable(el) && etype !== 'click')) {
  600. return;
  601. }
  602. value = field.getValue();
  603. // not validate field unless fill a value
  604. if ( field.ignoreBlank && !value && !focusin ) {
  605. me.hideMsg(el);
  606. return;
  607. }
  608. if ( etype === 'focusout' ) {
  609. if (etype0 === 'change') {
  610. return;
  611. }
  612. if ( timely === 2 || timely === 8 ) {
  613. old = field.old;
  614. if (value && old) {
  615. if (field.isValid && !old.showOk) {
  616. me.hideMsg(el);
  617. } else {
  618. me._makeMsg(el, field, old);
  619. }
  620. } else {
  621. return;
  622. }
  623. }
  624. }
  625. else {
  626. if ( timely < 2 && !e.data ) {
  627. return;
  628. }
  629. // mark timestamp to reduce the frequency of the received event
  630. timestamp = +new Date();
  631. if ( timestamp - (el._ts || 0) < 100 ) {
  632. return;
  633. }
  634. el._ts = timestamp;
  635. // handle keyup
  636. if ( etype === 'keyup' ) {
  637. if (etype0 === 'input') {
  638. return;
  639. }
  640. key = e.keyCode;
  641. specialKey = {
  642. 8: 1, // Backspace
  643. 9: 1, // Tab
  644. 16: 1, // Shift
  645. 32: 1, // Space
  646. 46: 1 // Delete
  647. };
  648. // only gets focus, no validation
  649. if ( key === 9 && !value ) {
  650. return;
  651. }
  652. // do not validate, if triggered by these keys
  653. if ( key < 48 && !specialKey[key] ) {
  654. return;
  655. }
  656. }
  657. if ( !focusin ) {
  658. // keyboard events, reducing the frequency of validation
  659. timer = timely <100 ? (etype === 'click' || el.tagName === 'SELECT') ? 0 : 400 : timely;
  660. }
  661. }
  662. }
  663. // if the current field is ignored
  664. if ( opt.ignore && $(el).is(opt.ignore) ) {
  665. return;
  666. }
  667. clearTimeout(field._t);
  668. if (timer) {
  669. field._t = setTimeout(function() {
  670. me._validate(el, field);
  671. }, timer);
  672. } else {
  673. if (special) field.old = {};
  674. me._validate(el, field);
  675. }
  676. },
  677. _setClass: function(el, isValid) {
  678. var $el = $(el), opt = this.options;
  679. if (opt.bindClassTo) {
  680. $el = $el.closest(opt.bindClassTo);
  681. }
  682. $el.removeClass( opt.invalidClass + ' ' + opt.validClass );
  683. if (isValid !== null) {
  684. $el.addClass( isValid ? opt.validClass : opt.invalidClass );
  685. }
  686. },
  687. _showmsg: function(e, type, msg) {
  688. var me = this,
  689. el = e.target;
  690. if ( me.$el.is(el) ) {
  691. if (isObject(type)) {
  692. me.showMsg(type)
  693. }
  694. else if ( type === 'tip' ) {
  695. me.$el.find(INPUT_SELECTOR +"["+ DATA_TIP +"]", el).each(function(){
  696. me.showMsg(this, {type: type, msg: msg});
  697. });
  698. }
  699. }
  700. else {
  701. me.showMsg(el, {type: type, msg: msg});
  702. }
  703. },
  704. _hidemsg: function(e) {
  705. var $el = $(e.target);
  706. if ( $el.is(INPUT_SELECTOR) ) {
  707. this.hideMsg($el);
  708. }
  709. },
  710. // Validated a field
  711. _validatedField: function(el, field, ret) {
  712. var me = this,
  713. opt = me.options,
  714. isValid = field.isValid = ret.isValid = !!ret.isValid,
  715. callback = isValid ? 'valid' : 'invalid';
  716. ret.key = field.key;
  717. ret.ruleName = field._r;
  718. ret.id = el.id;
  719. ret.value = field.value;
  720. me.elements[field.key] = ret.element = el;
  721. me.isValid = me.$el[0].isValid = isValid ? me.isFormValid() : isValid;
  722. if (isValid) {
  723. ret.type = 'ok';
  724. } else {
  725. if (me.submiting) {
  726. me.errors[field.key] = ret.msg;
  727. }
  728. me.hasError = true;
  729. }
  730. // cache result
  731. field.old = ret;
  732. // trigger callback
  733. isFunction(field[callback]) && field[callback].call(me, el, ret);
  734. isFunction(opt.validation) && opt.validation.call(me, el, ret);
  735. // trigger event
  736. $(el).attr( ARIA_INVALID, isValid ? null : true )
  737. .trigger( callback + CLS_NS_FIELD, [ret, me] );
  738. me.$el.triggerHandler('validation', [ret, me]);
  739. if (me.checkOnly) return;
  740. // set className
  741. me._setClass(el, ret.skip || ret.type === 'tip' ? null : isValid);
  742. me._makeMsg.apply(me, arguments);
  743. },
  744. _makeMsg: function(el, field, ret) {
  745. // show or hide the message
  746. if (field.msgMaker) {
  747. ret = $.extend({}, ret);
  748. if (field._e === 'focusin') {
  749. ret.type = 'tip';
  750. }
  751. this[ ret.showOk || ret.msg || ret.type === 'tip' ? 'showMsg' : 'hideMsg' ](el, ret, field);
  752. }
  753. },
  754. // Validated a rule
  755. _validatedRule: function(el, field, ret, msgOpt) {
  756. field = field || me.getField(el);
  757. msgOpt = msgOpt || {};
  758. var me = this,
  759. msg,
  760. rule,
  761. method = field._r,
  762. timely = field.timely,
  763. special = timely === 9 || timely === 8,
  764. transfer,
  765. temp,
  766. isValid = false;
  767. // use null to break validation from a field
  768. if (ret === null) {
  769. me._validatedField(el, field, {isValid: true, skip: true});
  770. field._i = 0;
  771. return;
  772. }
  773. else if (ret === undefined) {
  774. transfer = true;
  775. }
  776. else if (ret === true || ret === '') {
  777. isValid = true;
  778. }
  779. else if (isString(ret)) {
  780. msg = ret;
  781. }
  782. else if (isObject(ret)) {
  783. if (ret.error) {
  784. msg = ret.error;
  785. } else {
  786. msg = ret.ok;
  787. isValid = true;
  788. }
  789. }
  790. else {
  791. isValid = !!ret
  792. }
  793. rule = field._rules[field._i];
  794. if (rule.not) {
  795. msg = undefined;
  796. isValid = method === "required" || !isValid;
  797. }
  798. if (rule.or) {
  799. if (isValid) {
  800. while ( field._i < field._rules.length && field._rules[field._i].or ) {
  801. field._i++;
  802. }
  803. } else {
  804. transfer = true;
  805. }
  806. }
  807. else if (rule.and) {
  808. if (!field.isValid) transfer = true;
  809. }
  810. if (transfer) {
  811. isValid = true;
  812. }
  813. // message analysis, and throw rule level event
  814. else {
  815. if (isValid) {
  816. if (field.showOk !== false) {
  817. temp = attr(el, DATA_OK);
  818. msg = temp === null ? isString(field.ok) ? field.ok : msg : temp;
  819. if (!isString(msg) && isString(field.showOk)) {
  820. msg = field.showOk;
  821. }
  822. if (isString(msg)) {
  823. msgOpt.showOk = isValid;
  824. }
  825. }
  826. }
  827. if (!isValid || special) {
  828. /* rule message priority:
  829. 1. custom DOM message
  830. 2. custom field message;
  831. 3. global defined message;
  832. 4. rule returned message;
  833. 5. default message;
  834. */
  835. msg = (_getDataMsg(el, field, msg || rule.msg || me.messages[method]) || me.messages.fallback).replace(/\{0\|?([^\}]*)\}/, function(m, defaultDisplay){
  836. return me._getDisplay(el, field.display) || defaultDisplay || me.messages[0];
  837. });
  838. }
  839. if (!isValid) field.isValid = isValid;
  840. msgOpt.msg = msg;
  841. $(el).trigger( (isValid ? 'valid' : 'invalid') + CLS_NS_RULE, [method, msg]);
  842. }
  843. if (special && (!transfer || rule.and)) {
  844. if (!isValid && !field._m) field._m = msg;
  845. field._v = field._v || [];
  846. field._v.push({
  847. type: isValid ? !transfer ? 'ok' : 'tip' : 'error',
  848. msg: msg || rule.msg
  849. });
  850. }
  851. me._debug('log', ' ' + field._i + ': ' + method + ' => ' + (isValid || msg));
  852. // the current rule has passed, continue to validate
  853. if ( (isValid || special) && field._i < field._rules.length - 1) {
  854. field._i++;
  855. me._checkRule(el, field);
  856. }
  857. // field was invalid, or all fields was valid
  858. else {
  859. field._i = 0;
  860. if (special) {
  861. msgOpt.isValid = field.isValid;
  862. msgOpt.result = field._v;
  863. msgOpt.msg = field._m || '';
  864. if (!field.value && (field._e === 'focusin')) {
  865. msgOpt.type = 'tip';
  866. }
  867. } else {
  868. msgOpt.isValid = isValid;
  869. }
  870. me._validatedField(el, field, msgOpt);
  871. delete field._m;
  872. delete field._v;
  873. }
  874. },
  875. // Verify a rule form a field
  876. _checkRule: function(el, field) {
  877. var me = this,
  878. ret,
  879. fn,
  880. old,
  881. key = field.key,
  882. rule = field._rules[field._i],
  883. method = rule.method,
  884. params = rule.params;
  885. // request has been sent, wait it
  886. if (me.submiting && me.deferred[key]) {
  887. return;
  888. }
  889. old = field.old;
  890. field._r = method;
  891. if (old && !field.must && !rule.must && rule.result !== undefined &&
  892. old.ruleName === method && old.id === el.id &&
  893. field.value && old.value === field.value )
  894. {
  895. // get result from cache
  896. ret = rule.result;
  897. }
  898. else {
  899. // get result from current rule
  900. fn = _getDataRule(el, method) || me.rules[method] || noop;
  901. ret = fn.call(field, el, params, field);
  902. if (fn.msg) rule.msg = fn.msg;
  903. }
  904. // asynchronous validation
  905. if (isObject(ret) && isFunction(ret.then)) {
  906. me.deferred[key] = ret;
  907. // whether the field valid is unknown
  908. field.isValid = undefined;
  909. // show loading message
  910. !me.checkOnly && me.showMsg(el, {
  911. type: 'loading',
  912. msg: me.messages.loading
  913. }, field);
  914. // waiting to parse the response data
  915. ret.then(
  916. function(d, textStatus, jqXHR) {
  917. var data = trim(jqXHR.responseText),
  918. result,
  919. dataFilter = field.dataFilter;
  920. // detect if data is json or jsonp format
  921. if (/jsonp?/.test(this.dataType)) {
  922. data = d;
  923. } else if (data.charAt(0) === '{') {
  924. data = $.parseJSON(data);
  925. }
  926. // filter data
  927. result = dataFilter.call(this, data, field);
  928. if (result === undefined) result = dataFilter.call(this, data.data, field);
  929. rule.data = this.data;
  930. rule.result = field.old ? result : undefined;
  931. me._validatedRule(el, field, result);
  932. },
  933. function(jqXHR, textStatus){
  934. me._validatedRule(el, field, me.messages[textStatus] || textStatus);
  935. }
  936. ).always(function(){
  937. delete me.deferred[key];
  938. });
  939. }
  940. // other result
  941. else {
  942. me._validatedRule(el, field, ret);
  943. }
  944. },
  945. // Processing the validation
  946. _validate: function(el, field) {
  947. var me = this;
  948. // doesn't validate the element that has "disabled" or "novalidate" attribute
  949. if ( el.disabled || attr(el, NOVALIDATE) !== null ) {
  950. return;
  951. }
  952. field = field || me.getField(el);
  953. if (!field) return;
  954. if (!field._rules) me._parse(el);
  955. if (!field._rules) return;
  956. me._debug('info', field.key);
  957. field.isValid = true;
  958. field.element = el;
  959. // Cache the value
  960. field.value = field.getValue();
  961. // if the field is not required, and that has a blank value
  962. if (!field.required && !field.must && !field.value) {
  963. if (!_checkable(el)) {
  964. me._validatedField(el, field, {isValid: true});
  965. return true;
  966. }
  967. }
  968. me._checkRule(el, field);
  969. return field.isValid;
  970. },
  971. _debug: function(type, messages) {
  972. if (window.console && this.options.debug) {
  973. console[type](messages);
  974. }
  975. },
  976. /**
  977. * Detecting whether the value of an element that matches a rule
  978. *
  979. * @method test
  980. * @param {Element} el - input element
  981. * @param {String} rule - rule name
  982. */
  983. test: function(el, rule) {
  984. var me = this,
  985. ret,
  986. parts = rRule.exec(rule),
  987. field,
  988. method,
  989. params;
  990. if (parts) {
  991. method = parts[1];
  992. if (method in me.rules) {
  993. params = parts[2] || parts[3];
  994. params = params ? params.split(', ') : undefined;
  995. field = me.getField(el, true);
  996. field._r = method;
  997. field.value = field.getValue();
  998. ret = me.rules[method].call(field, el, params);
  999. }
  1000. }
  1001. return ret === true || ret === undefined || ret === null;
  1002. },
  1003. _getDisplay: function(el, str) {
  1004. return !isString(str) ? isFunction(str) ? str.call(this, el) : '' : str;
  1005. },
  1006. _getMsgOpt: function(obj, field) {
  1007. var opt = field ? field : this.options;
  1008. return $.extend({
  1009. type: 'error',
  1010. pos: _getPos(opt.msgClass),
  1011. target: opt.target,
  1012. wrapper: opt.msgWrapper,
  1013. style: opt.msgStyle,
  1014. cls: opt.msgClass,
  1015. arrow: opt.msgArrow,
  1016. icon: opt.msgIcon
  1017. }, isString(obj) ? {msg: obj} : obj);
  1018. },
  1019. _getMsgDOM: function(el, msgOpt) {
  1020. var $el = $(el), $msgbox, datafor, tgt, container;
  1021. if ( $el.is(INPUT_SELECTOR) ) {
  1022. tgt = msgOpt.target || attr(el, DATA_TARGET);
  1023. if (tgt) {
  1024. tgt = !isFunction(tgt) ? tgt.charAt(0) === '#' ? $(tgt) : this.$el.find(tgt) : tgt.call(this, el);
  1025. if (tgt.length) {
  1026. if ( tgt.is(INPUT_SELECTOR) ) {
  1027. $el = tgt
  1028. el = tgt.get(0);
  1029. } else if ( tgt.hasClass(CLS_MSG_BOX) ) {
  1030. $msgbox = tgt;
  1031. } else {
  1032. container = tgt;
  1033. }
  1034. }
  1035. }
  1036. if (!$msgbox) {
  1037. datafor = (!_checkable(el) || !el.name) && el.id ? el.id : el.name;
  1038. $msgbox = (container || this.$el).find(msgOpt.wrapper + '.' + CLS_MSG_BOX + '[for="' + datafor + '"]');
  1039. }
  1040. } else {
  1041. $msgbox = $el;
  1042. }
  1043. // Create new message box
  1044. if (!msgOpt.hide && !$msgbox.length) {
  1045. $msgbox = $('<'+ msgOpt.wrapper + '>').attr({
  1046. 'class': CLS_MSG_BOX + (msgOpt.cls ? ' ' + msgOpt.cls : ''),
  1047. 'style': msgOpt.style || undefined,
  1048. 'for': datafor
  1049. });
  1050. if (container) {
  1051. $msgbox.appendTo(container);
  1052. } else {
  1053. if ( _checkable(el) ) {
  1054. var $parent = $el.parent();
  1055. $msgbox.appendTo( $parent.is('label') ? $parent.parent() : $parent );
  1056. } else {
  1057. $msgbox[!msgOpt.pos || msgOpt.pos === 'right' ? 'insertAfter' : 'insertBefore']($el);
  1058. }
  1059. }
  1060. }
  1061. return $msgbox;
  1062. },
  1063. /**
  1064. * Show validation message
  1065. *
  1066. * @method showMsg
  1067. * @param {Element} el - input element
  1068. * @param {Object} msgOpt
  1069. */
  1070. showMsg: function(el, msgOpt, /*INTERNAL*/ field) {
  1071. if (!el) return;
  1072. var me = this,
  1073. opt = me.options,
  1074. msgShow,
  1075. msgMaker,
  1076. temp,
  1077. $msgbox;
  1078. if (isObject(el) && !el.jquery && !msgOpt) {
  1079. $.each(el, function(key, msg) {
  1080. var el = me.elements[key] || me.$el.find(_key2selector(key))[0];
  1081. me.showMsg(el, msg);
  1082. });
  1083. return;
  1084. }
  1085. if ($(el).is(INPUT_SELECTOR)) {
  1086. field = field || me.getField(el);
  1087. }
  1088. if (!(msgMaker = (field || opt).msgMaker)) {
  1089. return;
  1090. }
  1091. msgOpt = me._getMsgOpt(msgOpt, field);
  1092. el = (el.name && _checkable(el) ? me.$el.find('input[name="'+ el.name +'"]') : $(el)).get(0);
  1093. // ok or tip
  1094. if (!msgOpt.msg && msgOpt.type !== 'error') {
  1095. temp = attr(el, 'data-' + msgOpt.type);
  1096. if (temp !== null) msgOpt.msg = temp;
  1097. }
  1098. if ( !isString(msgOpt.msg) ) {
  1099. return;
  1100. }
  1101. $msgbox = me._getMsgDOM(el, msgOpt);
  1102. !rPos.test($msgbox[0].className) && $msgbox.addClass(msgOpt.cls);
  1103. if ( isIE === 6 && msgOpt.pos === 'bottom' ) {
  1104. $msgbox[0].style.marginTop = $(el).outerHeight() + 'px';
  1105. }
  1106. $msgbox.html( msgMaker.call(me, msgOpt) )[0].style.display = '';
  1107. if (isFunction(msgShow = field && field.msgShow || opt.msgShow)) {
  1108. msgShow.call(me, $msgbox, msgOpt.type);
  1109. }
  1110. },
  1111. /**
  1112. * Hide validation message
  1113. *
  1114. * @method hideMsg
  1115. * @param {Element} el - input element
  1116. * @param {Object} msgOpt optional
  1117. */
  1118. hideMsg: function(el, msgOpt, /*INTERNAL*/ field) {
  1119. var me = this,
  1120. opt = me.options,
  1121. msgHide,
  1122. $msgbox;
  1123. el = $(el).get(0);
  1124. if ($(el).is(INPUT_SELECTOR)) {
  1125. field = field || me.getField(el);
  1126. if (field) {
  1127. if (field.isValid || me.reseting) attr(el, ARIA_INVALID, null);
  1128. }
  1129. }
  1130. msgOpt = me._getMsgOpt(msgOpt, field);
  1131. msgOpt.hide = true;
  1132. $msgbox = me._getMsgDOM(el, msgOpt);
  1133. if (!$msgbox.length) return;
  1134. if ( isFunction(msgHide = field && field.msgHide || opt.msgHide) ) {
  1135. msgHide.call(me, $msgbox, msgOpt.type);
  1136. } else {
  1137. $msgbox[0].style.display = 'none';
  1138. $msgbox[0].innerHTML = '';
  1139. }
  1140. },
  1141. /**
  1142. * Get field information
  1143. *
  1144. * @method getField
  1145. * @param {Element} - input element
  1146. * @return {Object} field
  1147. */
  1148. getField: function(el, must) {
  1149. var me = this,
  1150. key,
  1151. field;
  1152. if (isString(el)) {
  1153. key = el;
  1154. el = undefined;
  1155. } else {
  1156. if (attr(el, DATA_RULE)) {
  1157. return me._parse(el);
  1158. }
  1159. if (el.id && '#' + el.id in me.fields || !el.name) {
  1160. key = '#' + el.id;
  1161. } else {
  1162. key = el.name;
  1163. }
  1164. }
  1165. if ( (field = me.fields[key]) || must && (field = new me.Field(key)) ) {
  1166. field.element = el;
  1167. }
  1168. return field;
  1169. },
  1170. /**
  1171. * Config a field
  1172. *
  1173. * @method: setField
  1174. * @param {String} key
  1175. * @param {Object} obj
  1176. */
  1177. setField: function(key, obj) {
  1178. var fields = {};
  1179. if (!key) return;
  1180. // update this field
  1181. if (isString(key)) {
  1182. fields[key] = obj;
  1183. }
  1184. // update fields
  1185. else {
  1186. fields = key;
  1187. }
  1188. this._initFields(fields);
  1189. },
  1190. /**
  1191. * Detecting whether the form is valid
  1192. *
  1193. * @method isFormValid
  1194. * @return {Boolean}
  1195. */
  1196. isFormValid: function() {
  1197. var fields = this.fields, k, field;
  1198. for (k in fields) {
  1199. field = fields[k];
  1200. if (!field._rules || !field.required && !field.must && !field.value) continue;
  1201. if (!field.isValid) return false;
  1202. }
  1203. return true;
  1204. },
  1205. /**
  1206. * Prevent submission form
  1207. *
  1208. * @method holdSubmit
  1209. * @param {Boolean} hold - If set to false, will release the hold
  1210. */
  1211. holdSubmit: function(hold) {
  1212. this.submiting = hold === undefined || hold;
  1213. },
  1214. /**
  1215. * Clean validation messages
  1216. *
  1217. * @method cleanUp
  1218. */
  1219. cleanUp: function() {
  1220. this._reset(1);
  1221. },
  1222. /**
  1223. * Destroy the validation
  1224. *
  1225. * @method destroy
  1226. */
  1227. destroy: function() {
  1228. this._reset(1);
  1229. this.$el.off(CLS_NS).removeData(NS);
  1230. attr(this.$el[0], NOVALIDATE, this._NOVALIDATE);
  1231. }
  1232. };
  1233. /**
  1234. * Create Field Factory
  1235. *
  1236. * @class
  1237. * @param {Object} context
  1238. * @return {Function} Factory
  1239. */
  1240. function _createFieldFactory(context) {
  1241. function FieldFactory() {
  1242. var options = this.options;
  1243. for (var i in options) {
  1244. if (i in fieldDefaults) this[i] = options[i];
  1245. }
  1246. $.extend(this, {
  1247. _valHook: function() {
  1248. return this.element.contentEditable === 'true' ? 'text' : 'val';
  1249. },
  1250. getValue: function() {
  1251. var elem = this.element;
  1252. if (elem.type === "number" && elem.validity && elem.validity.badInput) {
  1253. return 'NaN';
  1254. }
  1255. return $(elem)[this._valHook()]();
  1256. },
  1257. setValue: function(value) {
  1258. $(this.element)[this._valHook()](this.value = value);
  1259. },
  1260. // Get a range of validation messages
  1261. getRangeMsg: function(value, params, suffix) {
  1262. if (!params) return;
  1263. var me = this,
  1264. msg = me.messages[me._r] || '',
  1265. result,
  1266. p = params[0].split('~'),
  1267. e = params[1] === 'false',
  1268. a = p[0],
  1269. b = p[1],
  1270. c = 'rg',
  1271. args = [''],
  1272. isNumber = trim(value) && +value === +value;
  1273. function compare(large, small) {
  1274. return !e ? large >= small : large > small;
  1275. }
  1276. if (p.length === 2) {
  1277. if (a && b) {
  1278. if (isNumber && compare(value, +a) && compare(+b, value)) {
  1279. result = true;
  1280. }
  1281. args = args.concat(p);
  1282. c = e ? 'gtlt' : 'rg';
  1283. }
  1284. else if (a && !b) {
  1285. if (isNumber && compare(value, +a)) {
  1286. result = true;
  1287. }
  1288. args.push(a);
  1289. c = e ? 'gt' : 'gte';
  1290. }
  1291. else if (!a && b) {
  1292. if (isNumber && compare(+b, value)) {
  1293. result = true;
  1294. }
  1295. args.push(b);
  1296. c = e ? 'lt' : 'lte';
  1297. }
  1298. }
  1299. else {
  1300. if (value === +a) {
  1301. result = true;
  1302. }
  1303. args.push(a);
  1304. c = 'eq';
  1305. }
  1306. if (msg) {
  1307. if (suffix && msg[c + suffix]) {
  1308. c += suffix;
  1309. }
  1310. args[0] = msg[c];
  1311. }
  1312. return result || me._rules && ( me._rules[me._i].msg = me.renderMsg.apply(null, args) );
  1313. },
  1314. // Render message template
  1315. renderMsg: function() {
  1316. var args = arguments,
  1317. tpl = args[0],
  1318. i = args.length;
  1319. if (!tpl) return;
  1320. while (--i) {
  1321. tpl = tpl.replace('{' + i + '}', args[i]);
  1322. }
  1323. return tpl;
  1324. }
  1325. });
  1326. }
  1327. function Field(key, obj, oldField) {
  1328. this.key = key;
  1329. this.validator = context;
  1330. $.extend(this, oldField, obj);
  1331. }
  1332. FieldFactory.prototype = context;
  1333. Field.prototype = new FieldFactory();
  1334. return Field;
  1335. }
  1336. /**
  1337. * Create Rules
  1338. *
  1339. * @class
  1340. * @param {Object} obj rules
  1341. * @param {Object} context context
  1342. */
  1343. function Rules(obj, context) {
  1344. if (!isObject(obj)) return;
  1345. var k, that = context ? context === true ? this : context : Rules.prototype;
  1346. for (k in obj) {
  1347. if (_checkRuleName(k))
  1348. that[k] = _getRule(obj[k]);
  1349. }
  1350. }
  1351. /**
  1352. * Create Messages
  1353. *
  1354. * @class
  1355. * @param {Object} obj rules
  1356. * @param {Object} context context
  1357. */
  1358. function Messages(obj, context) {
  1359. if (!isObject(obj)) return;
  1360. var k, that = context ? context === true ? this : context : Messages.prototype;
  1361. for (k in obj) {
  1362. that[k] = obj[k];
  1363. }
  1364. }
  1365. // Rule converted factory
  1366. function _getRule(fn) {
  1367. switch ($.type(fn)) {
  1368. case 'function':
  1369. return fn;
  1370. case 'array':
  1371. var f = function() {
  1372. return fn[0].test(this.value) || fn[1] || false;
  1373. };
  1374. f.msg = fn[1];
  1375. return f;
  1376. case 'regexp':
  1377. return function() {
  1378. return fn.test(this.value);
  1379. };
  1380. }
  1381. }
  1382. // Get instance by an element
  1383. function _getInstance(el) {
  1384. var wrap, k, options;
  1385. if (!el || !el.tagName) return;
  1386. switch (el.tagName) {
  1387. case 'INPUT':
  1388. case 'SELECT':
  1389. case 'TEXTAREA':
  1390. case 'BUTTON':
  1391. case 'FIELDSET':
  1392. wrap = el.form || $(el).closest('.' + CLS_WRAPPER);
  1393. break;
  1394. case 'FORM':
  1395. wrap = el;
  1396. break;
  1397. default:
  1398. wrap = $(el).closest('.' + CLS_WRAPPER);
  1399. }
  1400. for (k in preinitialized) {
  1401. if ($(wrap).is(k)) {
  1402. options = preinitialized[k];
  1403. break;
  1404. }
  1405. }
  1406. return $(wrap).data(NS) || $(wrap)[NS](options).data(NS);
  1407. }
  1408. // Get custom rules on the node
  1409. function _getDataRule(el, method) {
  1410. var fn = trim(attr(el, DATA_RULE + '-' + method));
  1411. if ( fn && (fn = new Function("return " + fn)()) ) {
  1412. return _getRule(fn);
  1413. }
  1414. }
  1415. // Get custom messages on the node
  1416. function _getDataMsg(el, field, m) {
  1417. var msg = field.msg,
  1418. item = field._r;
  1419. if ( isObject(msg) ) msg = msg[item];
  1420. if ( !isString(msg) ) {
  1421. msg = attr(el, DATA_MSG + '-' + item) || attr(el, DATA_MSG) || ( m ? isString(m) ? m : m[item] : '');
  1422. }
  1423. return msg;
  1424. }
  1425. // Get message position
  1426. function _getPos(str) {
  1427. var pos;
  1428. if (str) pos = rPos.exec(str);
  1429. return pos && pos[0];
  1430. }
  1431. // Check whether the element is checkbox or radio
  1432. function _checkable(el) {
  1433. return el.tagName === 'INPUT' && el.type === 'checkbox' || el.type === 'radio';
  1434. }
  1435. // Parse date string to timestamp
  1436. function _parseDate(str) {
  1437. return Date.parse(str.replace(/\.|\-/g, '/'));
  1438. }
  1439. // Rule name only allows alphanumeric characters and underscores
  1440. function _checkRuleName(name) {
  1441. return /^\w+$/.test(name);
  1442. }
  1443. // Translate field key to jQuery selector.
  1444. function _key2selector(key) {
  1445. var isID = key.charAt(0) === "#";
  1446. key = key.replace(/([:.{(|)}/\[\]])/g, "\\$1");
  1447. return isID ? key : '[name="'+ key +'"]:first';
  1448. }
  1449. // Fixed a issue cause by refresh page in IE.
  1450. $(window).on('beforeunload', function(){
  1451. this.focus();
  1452. });
  1453. $(document)
  1454. .on('click', ':submit', function(){
  1455. var input = this, attrNode;
  1456. if (!input.form) return;
  1457. // Shim for "formnovalidate"
  1458. attrNode = input.getAttributeNode('formnovalidate');
  1459. if (attrNode && attrNode.nodeValue !== null || attr(input, NOVALIDATE)!== null) {
  1460. novalidateonce = true;
  1461. }
  1462. })
  1463. // Automatic initializing form validation
  1464. .on('focusin submit validate', 'form,.'+CLS_WRAPPER, function(e) {
  1465. if ( attr(this, NOVALIDATE) !== null ) return;
  1466. var $form = $(this), me;
  1467. if ( !$form.data(NS) && (me = _getInstance(this)) ) {
  1468. if ( !$.isEmptyObject(me.fields) ) {
  1469. // Execute event handler
  1470. if (e.type === 'focusin') {
  1471. me._focusin(e);
  1472. } else {
  1473. me._submit(e);
  1474. }
  1475. } else {
  1476. attr(this, NOVALIDATE, NOVALIDATE);
  1477. $form.off(CLS_NS).removeData(NS);
  1478. }
  1479. }
  1480. });
  1481. new Messages({
  1482. fallback: "This field is not valid.",
  1483. loading: 'Validating...'
  1484. });
  1485. // Built-in rules (global)
  1486. new Rules({
  1487. /**
  1488. * required
  1489. *
  1490. * @example:
  1491. required
  1492. required(anotherRule)
  1493. required(not, -1)
  1494. required(from, .contact)
  1495. */
  1496. required: function(element, params) {
  1497. var me = this,
  1498. val = trim(me.value),
  1499. isValid = true;
  1500. if (params) {
  1501. if ( params.length === 1 ) {
  1502. if ( !_checkRuleName(params[0]) ) {
  1503. if (!val && !$(params[0], me.$el).length ) {
  1504. return null;
  1505. }
  1506. }
  1507. else if ( me.rules[params[0]] ) {
  1508. if ( !val && !me.test(element, params[0]) ) {
  1509. return null;
  1510. }
  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( element, 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. */
  1727. remote: function(element, params) {
  1728. if (!params) return;
  1729. var me = this,
  1730. arr = rAjaxType.exec(params[0]),
  1731. rule = me._rules[me._i],
  1732. data = {},
  1733. queryString = '',
  1734. url = arr[3],
  1735. type = arr[2] || 'POST', // GET / POST
  1736. rType = (arr[1]||'').toLowerCase(), // CORS / JSONP
  1737. dataType;
  1738. rule.must = true;
  1739. data[element.name] = me.value;
  1740. // There are extra fields
  1741. if (params[1]) {
  1742. $.map(params.slice(1), function(name) {
  1743. var arr, key;
  1744. if (~name.indexOf('=')) {
  1745. queryString += '&' + name;
  1746. } else {
  1747. arr = name.split(':');
  1748. name = trim(arr[0]);
  1749. key = trim(arr[1]) || name;
  1750. data[ name ] = me.$el.find( _key2selector(key) ).val();
  1751. }
  1752. });
  1753. }
  1754. data = $.param(data) + queryString;
  1755. if (!me.must && rule.data && rule.data === data) {
  1756. return rule.result;
  1757. }
  1758. // Cross-domain request, force jsonp dataType
  1759. if (rType !== 'cors' && /^https?:/.test(url) && !~url.indexOf(location.host)) {
  1760. dataType = 'jsonp';
  1761. }
  1762. // Asynchronous validation need return jqXHR objects
  1763. return $.ajax({
  1764. url: url,
  1765. type: type,
  1766. data: data,
  1767. dataType: dataType
  1768. });
  1769. },
  1770. /**
  1771. * filter characters, direct filtration without prompting error (support custom regular expressions)
  1772. *
  1773. * @example
  1774. * filter filtering unsafe characters
  1775. * filter(regexp) filtering the "regexp" matched characters
  1776. */
  1777. filter: function(element, params) {
  1778. var value = this.value,
  1779. temp = value.replace( params ? (new RegExp("[" + params[0] + "]", "gm")) : rUnsafe, '' );
  1780. if (temp !== value) this.setValue(temp);
  1781. }
  1782. });
  1783. /**
  1784. * Config global options
  1785. *
  1786. * @static config
  1787. * @param {Object} options
  1788. */
  1789. Validator.config = function(key, value) {
  1790. if (isObject(key)) {
  1791. $.each(key, _config);
  1792. }
  1793. else if (isString(key)) {
  1794. _config(key, value);
  1795. }
  1796. function _config(k, o) {
  1797. if (k === 'rules') {
  1798. new Rules(o);
  1799. }
  1800. else if (k === 'messages') {
  1801. new Messages(o);
  1802. }
  1803. else if (k in fieldDefaults) {
  1804. fieldDefaults[k] = o;
  1805. }
  1806. else {
  1807. defaults[k] = o;
  1808. }
  1809. }
  1810. };
  1811. /**
  1812. * Config themes
  1813. *
  1814. * @static setTheme
  1815. * @param {String|Object} name
  1816. * @param {Object} obj
  1817. * @example
  1818. .setTheme( themeName, themeOptions )
  1819. .setTheme( multiThemes )
  1820. */
  1821. Validator.setTheme = function(name, obj) {
  1822. if ( isObject(name) ) {
  1823. $.extend(true, themes, name);
  1824. }
  1825. else if ( isString(name) && isObject(obj) ) {
  1826. themes[name] = $.extend(themes[name], obj);
  1827. }
  1828. };
  1829. /**
  1830. * Resource loader
  1831. *
  1832. * @static load
  1833. * @param {String} str
  1834. * @example
  1835. .load('local=zh-CN') // load: local/zh-CN.js and jquery.validator.css
  1836. .load('local=zh-CN&css=') // load: local/zh-CN.js
  1837. .load('local&css') // load: local/en.js (set <html lang="en">) and jquery.validator.css
  1838. .load('local') // dito
  1839. */
  1840. Validator.load = function(str) {
  1841. if (!str) return;
  1842. var doc = document,
  1843. params = {},
  1844. node = doc.scripts[0],
  1845. dir, el, ONLOAD;
  1846. str.replace(/([^?=&]+)=([^&#]*)/g, function(m, key, value){
  1847. params[key] = value;
  1848. });
  1849. dir = params.dir || Validator.dir;
  1850. if (!Validator.css && params.css !== '') {
  1851. el = doc.createElement('link');
  1852. el.rel = 'stylesheet';
  1853. el.href = Validator.css = dir + 'jquery.validator.css';
  1854. node.parentNode.insertBefore(el, node);
  1855. }
  1856. if (!Validator.local && ~str.indexOf('local') && params.local !== '') {
  1857. Validator.local = (params.local || doc.documentElement.lang || 'en').replace('_','-');
  1858. Validator.pending = 1;
  1859. el = doc.createElement('script');
  1860. el.src = dir + 'local/' + Validator.local + '.js';
  1861. ONLOAD = 'onload' in el ? 'onload' : 'onreadystatechange';
  1862. el[ONLOAD] = function() {
  1863. if (!el.readyState || /loaded|complete/.test(el.readyState)) {
  1864. el = el[ONLOAD] = null;
  1865. delete Validator.pending;
  1866. $(window).triggerHandler('validatorready');
  1867. }
  1868. };
  1869. node.parentNode.insertBefore(el, node);
  1870. }
  1871. };
  1872. // Auto loading resources
  1873. (function(){
  1874. var scripts = document.scripts,
  1875. i = scripts.length, node, arr,
  1876. re = /(.*validator(?:\.min)?.js)(\?.*(?:local|css|dir)(?:=[\w\-]*)?)?/;
  1877. while (i-- && !arr) {
  1878. node = scripts[i];
  1879. arr = (node.hasAttribute ? node.src : node.getAttribute('src',4)||'').match(re);
  1880. }
  1881. if (!arr) return;
  1882. Validator.dir = arr[1].split('/').slice(0, -1).join('/')+'/';
  1883. Validator.load(arr[2]);
  1884. })();
  1885. return $[NS] = Validator;
  1886. }));