bootstrapValidator.js 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962
  1. /**
  2. * BootstrapValidator (http://bootstrapvalidator.com)
  3. * The best jQuery plugin to validate form fields. Designed to use with Bootstrap 3
  4. *
  5. * @author https://twitter.com/nghuuphuoc
  6. * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc
  7. * @license MIT
  8. */
  9. if (typeof jQuery === 'undefined') {
  10. throw new Error('BootstrapValidator\'s JavaScript requires jQuery');
  11. }
  12. (function($) {
  13. var BootstrapValidator = function(form, options) {
  14. this.$form = $(form);
  15. this.options = $.extend({}, $.fn.bootstrapValidator.DEFAULT_OPTIONS, options);
  16. this.$invalidFields = $([]); // Array of invalid fields
  17. this.$submitButton = null; // The submit button which is clicked to submit form
  18. this.$hiddenButton = null;
  19. // Validating status
  20. this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED';
  21. this.STATUS_VALIDATING = 'VALIDATING';
  22. this.STATUS_INVALID = 'INVALID';
  23. this.STATUS_VALID = 'VALID';
  24. // Determine the event that is fired when user change the field value
  25. // Most modern browsers supports input event except IE 7, 8.
  26. // IE 9 supports input event but the event is still not fired if I press the backspace key.
  27. // Get IE version
  28. // https://gist.github.com/padolsey/527683/#comment-7595
  29. var ieVersion = (function() {
  30. var v = 3, div = document.createElement('div'), a = div.all || [];
  31. while (div.innerHTML = '<!--[if gt IE '+(++v)+']><br><![endif]-->', a[0]) {}
  32. return v > 4 ? v : !v;
  33. }());
  34. var el = document.createElement('div');
  35. this._changeEvent = (ieVersion === 9 || !('oninput' in el)) ? 'keyup' : 'input';
  36. // The flag to indicate that the form is ready to submit when a remote/callback validator returns
  37. this._submitIfValid = null;
  38. // Field elements
  39. this._cacheFields = {};
  40. this._init();
  41. };
  42. BootstrapValidator.prototype = {
  43. constructor: BootstrapValidator,
  44. /**
  45. * Init form
  46. */
  47. _init: function() {
  48. var that = this,
  49. options = {
  50. container: this.$form.attr('data-bv-container'),
  51. events: {
  52. formInit: this.$form.attr('data-bv-events-form-init'),
  53. formError: this.$form.attr('data-bv-events-form-error'),
  54. formSuccess: this.$form.attr('data-bv-events-form-success'),
  55. fieldAdded: this.$form.attr('data-bv-events-field-added'),
  56. fieldRemoved: this.$form.attr('data-bv-events-field-removed'),
  57. fieldInit: this.$form.attr('data-bv-events-field-init'),
  58. fieldError: this.$form.attr('data-bv-events-field-error'),
  59. fieldSuccess: this.$form.attr('data-bv-events-field-success'),
  60. fieldStatus: this.$form.attr('data-bv-events-field-status'),
  61. validatorError: this.$form.attr('data-bv-events-validator-error'),
  62. validatorSuccess: this.$form.attr('data-bv-events-validator-success')
  63. },
  64. excluded: this.$form.attr('data-bv-excluded'),
  65. feedbackIcons: {
  66. valid: this.$form.attr('data-bv-feedbackicons-valid'),
  67. invalid: this.$form.attr('data-bv-feedbackicons-invalid'),
  68. validating: this.$form.attr('data-bv-feedbackicons-validating')
  69. },
  70. group: this.$form.attr('data-bv-group'),
  71. live: this.$form.attr('data-bv-live'),
  72. message: this.$form.attr('data-bv-message'),
  73. onError: this.$form.attr('data-bv-onerror'),
  74. onSuccess: this.$form.attr('data-bv-onsuccess'),
  75. submitButtons: this.$form.attr('data-bv-submitbuttons'),
  76. threshold: this.$form.attr('data-bv-threshold'),
  77. trigger: this.$form.attr('data-bv-trigger'),
  78. verbose: this.$form.attr('data-bv-verbose'),
  79. fields: {}
  80. };
  81. this.$form
  82. // Disable client side validation in HTML 5
  83. .attr('novalidate', 'novalidate')
  84. .addClass(this.options.elementClass)
  85. // Disable the default submission first
  86. .on('submit.bv', function(e) {
  87. e.preventDefault();
  88. that.validate();
  89. })
  90. .on('click.bv', this.options.submitButtons, function() {
  91. that.$submitButton = $(this);
  92. // The user just click the submit button
  93. that._submitIfValid = true;
  94. })
  95. // Find all fields which have either "name" or "data-bv-field" attribute
  96. .find('[name], [data-bv-field]')
  97. .each(function() {
  98. var $field = $(this),
  99. field = $field.attr('name') || $field.attr('data-bv-field'),
  100. opts = that._parseOptions($field);
  101. if (opts) {
  102. $field.attr('data-bv-field', field);
  103. options.fields[field] = $.extend({}, opts, options.fields[field]);
  104. }
  105. });
  106. this.options = $.extend(true, this.options, options);
  107. // When pressing Enter on any field in the form, the first submit button will do its job.
  108. // The form then will be submitted.
  109. // I create a first hidden submit button
  110. this.$hiddenButton = $('<button/>')
  111. .attr('type', 'submit')
  112. .prependTo(this.$form)
  113. .addClass('bv-hidden-submit')
  114. .css({ display: 'none', width: 0, height: 0 });
  115. this.$form
  116. .on('click.bv', '[type="submit"]', function(e) {
  117. // #746: Check if the button click handler returns false
  118. if (!e.isDefaultPrevented()) {
  119. var $target = $(e.target),
  120. // The button might contain HTML tag
  121. $button = $target.is('[type="submit"]') ? $target.eq(0) : $target.parent('[type="submit"]').eq(0);
  122. // Don't perform validation when clicking on the submit button/input
  123. // which aren't defined by the 'submitButtons' option
  124. if (that.options.submitButtons && !$button.is(that.options.submitButtons) && !$button.is(that.$hiddenButton)) {
  125. that.$form.off('submit.bv').submit();
  126. }
  127. }
  128. });
  129. for (var field in this.options.fields) {
  130. this._initField(field);
  131. }
  132. this.$form.trigger($.Event(this.options.events.formInit), {
  133. bv: this,
  134. options: this.options
  135. });
  136. // Prepare the events
  137. if (this.options.onSuccess) {
  138. this.$form.on(this.options.events.formSuccess, function(e) {
  139. $.fn.bootstrapValidator.helpers.call(that.options.onSuccess, [e]);
  140. });
  141. }
  142. if (this.options.onError) {
  143. this.$form.on(this.options.events.formError, function(e) {
  144. $.fn.bootstrapValidator.helpers.call(that.options.onError, [e]);
  145. });
  146. }
  147. },
  148. /**
  149. * Parse the validator options from HTML attributes
  150. *
  151. * @param {jQuery} $field The field element
  152. * @returns {Object}
  153. */
  154. _parseOptions: function($field) {
  155. var field = $field.attr('name') || $field.attr('data-bv-field'),
  156. validators = {},
  157. validator,
  158. v, // Validator name
  159. attrName,
  160. enabled,
  161. optionName,
  162. optionAttrName,
  163. optionValue,
  164. html5AttrName,
  165. html5AttrMap;
  166. for (v in $.fn.bootstrapValidator.validators) {
  167. validator = $.fn.bootstrapValidator.validators[v];
  168. attrName = 'data-bv-' + v.toLowerCase(),
  169. enabled = $field.attr(attrName) + '';
  170. html5AttrMap = ('function' === typeof validator.enableByHtml5) ? validator.enableByHtml5($field) : null;
  171. if ((html5AttrMap && enabled !== 'false')
  172. || (html5AttrMap !== true && ('' === enabled || 'true' === enabled || attrName === enabled.toLowerCase())))
  173. {
  174. // Try to parse the options via attributes
  175. validator.html5Attributes = $.extend({}, { message: 'message', onerror: 'onError', onsuccess: 'onSuccess' }, validator.html5Attributes);
  176. validators[v] = $.extend({}, html5AttrMap === true ? {} : html5AttrMap, validators[v]);
  177. for (html5AttrName in validator.html5Attributes) {
  178. optionName = validator.html5Attributes[html5AttrName];
  179. optionAttrName = 'data-bv-' + v.toLowerCase() + '-' + html5AttrName,
  180. optionValue = $field.attr(optionAttrName);
  181. if (optionValue) {
  182. if ('true' === optionValue || optionAttrName === optionValue.toLowerCase()) {
  183. optionValue = true;
  184. } else if ('false' === optionValue) {
  185. optionValue = false;
  186. }
  187. validators[v][optionName] = optionValue;
  188. }
  189. }
  190. }
  191. }
  192. var opts = {
  193. container: $field.attr('data-bv-container'),
  194. excluded: $field.attr('data-bv-excluded'),
  195. feedbackIcons: $field.attr('data-bv-feedbackicons'),
  196. group: $field.attr('data-bv-group'),
  197. message: $field.attr('data-bv-message'),
  198. onError: $field.attr('data-bv-onerror'),
  199. onStatus: $field.attr('data-bv-onstatus'),
  200. onSuccess: $field.attr('data-bv-onsuccess'),
  201. selector: $field.attr('data-bv-selector'),
  202. threshold: $field.attr('data-bv-threshold'),
  203. trigger: $field.attr('data-bv-trigger'),
  204. verbose: $field.attr('data-bv-verbose'),
  205. validators: validators
  206. },
  207. emptyOptions = $.isEmptyObject(opts), // Check if the field options are set using HTML attributes
  208. emptyValidators = $.isEmptyObject(validators); // Check if the field validators are set using HTML attributes
  209. if (!emptyValidators || (!emptyOptions && this.options.fields && this.options.fields[field])) {
  210. opts.validators = validators;
  211. return opts;
  212. } else {
  213. return null;
  214. }
  215. },
  216. /**
  217. * Init field
  218. *
  219. * @param {String|jQuery} field The field name or field element
  220. */
  221. _initField: function(field) {
  222. var fields = $([]);
  223. switch (typeof field) {
  224. case 'object':
  225. fields = field;
  226. field = field.attr('data-bv-field');
  227. break;
  228. case 'string':
  229. fields = this.getFieldElements(field);
  230. fields.attr('data-bv-field', field);
  231. break;
  232. default:
  233. break;
  234. }
  235. // We don't need to validate non-existing fields
  236. if (fields.length === 0) {
  237. return;
  238. }
  239. if (this.options.fields[field] === null || this.options.fields[field].validators === null) {
  240. return;
  241. }
  242. var validatorName;
  243. for (validatorName in this.options.fields[field].validators) {
  244. if (!$.fn.bootstrapValidator.validators[validatorName]) {
  245. delete this.options.fields[field].validators[validatorName];
  246. }
  247. }
  248. if (this.options.fields[field].enabled === null) {
  249. this.options.fields[field].enabled = true;
  250. }
  251. var that = this,
  252. total = fields.length,
  253. type = fields.attr('type'),
  254. updateAll = (total === 1) || ('radio' === type) || ('checkbox' === type),
  255. event = ('radio' === type || 'checkbox' === type || 'file' === type || 'SELECT' === fields.eq(0).get(0).tagName) ? 'change' : this._changeEvent,
  256. trigger = (this.options.fields[field].trigger || this.options.trigger || event).split(' '),
  257. events = $.map(trigger, function(item) {
  258. return item + '.update.bv';
  259. }).join(' ');
  260. for (var i = 0; i < total; i++) {
  261. var $field = fields.eq(i),
  262. group = this.options.fields[field].group || this.options.group,
  263. $parent = $field.parents(group),
  264. // Allow user to indicate where the error messages are shown
  265. container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container),
  266. $message = (container && container !== 'tooltip' && container !== 'popover') ? $(container) : this._getMessageContainer($field, group);
  267. if (container && container !== 'tooltip' && container !== 'popover') {
  268. $message.addClass('has-error');
  269. }
  270. // Remove all error messages and feedback icons
  271. $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]').remove();
  272. $parent.find('i[data-bv-icon-for="' + field + '"]').remove();
  273. // Whenever the user change the field value, mark it as not validated yet
  274. $field.off(events).on(events, function() {
  275. that.updateStatus($(this), that.STATUS_NOT_VALIDATED);
  276. });
  277. // Create help block elements for showing the error messages
  278. $field.data('bv.messages', $message);
  279. for (validatorName in this.options.fields[field].validators) {
  280. $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
  281. if (!updateAll || i === total - 1) {
  282. $('<small/>')
  283. .css('display', 'none')
  284. .addClass('help-block')
  285. .attr('data-bv-validator', validatorName)
  286. .attr('data-bv-for', field)
  287. .attr('data-bv-result', this.STATUS_NOT_VALIDATED)
  288. .html(this._getMessage(field, validatorName))
  289. .appendTo($message);
  290. }
  291. // Init the validator
  292. if ('function' === typeof $.fn.bootstrapValidator.validators[validatorName].init) {
  293. $.fn.bootstrapValidator.validators[validatorName].init(this, $field, this.options.fields[field].validators[validatorName]);
  294. }
  295. }
  296. // Prepare the feedback icons
  297. // Available from Bootstrap 3.1 (http://getbootstrap.com/css/#forms-control-validation)
  298. if (this.options.fields[field].feedbackIcons !== false && this.options.fields[field].feedbackIcons !== 'false'
  299. && this.options.feedbackIcons
  300. && this.options.feedbackIcons.validating && this.options.feedbackIcons.invalid && this.options.feedbackIcons.valid
  301. && (!updateAll || i === total - 1))
  302. {
  303. // $parent.removeClass('has-success').removeClass('has-error').addClass('has-feedback');
  304. // Keep error messages which are populated from back-end
  305. $parent.addClass('has-feedback');
  306. var $icon = $('<i/>')
  307. .css('display', 'none')
  308. .addClass('form-control-feedback')
  309. .attr('data-bv-icon-for', field)
  310. .insertAfter($field);
  311. // Place it after the container of checkbox/radio
  312. // so when clicking the icon, it doesn't effect to the checkbox/radio element
  313. if ('checkbox' === type || 'radio' === type) {
  314. var $fieldParent = $field.parent();
  315. if ($fieldParent.hasClass(type)) {
  316. $icon.insertAfter($fieldParent);
  317. } else if ($fieldParent.parent().hasClass(type)) {
  318. $icon.insertAfter($fieldParent.parent());
  319. }
  320. }
  321. // The feedback icon does not render correctly if there is no label
  322. // https://github.com/twbs/bootstrap/issues/12873
  323. if ($parent.find('label').length === 0) {
  324. $icon.addClass('bv-no-label');
  325. }
  326. // Fix feedback icons in input-group
  327. if ($parent.find('.input-group').length !== 0) {
  328. $icon.addClass('bv-icon-input-group')
  329. .insertAfter($parent.find('.input-group').eq(0));
  330. }
  331. if (container) {
  332. $field
  333. // Show tooltip/popover message when field gets focus
  334. .off('focus.container.bv')
  335. .on('focus.container.bv', function() {
  336. switch (container) {
  337. case 'tooltip':
  338. $icon.tooltip('show');
  339. break;
  340. case 'popover':
  341. $icon.popover('show');
  342. break;
  343. default:
  344. break;
  345. }
  346. })
  347. // and hide them when losing focus
  348. .off('blur.container.bv')
  349. .on('blur.container.bv', function() {
  350. switch (container) {
  351. case 'tooltip':
  352. $icon.tooltip('hide');
  353. break;
  354. case 'popover':
  355. $icon.popover('hide');
  356. break;
  357. default:
  358. break;
  359. }
  360. });
  361. }
  362. }
  363. }
  364. // Prepare the events
  365. fields
  366. .on(this.options.events.fieldSuccess, function(e, data) {
  367. var onSuccess = that.getOptions(data.field, null, 'onSuccess');
  368. if (onSuccess) {
  369. $.fn.bootstrapValidator.helpers.call(onSuccess, [e, data]);
  370. }
  371. })
  372. .on(this.options.events.fieldError, function(e, data) {
  373. var onError = that.getOptions(data.field, null, 'onError');
  374. if (onError) {
  375. $.fn.bootstrapValidator.helpers.call(onError, [e, data]);
  376. }
  377. })
  378. .on(this.options.events.fieldStatus, function(e, data) {
  379. var onStatus = that.getOptions(data.field, null, 'onStatus');
  380. if (onStatus) {
  381. $.fn.bootstrapValidator.helpers.call(onStatus, [e, data]);
  382. }
  383. })
  384. .on(this.options.events.validatorError, function(e, data) {
  385. var onError = that.getOptions(data.field, data.validator, 'onError');
  386. if (onError) {
  387. $.fn.bootstrapValidator.helpers.call(onError, [e, data]);
  388. }
  389. })
  390. .on(this.options.events.validatorSuccess, function(e, data) {
  391. var onSuccess = that.getOptions(data.field, data.validator, 'onSuccess');
  392. if (onSuccess) {
  393. $.fn.bootstrapValidator.helpers.call(onSuccess, [e, data]);
  394. }
  395. });
  396. // Set live mode
  397. events = $.map(trigger, function(item) {
  398. return item + '.live.bv';
  399. }).join(' ');
  400. switch (this.options.live) {
  401. case 'submitted':
  402. break;
  403. case 'disabled':
  404. fields.off(events);
  405. break;
  406. case 'enabled':
  407. /* falls through */
  408. default:
  409. fields.off(events).on(events, function(e) {
  410. // #1040: The input with placeholder is auto validated on IE 10, 11
  411. if ('input' === e.type && document.activeElement !== this) {
  412. return;
  413. }
  414. if (that._exceedThreshold($(this))) {
  415. that.validateField($(this));
  416. }
  417. });
  418. break;
  419. }
  420. fields.trigger($.Event(this.options.events.fieldInit), {
  421. bv: this,
  422. field: field,
  423. element: fields
  424. });
  425. },
  426. /**
  427. * Get the error message for given field and validator
  428. *
  429. * @param {String} field The field name
  430. * @param {String} validatorName The validator name
  431. * @returns {String}
  432. */
  433. _getMessage: function(field, validatorName) {
  434. if (!this.options.fields[field] || !$.fn.bootstrapValidator.validators[validatorName]
  435. || !this.options.fields[field].validators || !this.options.fields[field].validators[validatorName])
  436. {
  437. return '';
  438. }
  439. var options = this.options.fields[field].validators[validatorName];
  440. switch (true) {
  441. case (!!options.message):
  442. return options.message;
  443. case (!!this.options.fields[field].message):
  444. return this.options.fields[field].message;
  445. case (!!$.fn.bootstrapValidator.i18n[validatorName]):
  446. return $.fn.bootstrapValidator.i18n[validatorName]['default'];
  447. default:
  448. return this.options.message;
  449. }
  450. },
  451. /**
  452. * Get the element to place the error messages
  453. *
  454. * @param {jQuery} $field The field element
  455. * @param {String} group
  456. * @returns {jQuery}
  457. */
  458. _getMessageContainer: function($field, group) {
  459. var $parent = $field.parent();
  460. if ($parent.is(group)) {
  461. return $parent;
  462. }
  463. var cssClasses = $parent.attr('class');
  464. if (!cssClasses) {
  465. return this._getMessageContainer($parent, group);
  466. }
  467. cssClasses = cssClasses.split(' ');
  468. var n = cssClasses.length;
  469. for (var i = 0; i < n; i++) {
  470. if (/^col-(xs|sm|md|lg)-\d+$/.test(cssClasses[i]) || /^col-(xs|sm|md|lg)-offset-\d+$/.test(cssClasses[i])) {
  471. return $parent;
  472. }
  473. }
  474. return this._getMessageContainer($parent, group);
  475. },
  476. /**
  477. * Called when all validations are completed
  478. */
  479. _submit: function() {
  480. var isValid = this.isValid(),
  481. eventType = isValid ? this.options.events.formSuccess : this.options.events.formError,
  482. e = $.Event(eventType);
  483. this.$form.trigger(e);
  484. // Call default handler
  485. // Check if whether the submit button is clicked
  486. if (this.$submitButton) {
  487. isValid ? this._onSuccess(e) : this._onError(e);
  488. }
  489. },
  490. /**
  491. * Check if the field is excluded.
  492. * Returning true means that the field will not be validated
  493. *
  494. * @param {jQuery} $field The field element
  495. * @returns {Boolean}
  496. */
  497. _isExcluded: function($field) {
  498. var excludedAttr = $field.attr('data-bv-excluded'),
  499. // I still need to check the 'name' attribute while initializing the field
  500. field = $field.attr('data-bv-field') || $field.attr('name');
  501. switch (true) {
  502. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'true' || this.options.fields[field].excluded === true)):
  503. case (excludedAttr === 'true'):
  504. case (excludedAttr === ''):
  505. return true;
  506. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'false' || this.options.fields[field].excluded === false)):
  507. case (excludedAttr === 'false'):
  508. return false;
  509. default:
  510. if (this.options.excluded) {
  511. // Convert to array first
  512. if ('string' === typeof this.options.excluded) {
  513. this.options.excluded = $.map(this.options.excluded.split(','), function(item) {
  514. // Trim the spaces
  515. return $.trim(item);
  516. });
  517. }
  518. var length = this.options.excluded.length;
  519. for (var i = 0; i < length; i++) {
  520. if (('string' === typeof this.options.excluded[i] && $field.is(this.options.excluded[i]))
  521. || ('function' === typeof this.options.excluded[i] && this.options.excluded[i].call(this, $field, this) === true))
  522. {
  523. return true;
  524. }
  525. }
  526. }
  527. return false;
  528. }
  529. },
  530. /**
  531. * Check if the number of characters of field value exceed the threshold or not
  532. *
  533. * @param {jQuery} $field The field element
  534. * @returns {Boolean}
  535. */
  536. _exceedThreshold: function($field) {
  537. var field = $field.attr('data-bv-field'),
  538. threshold = this.options.fields[field].threshold || this.options.threshold;
  539. if (!threshold) {
  540. return true;
  541. }
  542. var cannotType = $.inArray($field.attr('type'), ['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit']) !== -1;
  543. return (cannotType || $field.val().length >= threshold);
  544. },
  545. // ---
  546. // Events
  547. // ---
  548. /**
  549. * The default handler of error.form.bv event.
  550. * It will be called when there is a invalid field
  551. *
  552. * @param {jQuery.Event} e The jQuery event object
  553. */
  554. _onError: function(e) {
  555. if (e.isDefaultPrevented()) {
  556. return;
  557. }
  558. if ('submitted' === this.options.live) {
  559. // Enable live mode
  560. this.options.live = 'enabled';
  561. var that = this;
  562. for (var field in this.options.fields) {
  563. (function(f) {
  564. var fields = that.getFieldElements(f);
  565. if (fields.length) {
  566. var type = $(fields[0]).attr('type'),
  567. event = ('radio' === type || 'checkbox' === type || 'file' === type || 'SELECT' === $(fields[0]).get(0).tagName) ? 'change' : that._changeEvent,
  568. trigger = that.options.fields[field].trigger || that.options.trigger || event,
  569. events = $.map(trigger.split(' '), function(item) {
  570. return item + '.live.bv';
  571. }).join(' ');
  572. fields.off(events).on(events, function() {
  573. if (that._exceedThreshold($(this))) {
  574. that.validateField($(this));
  575. }
  576. });
  577. }
  578. })(field);
  579. }
  580. }
  581. var $invalidField = this.$invalidFields.eq(0);
  582. if ($invalidField) {
  583. // Activate the tab containing the invalid field if exists
  584. var $tabPane = $invalidField.parents('.tab-pane'), tabId;
  585. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  586. $('a[href="#' + tabId + '"][data-toggle="tab"]').tab('show');
  587. }
  588. // Focus to the first invalid field
  589. $invalidField.focus();
  590. }
  591. },
  592. /**
  593. * The default handler of success.form.bv event.
  594. * It will be called when all the fields are valid
  595. *
  596. * @param {jQuery.Event} e The jQuery event object
  597. */
  598. _onSuccess: function(e) {
  599. if (e.isDefaultPrevented()) {
  600. return;
  601. }
  602. // Submit the form
  603. this.disableSubmitButtons(true).defaultSubmit();
  604. },
  605. /**
  606. * Called after validating a field element
  607. *
  608. * @param {jQuery} $field The field element
  609. * @param {String} [validatorName] The validator name
  610. */
  611. _onFieldValidated: function($field, validatorName) {
  612. var field = $field.attr('data-bv-field'),
  613. validators = this.options.fields[field].validators,
  614. counter = {},
  615. numValidators = 0,
  616. data = {
  617. bv: this,
  618. field: field,
  619. element: $field,
  620. validator: validatorName,
  621. result: $field.data('bv.response.' + validatorName)
  622. };
  623. // Trigger an event after given validator completes
  624. if (validatorName) {
  625. switch ($field.data('bv.result.' + validatorName)) {
  626. case this.STATUS_INVALID:
  627. $field.trigger($.Event(this.options.events.validatorError), data);
  628. break;
  629. case this.STATUS_VALID:
  630. $field.trigger($.Event(this.options.events.validatorSuccess), data);
  631. break;
  632. default:
  633. break;
  634. }
  635. }
  636. counter[this.STATUS_NOT_VALIDATED] = 0;
  637. counter[this.STATUS_VALIDATING] = 0;
  638. counter[this.STATUS_INVALID] = 0;
  639. counter[this.STATUS_VALID] = 0;
  640. for (var v in validators) {
  641. if (validators[v].enabled === false) {
  642. continue;
  643. }
  644. numValidators++;
  645. var result = $field.data('bv.result.' + v);
  646. if (result) {
  647. counter[result]++;
  648. }
  649. }
  650. if (counter[this.STATUS_VALID] === numValidators) {
  651. // Remove from the list of invalid fields
  652. this.$invalidFields = this.$invalidFields.not($field);
  653. $field.trigger($.Event(this.options.events.fieldSuccess), data);
  654. }
  655. // If all validators are completed and there is at least one validator which doesn't pass
  656. else if (counter[this.STATUS_NOT_VALIDATED] === 0 && counter[this.STATUS_VALIDATING] === 0 && counter[this.STATUS_INVALID] > 0) {
  657. // Add to the list of invalid fields
  658. this.$invalidFields = this.$invalidFields.add($field);
  659. $field.trigger($.Event(this.options.events.fieldError), data);
  660. }
  661. },
  662. // ---
  663. // Public methods
  664. // ---
  665. /**
  666. * Retrieve the field elements by given name
  667. *
  668. * @param {String} field The field name
  669. * @returns {null|jQuery[]}
  670. */
  671. getFieldElements: function(field) {
  672. if (!this._cacheFields[field]) {
  673. this._cacheFields[field] = (this.options.fields[field] && this.options.fields[field].selector)
  674. ? $(this.options.fields[field].selector)
  675. : this.$form.find('[name="' + field + '"]');
  676. }
  677. return this._cacheFields[field];
  678. },
  679. /**
  680. * Get the field options
  681. *
  682. * @param {String|jQuery} [field] The field name or field element. If it is not set, the method returns the form options
  683. * @param {String} [validator] The name of validator. It null, the method returns form options
  684. * @param {String} [option] The option name
  685. * @return {String|Object}
  686. */
  687. getOptions: function(field, validator, option) {
  688. if (!field) {
  689. return this.options;
  690. }
  691. if ('object' === typeof field) {
  692. field = field.attr('data-bv-field');
  693. }
  694. if (!this.options.fields[field]) {
  695. return null;
  696. }
  697. var options = this.options.fields[field];
  698. if (!validator) {
  699. return option ? options[option] : options;
  700. }
  701. if (!options.validators || !options.validators[validator]) {
  702. return null;
  703. }
  704. return option ? options.validators[validator][option] : options.validators[validator];
  705. },
  706. /**
  707. * Disable/enable submit buttons
  708. *
  709. * @param {Boolean} disabled Can be true or false
  710. * @returns {BootstrapValidator}
  711. */
  712. disableSubmitButtons: function(disabled) {
  713. if (!disabled) {
  714. this.$form.find(this.options.submitButtons).removeAttr('disabled');
  715. } else if (this.options.live !== 'disabled') {
  716. // Don't disable if the live validating mode is disabled
  717. this.$form.find(this.options.submitButtons).attr('disabled', 'disabled');
  718. }
  719. return this;
  720. },
  721. /**
  722. * Validate the form
  723. *
  724. * @returns {BootstrapValidator}
  725. */
  726. validate: function() {
  727. if (!this.options.fields) {
  728. return this;
  729. }
  730. this.disableSubmitButtons(true);
  731. this._submitIfValid = false;
  732. for (var field in this.options.fields) {
  733. this.validateField(field);
  734. }
  735. this._submit();
  736. this._submitIfValid = true;
  737. return this;
  738. },
  739. /**
  740. * Validate given field
  741. *
  742. * @param {String|jQuery} field The field name or field element
  743. * @returns {BootstrapValidator}
  744. */
  745. validateField: function(field) {
  746. var fields = $([]);
  747. switch (typeof field) {
  748. case 'object':
  749. fields = field;
  750. field = field.attr('data-bv-field');
  751. break;
  752. case 'string':
  753. fields = this.getFieldElements(field);
  754. break;
  755. default:
  756. break;
  757. }
  758. if (fields.length === 0 || !this.options.fields[field] || this.options.fields[field].enabled === false) {
  759. return this;
  760. }
  761. var that = this,
  762. type = fields.attr('type'),
  763. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  764. updateAll = ('radio' === type || 'checkbox' === type),
  765. validators = this.options.fields[field].validators,
  766. verbose = this.options.fields[field].verbose === 'true' || this.options.fields[field].verbose === true || this.options.verbose === 'true' || this.options.verbose === true,
  767. validatorName,
  768. validateResult;
  769. for (var i = 0; i < total; i++) {
  770. var $field = fields.eq(i);
  771. if (this._isExcluded($field)) {
  772. continue;
  773. }
  774. var stop = false;
  775. for (validatorName in validators) {
  776. if ($field.data('bv.dfs.' + validatorName)) {
  777. $field.data('bv.dfs.' + validatorName).reject();
  778. }
  779. if (stop) {
  780. break;
  781. }
  782. // Don't validate field if it is already done
  783. var result = $field.data('bv.result.' + validatorName);
  784. if (result === this.STATUS_VALID || result === this.STATUS_INVALID) {
  785. this._onFieldValidated($field, validatorName);
  786. continue;
  787. } else if (validators[validatorName].enabled === false) {
  788. this.updateStatus(updateAll ? field : $field, this.STATUS_VALID, validatorName);
  789. continue;
  790. }
  791. $field.data('bv.result.' + validatorName, this.STATUS_VALIDATING);
  792. validateResult = $.fn.bootstrapValidator.validators[validatorName].validate(this, $field, validators[validatorName]);
  793. // validateResult can be a $.Deferred object ...
  794. if ('object' === typeof validateResult && validateResult.resolve) {
  795. this.updateStatus(updateAll ? field : $field, this.STATUS_VALIDATING, validatorName);
  796. $field.data('bv.dfs.' + validatorName, validateResult);
  797. validateResult.done(function($f, v, response) {
  798. // v is validator name
  799. $f.removeData('bv.dfs.' + v).data('bv.response.' + v, response);
  800. if (response.message) {
  801. that.updateMessage($f, v, response.message);
  802. }
  803. that.updateStatus(updateAll ? $f.attr('data-bv-field') : $f, response.valid ? that.STATUS_VALID : that.STATUS_INVALID, v);
  804. if (response.valid && that._submitIfValid === true) {
  805. // If a remote validator returns true and the form is ready to submit, then do it
  806. that._submit();
  807. } else if (!response.valid && !verbose) {
  808. stop = true;
  809. }
  810. });
  811. }
  812. // ... or object { valid: true/false, message: 'dynamic message' }
  813. else if ('object' === typeof validateResult && validateResult.valid !== undefined && validateResult.message !== undefined) {
  814. $field.data('bv.response.' + validatorName, validateResult);
  815. this.updateMessage(updateAll ? field : $field, validatorName, validateResult.message);
  816. this.updateStatus(updateAll ? field : $field, validateResult.valid ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  817. if (!validateResult.valid && !verbose) {
  818. break;
  819. }
  820. }
  821. // ... or a boolean value
  822. else if ('boolean' === typeof validateResult) {
  823. $field.data('bv.response.' + validatorName, validateResult);
  824. this.updateStatus(updateAll ? field : $field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  825. if (!validateResult && !verbose) {
  826. break;
  827. }
  828. }
  829. }
  830. }
  831. return this;
  832. },
  833. /**
  834. * Update the error message
  835. *
  836. * @param {String|jQuery} field The field name or field element
  837. * @param {String} validator The validator name
  838. * @param {String} message The message
  839. * @returns {BootstrapValidator}
  840. */
  841. updateMessage: function(field, validator, message) {
  842. var $fields = $([]);
  843. switch (typeof field) {
  844. case 'object':
  845. $fields = field;
  846. field = field.attr('data-bv-field');
  847. break;
  848. case 'string':
  849. $fields = this.getFieldElements(field);
  850. break;
  851. default:
  852. break;
  853. }
  854. $fields.each(function() {
  855. $(this).data('bv.messages').find('.help-block[data-bv-validator="' + validator + '"][data-bv-for="' + field + '"]').html(message);
  856. });
  857. },
  858. /**
  859. * Update all validating results of field
  860. *
  861. * @param {String|jQuery} field The field name or field element
  862. * @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
  863. * @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators
  864. * @returns {BootstrapValidator}
  865. */
  866. updateStatus: function(field, status, validatorName) {
  867. var fields = $([]);
  868. switch (typeof field) {
  869. case 'object':
  870. fields = field;
  871. field = field.attr('data-bv-field');
  872. break;
  873. case 'string':
  874. fields = this.getFieldElements(field);
  875. break;
  876. default:
  877. break;
  878. }
  879. if (status === this.STATUS_NOT_VALIDATED) {
  880. // Reset the flag
  881. this._submitIfValid = false;
  882. }
  883. var that = this,
  884. type = fields.attr('type'),
  885. group = this.options.fields[field].group || this.options.group,
  886. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  887. for (var i = 0; i < total; i++) {
  888. var $field = fields.eq(i);
  889. if (this._isExcluded($field)) {
  890. continue;
  891. }
  892. var $parent = $field.parents(group),
  893. $message = $field.data('bv.messages'),
  894. $allErrors = $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]'),
  895. $errors = validatorName ? $allErrors.filter('[data-bv-validator="' + validatorName + '"]') : $allErrors,
  896. $icon = $parent.find('.form-control-feedback[data-bv-icon-for="' + field + '"]'),
  897. container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container),
  898. isValidField = null;
  899. // Update status
  900. if (validatorName) {
  901. $field.data('bv.result.' + validatorName, status);
  902. } else {
  903. for (var v in this.options.fields[field].validators) {
  904. $field.data('bv.result.' + v, status);
  905. }
  906. }
  907. // Show/hide error elements and feedback icons
  908. $errors.attr('data-bv-result', status);
  909. // Determine the tab containing the element
  910. var $tabPane = $field.parents('.tab-pane'),
  911. tabId, $tab;
  912. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  913. $tab = $('a[href="#' + tabId + '"][data-toggle="tab"]').parent();
  914. }
  915. switch (status) {
  916. case this.STATUS_VALIDATING:
  917. isValidField = null;
  918. this.disableSubmitButtons(true);
  919. $parent.removeClass('has-success').removeClass('has-error');
  920. if ($icon) {
  921. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).addClass(this.options.feedbackIcons.validating).show();
  922. }
  923. if ($tab) {
  924. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  925. }
  926. break;
  927. case this.STATUS_INVALID:
  928. isValidField = false;
  929. this.disableSubmitButtons(true);
  930. $parent.removeClass('has-success').addClass('has-error');
  931. if ($icon) {
  932. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.invalid).show();
  933. }
  934. if ($tab) {
  935. $tab.removeClass('bv-tab-success').addClass('bv-tab-error');
  936. }
  937. break;
  938. case this.STATUS_VALID:
  939. // If the field is valid (passes all validators)
  940. isValidField = ($allErrors.filter('[data-bv-result="' + this.STATUS_NOT_VALIDATED +'"]').length === 0)
  941. ? ($allErrors.filter('[data-bv-result="' + this.STATUS_VALID +'"]').length === $allErrors.length) // All validators are completed
  942. : null; // There are some validators that have not done
  943. if (isValidField !== null) {
  944. this.disableSubmitButtons(this.$submitButton ? !this.isValid() : !isValidField);
  945. if ($icon) {
  946. $icon
  947. .removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).removeClass(this.options.feedbackIcons.valid)
  948. .addClass(isValidField ? this.options.feedbackIcons.valid : this.options.feedbackIcons.invalid)
  949. .show();
  950. }
  951. }
  952. $parent.removeClass('has-error has-success').addClass(this.isValidContainer($parent) ? 'has-success' : 'has-error');
  953. if ($tab) {
  954. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error').addClass(this.isValidContainer($tabPane) ? 'bv-tab-success' : 'bv-tab-error');
  955. }
  956. break;
  957. case this.STATUS_NOT_VALIDATED:
  958. /* falls through */
  959. default:
  960. isValidField = null;
  961. this.disableSubmitButtons(false);
  962. $parent.removeClass('has-success').removeClass('has-error');
  963. if ($icon) {
  964. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).hide();
  965. }
  966. if ($tab) {
  967. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  968. }
  969. break;
  970. }
  971. switch (true) {
  972. // Only show the first error message if it is placed inside a tooltip ...
  973. case ($icon && 'tooltip' === container):
  974. (isValidField === false)
  975. ? $icon.css('cursor', 'pointer').tooltip('destroy').tooltip({
  976. container: 'body',
  977. html: true,
  978. placement: 'auto top',
  979. title: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html()
  980. })
  981. : $icon.css('cursor', '').tooltip('destroy');
  982. break;
  983. // ... or popover
  984. case ($icon && 'popover' === container):
  985. (isValidField === false)
  986. ? $icon.css('cursor', 'pointer').popover('destroy').popover({
  987. container: 'body',
  988. content: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html(),
  989. html: true,
  990. placement: 'auto top',
  991. trigger: 'hover click'
  992. })
  993. : $icon.css('cursor', '').popover('destroy');
  994. break;
  995. default:
  996. (status === this.STATUS_INVALID) ? $errors.show() : $errors.hide();
  997. break;
  998. }
  999. // Trigger an event
  1000. $field.trigger($.Event(this.options.events.fieldStatus), {
  1001. bv: this,
  1002. field: field,
  1003. element: $field,
  1004. status: status
  1005. });
  1006. this._onFieldValidated($field, validatorName);
  1007. }
  1008. return this;
  1009. },
  1010. /**
  1011. * Check the form validity
  1012. *
  1013. * @returns {Boolean}
  1014. */
  1015. isValid: function() {
  1016. for (var field in this.options.fields) {
  1017. if (!this.isValidField(field)) {
  1018. return false;
  1019. }
  1020. }
  1021. return true;
  1022. },
  1023. /**
  1024. * Check if the field is valid or not
  1025. *
  1026. * @param {String|jQuery} field The field name or field element
  1027. * @returns {Boolean}
  1028. */
  1029. isValidField: function(field) {
  1030. var fields = $([]);
  1031. switch (typeof field) {
  1032. case 'object':
  1033. fields = field;
  1034. field = field.attr('data-bv-field');
  1035. break;
  1036. case 'string':
  1037. fields = this.getFieldElements(field);
  1038. break;
  1039. default:
  1040. break;
  1041. }
  1042. if (fields.length === 0 || !this.options.fields[field] || this.options.fields[field].enabled === false) {
  1043. return true;
  1044. }
  1045. var type = fields.attr('type'),
  1046. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  1047. $field, validatorName, status;
  1048. for (var i = 0; i < total; i++) {
  1049. $field = fields.eq(i);
  1050. if (this._isExcluded($field)) {
  1051. continue;
  1052. }
  1053. for (validatorName in this.options.fields[field].validators) {
  1054. if (this.options.fields[field].validators[validatorName].enabled === false) {
  1055. continue;
  1056. }
  1057. status = $field.data('bv.result.' + validatorName);
  1058. if (status !== this.STATUS_VALID) {
  1059. return false;
  1060. }
  1061. }
  1062. }
  1063. return true;
  1064. },
  1065. /**
  1066. * Check if all fields inside a given container are valid.
  1067. * It's useful when working with a wizard-like such as tab, collapse
  1068. *
  1069. * @param {String|jQuery} container The container selector or element
  1070. * @returns {Boolean}
  1071. */
  1072. isValidContainer: function(container) {
  1073. var that = this,
  1074. map = {},
  1075. $container = ('string' === typeof container) ? $(container) : container;
  1076. if ($container.length === 0) {
  1077. return true;
  1078. }
  1079. $container.find('[data-bv-field]').each(function() {
  1080. var $field = $(this),
  1081. field = $field.attr('data-bv-field');
  1082. if (!that._isExcluded($field) && !map[field]) {
  1083. map[field] = $field;
  1084. }
  1085. });
  1086. for (var field in map) {
  1087. var $f = map[field];
  1088. if ($f.data('bv.messages')
  1089. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]')
  1090. .filter('[data-bv-result="' + this.STATUS_INVALID +'"]')
  1091. .length > 0)
  1092. {
  1093. return false;
  1094. }
  1095. }
  1096. return true;
  1097. },
  1098. /**
  1099. * Submit the form using default submission.
  1100. * It also does not perform any validations when submitting the form
  1101. */
  1102. defaultSubmit: function() {
  1103. if (this.$submitButton) {
  1104. // Create hidden input to send the submit buttons
  1105. $('<input/>')
  1106. .attr('type', 'hidden')
  1107. .attr('data-bv-submit-hidden', '')
  1108. .attr('name', this.$submitButton.attr('name'))
  1109. .val(this.$submitButton.val())
  1110. .appendTo(this.$form);
  1111. }
  1112. // Submit form
  1113. this.$form.off('submit.bv').submit();
  1114. },
  1115. // ---
  1116. // Useful APIs which aren't used internally
  1117. // ---
  1118. /**
  1119. * Get the list of invalid fields
  1120. *
  1121. * @returns {jQuery[]}
  1122. */
  1123. getInvalidFields: function() {
  1124. return this.$invalidFields;
  1125. },
  1126. /**
  1127. * Returns the clicked submit button
  1128. *
  1129. * @returns {jQuery}
  1130. */
  1131. getSubmitButton: function() {
  1132. return this.$submitButton;
  1133. },
  1134. /**
  1135. * Get the error messages
  1136. *
  1137. * @param {String|jQuery} [field] The field name or field element
  1138. * If the field is not defined, the method returns all error messages of all fields
  1139. * @param {String} [validator] The name of validator
  1140. * If the validator is not defined, the method returns error messages of all validators
  1141. * @returns {String[]}
  1142. */
  1143. getMessages: function(field, validator) {
  1144. var that = this,
  1145. messages = [],
  1146. $fields = $([]);
  1147. switch (true) {
  1148. case (field && 'object' === typeof field):
  1149. $fields = field;
  1150. break;
  1151. case (field && 'string' === typeof field):
  1152. var f = this.getFieldElements(field);
  1153. if (f.length > 0) {
  1154. var type = f.attr('type');
  1155. $fields = ('radio' === type || 'checkbox' === type) ? f.eq(0) : f;
  1156. }
  1157. break;
  1158. default:
  1159. $fields = this.$invalidFields;
  1160. break;
  1161. }
  1162. var filter = validator ? '[data-bv-validator="' + validator + '"]' : '';
  1163. $fields.each(function() {
  1164. messages = messages.concat(
  1165. $(this)
  1166. .data('bv.messages')
  1167. .find('.help-block[data-bv-for="' + $(this).attr('data-bv-field') + '"][data-bv-result="' + that.STATUS_INVALID + '"]' + filter)
  1168. .map(function() {
  1169. var v = $(this).attr('data-bv-validator'),
  1170. f = $(this).attr('data-bv-for');
  1171. return (that.options.fields[f].validators[v].enabled === false) ? '' : $(this).html();
  1172. })
  1173. .get()
  1174. );
  1175. });
  1176. return messages;
  1177. },
  1178. /**
  1179. * Update the option of a specific validator
  1180. *
  1181. * @param {String|jQuery} field The field name or field element
  1182. * @param {String} validator The validator name
  1183. * @param {String} option The option name
  1184. * @param {String} value The value to set
  1185. * @returns {BootstrapValidator}
  1186. */
  1187. updateOption: function(field, validator, option, value) {
  1188. if ('object' === typeof field) {
  1189. field = field.attr('data-bv-field');
  1190. }
  1191. if (this.options.fields[field] && this.options.fields[field].validators[validator]) {
  1192. this.options.fields[field].validators[validator][option] = value;
  1193. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validator);
  1194. }
  1195. return this;
  1196. },
  1197. /**
  1198. * Add a new field
  1199. *
  1200. * @param {String|jQuery} field The field name or field element
  1201. * @param {Object} [options] The validator rules
  1202. * @returns {BootstrapValidator}
  1203. */
  1204. addField: function(field, options) {
  1205. var fields = $([]);
  1206. switch (typeof field) {
  1207. case 'object':
  1208. fields = field;
  1209. field = field.attr('data-bv-field') || field.attr('name');
  1210. break;
  1211. case 'string':
  1212. delete this._cacheFields[field];
  1213. fields = this.getFieldElements(field);
  1214. break;
  1215. default:
  1216. break;
  1217. }
  1218. fields.attr('data-bv-field', field);
  1219. var type = fields.attr('type'),
  1220. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1221. for (var i = 0; i < total; i++) {
  1222. var $field = fields.eq(i);
  1223. // Try to parse the options from HTML attributes
  1224. var opts = this._parseOptions($field);
  1225. opts = (opts === null) ? options : $.extend(true, options, opts);
  1226. this.options.fields[field] = $.extend(true, this.options.fields[field], opts);
  1227. // Update the cache
  1228. this._cacheFields[field] = this._cacheFields[field] ? this._cacheFields[field].add($field) : $field;
  1229. // Init the element
  1230. this._initField(('checkbox' === type || 'radio' === type) ? field : $field);
  1231. }
  1232. this.disableSubmitButtons(false);
  1233. // Trigger an event
  1234. this.$form.trigger($.Event(this.options.events.fieldAdded), {
  1235. field: field,
  1236. element: fields,
  1237. options: this.options.fields[field]
  1238. });
  1239. return this;
  1240. },
  1241. /**
  1242. * Remove a given field
  1243. *
  1244. * @param {String|jQuery} field The field name or field element
  1245. * @returns {BootstrapValidator}
  1246. */
  1247. removeField: function(field) {
  1248. var fields = $([]);
  1249. switch (typeof field) {
  1250. case 'object':
  1251. fields = field;
  1252. field = field.attr('data-bv-field') || field.attr('name');
  1253. fields.attr('data-bv-field', field);
  1254. break;
  1255. case 'string':
  1256. fields = this.getFieldElements(field);
  1257. break;
  1258. default:
  1259. break;
  1260. }
  1261. if (fields.length === 0) {
  1262. return this;
  1263. }
  1264. var type = fields.attr('type'),
  1265. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1266. for (var i = 0; i < total; i++) {
  1267. var $field = fields.eq(i);
  1268. // Remove from the list of invalid fields
  1269. this.$invalidFields = this.$invalidFields.not($field);
  1270. // Update the cache
  1271. this._cacheFields[field] = this._cacheFields[field].not($field);
  1272. }
  1273. if (!this._cacheFields[field] || this._cacheFields[field].length === 0) {
  1274. delete this.options.fields[field];
  1275. }
  1276. if ('checkbox' === type || 'radio' === type) {
  1277. this._initField(field);
  1278. }
  1279. this.disableSubmitButtons(false);
  1280. // Trigger an event
  1281. this.$form.trigger($.Event(this.options.events.fieldRemoved), {
  1282. field: field,
  1283. element: fields
  1284. });
  1285. return this;
  1286. },
  1287. /**
  1288. * Reset given field
  1289. *
  1290. * @param {String|jQuery} field The field name or field element
  1291. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1292. * @returns {BootstrapValidator}
  1293. */
  1294. resetField: function(field, resetValue) {
  1295. var $fields = $([]);
  1296. switch (typeof field) {
  1297. case 'object':
  1298. $fields = field;
  1299. field = field.attr('data-bv-field');
  1300. break;
  1301. case 'string':
  1302. $fields = this.getFieldElements(field);
  1303. break;
  1304. default:
  1305. break;
  1306. }
  1307. var total = $fields.length;
  1308. if (this.options.fields[field]) {
  1309. for (var i = 0; i < total; i++) {
  1310. for (var validator in this.options.fields[field].validators) {
  1311. $fields.eq(i).removeData('bv.dfs.' + validator);
  1312. }
  1313. }
  1314. }
  1315. // Mark field as not validated yet
  1316. this.updateStatus(field, this.STATUS_NOT_VALIDATED);
  1317. if (resetValue) {
  1318. var type = $fields.attr('type');
  1319. ('radio' === type || 'checkbox' === type) ? $fields.removeAttr('checked').removeAttr('selected') : $fields.val('');
  1320. }
  1321. return this;
  1322. },
  1323. /**
  1324. * Reset the form
  1325. *
  1326. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1327. * @returns {BootstrapValidator}
  1328. */
  1329. resetForm: function(resetValue) {
  1330. for (var field in this.options.fields) {
  1331. this.resetField(field, resetValue);
  1332. }
  1333. this.$invalidFields = $([]);
  1334. this.$submitButton = null;
  1335. // Enable submit buttons
  1336. this.disableSubmitButtons(false);
  1337. return this;
  1338. },
  1339. /**
  1340. * Revalidate given field
  1341. * It's used when you need to revalidate the field which its value is updated by other plugin
  1342. *
  1343. * @param {String|jQuery} field The field name of field element
  1344. * @returns {BootstrapValidator}
  1345. */
  1346. revalidateField: function(field) {
  1347. this.updateStatus(field, this.STATUS_NOT_VALIDATED)
  1348. .validateField(field);
  1349. return this;
  1350. },
  1351. /**
  1352. * Enable/Disable all validators to given field
  1353. *
  1354. * @param {String} field The field name
  1355. * @param {Boolean} enabled Enable/Disable field validators
  1356. * @param {String} [validatorName] The validator name. If null, all validators will be enabled/disabled
  1357. * @returns {BootstrapValidator}
  1358. */
  1359. enableFieldValidators: function(field, enabled, validatorName) {
  1360. var validators = this.options.fields[field].validators;
  1361. // Enable/disable particular validator
  1362. if (validatorName
  1363. && validators
  1364. && validators[validatorName] && validators[validatorName].enabled !== enabled)
  1365. {
  1366. this.options.fields[field].validators[validatorName].enabled = enabled;
  1367. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validatorName);
  1368. }
  1369. // Enable/disable all validators
  1370. else if (!validatorName && this.options.fields[field].enabled !== enabled) {
  1371. this.options.fields[field].enabled = enabled;
  1372. for (var v in validators) {
  1373. this.enableFieldValidators(field, enabled, v);
  1374. }
  1375. }
  1376. return this;
  1377. },
  1378. /**
  1379. * Some validators have option which its value is dynamic.
  1380. * For example, the zipCode validator has the country option which might be changed dynamically by a select element.
  1381. *
  1382. * @param {jQuery|String} field The field name or element
  1383. * @param {String|Function} option The option which can be determined by:
  1384. * - a string
  1385. * - name of field which defines the value
  1386. * - name of function which returns the value
  1387. * - a function returns the value
  1388. *
  1389. * The callback function has the format of
  1390. * callback: function(value, validator, $field) {
  1391. * // value is the value of field
  1392. * // validator is the BootstrapValidator instance
  1393. * // $field is the field element
  1394. * }
  1395. *
  1396. * @returns {String}
  1397. */
  1398. getDynamicOption: function(field, option) {
  1399. var $field = ('string' === typeof field) ? this.getFieldElements(field) : field,
  1400. value = $field.val();
  1401. // Option can be determined by
  1402. // ... a function
  1403. if ('function' === typeof option) {
  1404. return $.fn.bootstrapValidator.helpers.call(option, [value, this, $field]);
  1405. }
  1406. // ... value of other field
  1407. else if ('string' === typeof option) {
  1408. var $f = this.getFieldElements(option);
  1409. if ($f.length) {
  1410. return $f.val();
  1411. }
  1412. // ... return value of callback
  1413. else {
  1414. return $.fn.bootstrapValidator.helpers.call(option, [value, this, $field]) || option;
  1415. }
  1416. }
  1417. return null;
  1418. },
  1419. /**
  1420. * Destroy the plugin
  1421. * It will remove all error messages, feedback icons and turn off the events
  1422. */
  1423. destroy: function() {
  1424. var field, fields, $field, validator, $icon, group;
  1425. for (field in this.options.fields) {
  1426. fields = this.getFieldElements(field);
  1427. group = this.options.fields[field].group || this.options.group;
  1428. for (var i = 0; i < fields.length; i++) {
  1429. $field = fields.eq(i);
  1430. $field
  1431. // Remove all error messages
  1432. .data('bv.messages')
  1433. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]').remove().end()
  1434. .end()
  1435. .removeData('bv.messages')
  1436. // Remove feedback classes
  1437. .parents(group)
  1438. .removeClass('has-feedback has-error has-success')
  1439. .end()
  1440. // Turn off events
  1441. .off('.bv')
  1442. .removeAttr('data-bv-field');
  1443. // Remove feedback icons, tooltip/popover container
  1444. $icon = $field.parents(group).find('i[data-bv-icon-for="' + field + '"]');
  1445. if ($icon) {
  1446. var container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container);
  1447. switch (container) {
  1448. case 'tooltip':
  1449. $icon.tooltip('destroy').remove();
  1450. break;
  1451. case 'popover':
  1452. $icon.popover('destroy').remove();
  1453. break;
  1454. default:
  1455. $icon.remove();
  1456. break;
  1457. }
  1458. }
  1459. for (validator in this.options.fields[field].validators) {
  1460. if ($field.data('bv.dfs.' + validator)) {
  1461. $field.data('bv.dfs.' + validator).reject();
  1462. }
  1463. $field.removeData('bv.result.' + validator)
  1464. .removeData('bv.response.' + validator)
  1465. .removeData('bv.dfs.' + validator);
  1466. // Destroy the validator
  1467. if ('function' === typeof $.fn.bootstrapValidator.validators[validator].destroy) {
  1468. $.fn.bootstrapValidator.validators[validator].destroy(this, $field, this.options.fields[field].validators[validator]);
  1469. }
  1470. }
  1471. }
  1472. }
  1473. this.disableSubmitButtons(false); // Enable submit buttons
  1474. this.$hiddenButton.remove(); // Remove the hidden button
  1475. this.$form
  1476. .removeClass(this.options.elementClass)
  1477. .off('.bv')
  1478. .removeData('bootstrapValidator')
  1479. // Remove generated hidden elements
  1480. .find('[data-bv-submit-hidden]').remove().end()
  1481. .find('[type="submit"]').off('click.bv');
  1482. }
  1483. };
  1484. // Plugin definition
  1485. $.fn.bootstrapValidator = function(option) {
  1486. var params = arguments;
  1487. return this.each(function() {
  1488. var $this = $(this),
  1489. data = $this.data('bootstrapValidator'),
  1490. options = 'object' === typeof option && option;
  1491. if (!data) {
  1492. data = new BootstrapValidator(this, options);
  1493. $this.data('bootstrapValidator', data);
  1494. }
  1495. // Allow to call plugin method
  1496. if ('string' === typeof option) {
  1497. data[option].apply(data, Array.prototype.slice.call(params, 1));
  1498. }
  1499. });
  1500. };
  1501. // The default options
  1502. $.fn.bootstrapValidator.DEFAULT_OPTIONS = {
  1503. // The form CSS class
  1504. elementClass: 'bv-form',
  1505. // Default invalid message
  1506. message: 'This value is not valid',
  1507. // The CSS selector for indicating the element consists the field
  1508. // By default, each field is placed inside the <div class="form-group"></div>
  1509. // You should adjust this option if your form group consists of many fields which not all of them need to be validated
  1510. group: '.form-group',
  1511. //The error messages container. It can be:
  1512. // - 'tooltip' if you want to use Bootstrap tooltip to show error messages
  1513. // - 'popover' if you want to use Bootstrap popover to show error messages
  1514. // - a CSS selector indicating the container
  1515. // In the first two cases, since the tooltip/popover should be small enough, the plugin only shows only one error message
  1516. // You also can define the message container for particular field
  1517. container: null,
  1518. // The field will not be live validated if its length is less than this number of characters
  1519. threshold: null,
  1520. // Indicate fields which won't be validated
  1521. // By default, the plugin will not validate the following kind of fields:
  1522. // - disabled
  1523. // - hidden
  1524. // - invisible
  1525. //
  1526. // The setting consists of jQuery filters. Accept 3 formats:
  1527. // - A string. Use a comma to separate filter
  1528. // - An array. Each element is a filter
  1529. // - An array. Each element can be a callback function
  1530. // function($field, validator) {
  1531. // $field is jQuery object representing the field element
  1532. // validator is the BootstrapValidator instance
  1533. // return true or false;
  1534. // }
  1535. //
  1536. // The 3 following settings are equivalent:
  1537. //
  1538. // 1) ':disabled, :hidden, :not(:visible)'
  1539. // 2) [':disabled', ':hidden', ':not(:visible)']
  1540. // 3) [':disabled', ':hidden', function($field) {
  1541. // return !$field.is(':visible');
  1542. // }]
  1543. excluded: [':disabled', ':hidden', ':not(:visible)'],
  1544. // Shows ok/error/loading icons based on the field validity.
  1545. // This feature requires Bootstrap v3.1.0 or later (http://getbootstrap.com/css/#forms-control-validation).
  1546. // Since Bootstrap doesn't provide any methods to know its version, this option cannot be on/off automatically.
  1547. // In other word, to use this feature you have to upgrade your Bootstrap to v3.1.0 or later.
  1548. //
  1549. // Examples:
  1550. // - Use Glyphicons icons:
  1551. // feedbackIcons: {
  1552. // valid: 'glyphicon glyphicon-ok',
  1553. // invalid: 'glyphicon glyphicon-remove',
  1554. // validating: 'glyphicon glyphicon-refresh'
  1555. // }
  1556. // - Use FontAwesome icons:
  1557. // feedbackIcons: {
  1558. // valid: 'fa fa-check',
  1559. // invalid: 'fa fa-times',
  1560. // validating: 'fa fa-refresh'
  1561. // }
  1562. feedbackIcons: {
  1563. valid: null,
  1564. invalid: null,
  1565. validating: null
  1566. },
  1567. // The submit buttons selector
  1568. // These buttons will be disabled to prevent the valid form from multiple submissions
  1569. submitButtons: '[type="submit"]',
  1570. // Live validating option
  1571. // Can be one of 3 values:
  1572. // - enabled: The plugin validates fields as soon as they are changed
  1573. // - disabled: Disable the live validating. The error messages are only shown after the form is submitted
  1574. // - submitted: The live validating is enabled after the form is submitted
  1575. live: 'enabled',
  1576. // Map the field name with validator rules
  1577. fields: null,
  1578. // Use custom event name to avoid window.onerror being invoked by jQuery
  1579. // See https://github.com/nghuuphuoc/bootstrapvalidator/issues/630
  1580. events: {
  1581. formInit: 'init.form.bv',
  1582. formError: 'error.form.bv',
  1583. formSuccess: 'success.form.bv',
  1584. fieldAdded: 'added.field.bv',
  1585. fieldRemoved: 'removed.field.bv',
  1586. fieldInit: 'init.field.bv',
  1587. fieldError: 'error.field.bv',
  1588. fieldSuccess: 'success.field.bv',
  1589. fieldStatus: 'status.field.bv',
  1590. validatorError: 'error.validator.bv',
  1591. validatorSuccess: 'success.validator.bv'
  1592. },
  1593. // Whether to be verbose when validating a field or not.
  1594. // Possible values:
  1595. // - true: when a field has multiple validators, all of them will be checked, and respectively - if errors occur in
  1596. // multiple validators, all of them will be displayed to the user
  1597. // - false: when a field has multiple validators, validation for this field will be terminated upon the first encountered error.
  1598. // Thus, only the very first error message related to this field will be displayed to the user
  1599. verbose: true
  1600. };
  1601. // Available validators
  1602. $.fn.bootstrapValidator.validators = {};
  1603. // i18n
  1604. $.fn.bootstrapValidator.i18n = {};
  1605. $.fn.bootstrapValidator.Constructor = BootstrapValidator;
  1606. // Helper methods, which can be used in validator class
  1607. $.fn.bootstrapValidator.helpers = {
  1608. /**
  1609. * Execute a callback function
  1610. *
  1611. * @param {String|Function} functionName Can be
  1612. * - name of global function
  1613. * - name of namespace function (such as A.B.C)
  1614. * - a function
  1615. * @param {Array} args The callback arguments
  1616. */
  1617. call: function(functionName, args) {
  1618. if ('function' === typeof functionName) {
  1619. return functionName.apply(this, args);
  1620. } else if ('string' === typeof functionName) {
  1621. if ('()' === functionName.substring(functionName.length - 2)) {
  1622. functionName = functionName.substring(0, functionName.length - 2);
  1623. }
  1624. var ns = functionName.split('.'),
  1625. func = ns.pop(),
  1626. context = window;
  1627. for (var i = 0; i < ns.length; i++) {
  1628. context = context[ns[i]];
  1629. }
  1630. return (typeof context[func] === 'undefined') ? null : context[func].apply(this, args);
  1631. }
  1632. },
  1633. /**
  1634. * Format a string
  1635. * It's used to format the error message
  1636. * format('The field must between %s and %s', [10, 20]) = 'The field must between 10 and 20'
  1637. *
  1638. * @param {String} message
  1639. * @param {Array} parameters
  1640. * @returns {String}
  1641. */
  1642. format: function(message, parameters) {
  1643. if (!$.isArray(parameters)) {
  1644. parameters = [parameters];
  1645. }
  1646. for (var i in parameters) {
  1647. message = message.replace('%s', parameters[i]);
  1648. }
  1649. return message;
  1650. },
  1651. /**
  1652. * Validate a date
  1653. *
  1654. * @param {Number} year The full year in 4 digits
  1655. * @param {Number} month The month number
  1656. * @param {Number} day The day number
  1657. * @param {Boolean} [notInFuture] If true, the date must not be in the future
  1658. * @returns {Boolean}
  1659. */
  1660. date: function(year, month, day, notInFuture) {
  1661. if (isNaN(year) || isNaN(month) || isNaN(day)) {
  1662. return false;
  1663. }
  1664. if (day.length > 2 || month.length > 2 || year.length > 4) {
  1665. return false;
  1666. }
  1667. day = parseInt(day, 10);
  1668. month = parseInt(month, 10);
  1669. year = parseInt(year, 10);
  1670. if (year < 1000 || year > 9999 || month <= 0 || month > 12) {
  1671. return false;
  1672. }
  1673. var numDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  1674. // Update the number of days in Feb of leap year
  1675. if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
  1676. numDays[1] = 29;
  1677. }
  1678. // Check the day
  1679. if (day <= 0 || day > numDays[month - 1]) {
  1680. return false;
  1681. }
  1682. if (notInFuture === true) {
  1683. var currentDate = new Date(),
  1684. currentYear = currentDate.getFullYear(),
  1685. currentMonth = currentDate.getMonth(),
  1686. currentDay = currentDate.getDate();
  1687. return (year < currentYear
  1688. || (year === currentYear && month - 1 < currentMonth)
  1689. || (year === currentYear && month - 1 === currentMonth && day < currentDay));
  1690. }
  1691. return true;
  1692. },
  1693. /**
  1694. * Implement Luhn validation algorithm
  1695. * Credit to https://gist.github.com/ShirtlessKirk/2134376
  1696. *
  1697. * @see http://en.wikipedia.org/wiki/Luhn
  1698. * @param {String} value
  1699. * @returns {Boolean}
  1700. */
  1701. luhn: function(value) {
  1702. var length = value.length,
  1703. mul = 0,
  1704. prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
  1705. sum = 0;
  1706. while (length--) {
  1707. sum += prodArr[mul][parseInt(value.charAt(length), 10)];
  1708. mul ^= 1;
  1709. }
  1710. return (sum % 10 === 0 && sum > 0);
  1711. },
  1712. /**
  1713. * Implement modulus 11, 10 (ISO 7064) algorithm
  1714. *
  1715. * @param {String} value
  1716. * @returns {Boolean}
  1717. */
  1718. mod11And10: function(value) {
  1719. var check = 5,
  1720. length = value.length;
  1721. for (var i = 0; i < length; i++) {
  1722. check = (((check || 10) * 2) % 11 + parseInt(value.charAt(i), 10)) % 10;
  1723. }
  1724. return (check === 1);
  1725. },
  1726. /**
  1727. * Implements Mod 37, 36 (ISO 7064) algorithm
  1728. * Usages:
  1729. * mod37And36('A12425GABC1234002M')
  1730. * mod37And36('002006673085', '0123456789')
  1731. *
  1732. * @param {String} value
  1733. * @param {String} [alphabet]
  1734. * @returns {Boolean}
  1735. */
  1736. mod37And36: function(value, alphabet) {
  1737. alphabet = alphabet || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  1738. var modulus = alphabet.length,
  1739. length = value.length,
  1740. check = Math.floor(modulus / 2);
  1741. for (var i = 0; i < length; i++) {
  1742. check = (((check || modulus) * 2) % (modulus + 1) + alphabet.indexOf(value.charAt(i))) % modulus;
  1743. }
  1744. return (check === 1);
  1745. }
  1746. };
  1747. }(window.jQuery));