bootstrapValidator.js 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956
  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() {
  410. if (that._exceedThreshold($(this))) {
  411. that.validateField($(this));
  412. }
  413. });
  414. break;
  415. }
  416. fields.trigger($.Event(this.options.events.fieldInit), {
  417. bv: this,
  418. field: field,
  419. element: fields
  420. });
  421. },
  422. /**
  423. * Get the error message for given field and validator
  424. *
  425. * @param {String} field The field name
  426. * @param {String} validatorName The validator name
  427. * @returns {String}
  428. */
  429. _getMessage: function(field, validatorName) {
  430. if (!this.options.fields[field] || !$.fn.bootstrapValidator.validators[validatorName]
  431. || !this.options.fields[field].validators || !this.options.fields[field].validators[validatorName])
  432. {
  433. return '';
  434. }
  435. var options = this.options.fields[field].validators[validatorName];
  436. switch (true) {
  437. case (!!options.message):
  438. return options.message;
  439. case (!!this.options.fields[field].message):
  440. return this.options.fields[field].message;
  441. case (!!$.fn.bootstrapValidator.i18n[validatorName]):
  442. return $.fn.bootstrapValidator.i18n[validatorName]['default'];
  443. default:
  444. return this.options.message;
  445. }
  446. },
  447. /**
  448. * Get the element to place the error messages
  449. *
  450. * @param {jQuery} $field The field element
  451. * @param {String} group
  452. * @returns {jQuery}
  453. */
  454. _getMessageContainer: function($field, group) {
  455. var $parent = $field.parent();
  456. if ($parent.is(group)) {
  457. return $parent;
  458. }
  459. var cssClasses = $parent.attr('class');
  460. if (!cssClasses) {
  461. return this._getMessageContainer($parent, group);
  462. }
  463. cssClasses = cssClasses.split(' ');
  464. var n = cssClasses.length;
  465. for (var i = 0; i < n; i++) {
  466. if (/^col-(xs|sm|md|lg)-\d+$/.test(cssClasses[i]) || /^col-(xs|sm|md|lg)-offset-\d+$/.test(cssClasses[i])) {
  467. return $parent;
  468. }
  469. }
  470. return this._getMessageContainer($parent, group);
  471. },
  472. /**
  473. * Called when all validations are completed
  474. */
  475. _submit: function() {
  476. var isValid = this.isValid(),
  477. eventType = isValid ? this.options.events.formSuccess : this.options.events.formError,
  478. e = $.Event(eventType);
  479. this.$form.trigger(e);
  480. // Call default handler
  481. // Check if whether the submit button is clicked
  482. if (this.$submitButton) {
  483. isValid ? this._onSuccess(e) : this._onError(e);
  484. }
  485. },
  486. /**
  487. * Check if the field is excluded.
  488. * Returning true means that the field will not be validated
  489. *
  490. * @param {jQuery} $field The field element
  491. * @returns {Boolean}
  492. */
  493. _isExcluded: function($field) {
  494. var excludedAttr = $field.attr('data-bv-excluded'),
  495. // I still need to check the 'name' attribute while initializing the field
  496. field = $field.attr('data-bv-field') || $field.attr('name');
  497. switch (true) {
  498. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'true' || this.options.fields[field].excluded === true)):
  499. case (excludedAttr === 'true'):
  500. case (excludedAttr === ''):
  501. return true;
  502. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'false' || this.options.fields[field].excluded === false)):
  503. case (excludedAttr === 'false'):
  504. return false;
  505. default:
  506. if (this.options.excluded) {
  507. // Convert to array first
  508. if ('string' === typeof this.options.excluded) {
  509. this.options.excluded = $.map(this.options.excluded.split(','), function(item) {
  510. // Trim the spaces
  511. return $.trim(item);
  512. });
  513. }
  514. var length = this.options.excluded.length;
  515. for (var i = 0; i < length; i++) {
  516. if (('string' === typeof this.options.excluded[i] && $field.is(this.options.excluded[i]))
  517. || ('function' === typeof this.options.excluded[i] && this.options.excluded[i].call(this, $field, this) === true))
  518. {
  519. return true;
  520. }
  521. }
  522. }
  523. return false;
  524. }
  525. },
  526. /**
  527. * Check if the number of characters of field value exceed the threshold or not
  528. *
  529. * @param {jQuery} $field The field element
  530. * @returns {Boolean}
  531. */
  532. _exceedThreshold: function($field) {
  533. var field = $field.attr('data-bv-field'),
  534. threshold = this.options.fields[field].threshold || this.options.threshold;
  535. if (!threshold) {
  536. return true;
  537. }
  538. var cannotType = $.inArray($field.attr('type'), ['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit']) !== -1;
  539. return (cannotType || $field.val().length >= threshold);
  540. },
  541. // ---
  542. // Events
  543. // ---
  544. /**
  545. * The default handler of error.form.bv event.
  546. * It will be called when there is a invalid field
  547. *
  548. * @param {jQuery.Event} e The jQuery event object
  549. */
  550. _onError: function(e) {
  551. if (e.isDefaultPrevented()) {
  552. return;
  553. }
  554. if ('submitted' === this.options.live) {
  555. // Enable live mode
  556. this.options.live = 'enabled';
  557. var that = this;
  558. for (var field in this.options.fields) {
  559. (function(f) {
  560. var fields = that.getFieldElements(f);
  561. if (fields.length) {
  562. var type = $(fields[0]).attr('type'),
  563. event = ('radio' === type || 'checkbox' === type || 'file' === type || 'SELECT' === $(fields[0]).get(0).tagName) ? 'change' : that._changeEvent,
  564. trigger = that.options.fields[field].trigger || that.options.trigger || event,
  565. events = $.map(trigger.split(' '), function(item) {
  566. return item + '.live.bv';
  567. }).join(' ');
  568. fields.off(events).on(events, function() {
  569. if (that._exceedThreshold($(this))) {
  570. that.validateField($(this));
  571. }
  572. });
  573. }
  574. })(field);
  575. }
  576. }
  577. var $invalidField = this.$invalidFields.eq(0);
  578. if ($invalidField) {
  579. // Activate the tab containing the invalid field if exists
  580. var $tabPane = $invalidField.parents('.tab-pane'), tabId;
  581. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  582. $('a[href="#' + tabId + '"][data-toggle="tab"]').tab('show');
  583. }
  584. // Focus to the first invalid field
  585. $invalidField.focus();
  586. }
  587. },
  588. /**
  589. * The default handler of success.form.bv event.
  590. * It will be called when all the fields are valid
  591. *
  592. * @param {jQuery.Event} e The jQuery event object
  593. */
  594. _onSuccess: function(e) {
  595. if (e.isDefaultPrevented()) {
  596. return;
  597. }
  598. // Submit the form
  599. this.disableSubmitButtons(true).defaultSubmit();
  600. },
  601. /**
  602. * Called after validating a field element
  603. *
  604. * @param {jQuery} $field The field element
  605. * @param {String} [validatorName] The validator name
  606. */
  607. _onFieldValidated: function($field, validatorName) {
  608. var field = $field.attr('data-bv-field'),
  609. validators = this.options.fields[field].validators,
  610. counter = {},
  611. numValidators = 0,
  612. data = {
  613. bv: this,
  614. field: field,
  615. element: $field,
  616. validator: validatorName,
  617. result: $field.data('bv.response.' + validatorName)
  618. };
  619. // Trigger an event after given validator completes
  620. if (validatorName) {
  621. switch ($field.data('bv.result.' + validatorName)) {
  622. case this.STATUS_INVALID:
  623. $field.trigger($.Event(this.options.events.validatorError), data);
  624. break;
  625. case this.STATUS_VALID:
  626. $field.trigger($.Event(this.options.events.validatorSuccess), data);
  627. break;
  628. default:
  629. break;
  630. }
  631. }
  632. counter[this.STATUS_NOT_VALIDATED] = 0;
  633. counter[this.STATUS_VALIDATING] = 0;
  634. counter[this.STATUS_INVALID] = 0;
  635. counter[this.STATUS_VALID] = 0;
  636. for (var v in validators) {
  637. if (validators[v].enabled === false) {
  638. continue;
  639. }
  640. numValidators++;
  641. var result = $field.data('bv.result.' + v);
  642. if (result) {
  643. counter[result]++;
  644. }
  645. }
  646. if (counter[this.STATUS_VALID] === numValidators) {
  647. // Remove from the list of invalid fields
  648. this.$invalidFields = this.$invalidFields.not($field);
  649. $field.trigger($.Event(this.options.events.fieldSuccess), data);
  650. }
  651. // If all validators are completed and there is at least one validator which doesn't pass
  652. else if (counter[this.STATUS_NOT_VALIDATED] === 0 && counter[this.STATUS_VALIDATING] === 0 && counter[this.STATUS_INVALID] > 0) {
  653. // Add to the list of invalid fields
  654. this.$invalidFields = this.$invalidFields.add($field);
  655. $field.trigger($.Event(this.options.events.fieldError), data);
  656. }
  657. },
  658. // ---
  659. // Public methods
  660. // ---
  661. /**
  662. * Retrieve the field elements by given name
  663. *
  664. * @param {String} field The field name
  665. * @returns {null|jQuery[]}
  666. */
  667. getFieldElements: function(field) {
  668. if (!this._cacheFields[field]) {
  669. this._cacheFields[field] = (this.options.fields[field] && this.options.fields[field].selector)
  670. ? $(this.options.fields[field].selector)
  671. : this.$form.find('[name="' + field + '"]');
  672. }
  673. return this._cacheFields[field];
  674. },
  675. /**
  676. * Get the field options
  677. *
  678. * @param {String|jQuery} [field] The field name or field element. If it is not set, the method returns the form options
  679. * @param {String} [validator] The name of validator. It null, the method returns form options
  680. * @param {String} [option] The option name
  681. * @return {String|Object}
  682. */
  683. getOptions: function(field, validator, option) {
  684. if (!field) {
  685. return this.options;
  686. }
  687. if ('object' === typeof field) {
  688. field = field.attr('data-bv-field');
  689. }
  690. if (!this.options.fields[field]) {
  691. return null;
  692. }
  693. var options = this.options.fields[field];
  694. if (!validator) {
  695. return option ? options[option] : options;
  696. }
  697. if (!options.validators || !options.validators[validator]) {
  698. return null;
  699. }
  700. return option ? options.validators[validator][option] : options.validators[validator];
  701. },
  702. /**
  703. * Disable/enable submit buttons
  704. *
  705. * @param {Boolean} disabled Can be true or false
  706. * @returns {BootstrapValidator}
  707. */
  708. disableSubmitButtons: function(disabled) {
  709. if (!disabled) {
  710. this.$form.find(this.options.submitButtons).removeAttr('disabled');
  711. } else if (this.options.live !== 'disabled') {
  712. // Don't disable if the live validating mode is disabled
  713. this.$form.find(this.options.submitButtons).attr('disabled', 'disabled');
  714. }
  715. return this;
  716. },
  717. /**
  718. * Validate the form
  719. *
  720. * @returns {BootstrapValidator}
  721. */
  722. validate: function() {
  723. if (!this.options.fields) {
  724. return this;
  725. }
  726. this.disableSubmitButtons(true);
  727. for (var field in this.options.fields) {
  728. this.validateField(field);
  729. }
  730. this._submit();
  731. return this;
  732. },
  733. /**
  734. * Validate given field
  735. *
  736. * @param {String|jQuery} field The field name or field element
  737. * @returns {BootstrapValidator}
  738. */
  739. validateField: function(field) {
  740. var fields = $([]);
  741. switch (typeof field) {
  742. case 'object':
  743. fields = field;
  744. field = field.attr('data-bv-field');
  745. break;
  746. case 'string':
  747. fields = this.getFieldElements(field);
  748. break;
  749. default:
  750. break;
  751. }
  752. if (fields.length === 0 || (this.options.fields[field] && this.options.fields[field].enabled === false)) {
  753. return this;
  754. }
  755. var that = this,
  756. type = fields.attr('type'),
  757. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  758. updateAll = ('radio' === type || 'checkbox' === type),
  759. validators = this.options.fields[field].validators,
  760. verbose = this.options.fields[field].verbose === 'true' || this.options.fields[field].verbose === true || this.options.verbose === 'true' || this.options.verbose === true,
  761. validatorName,
  762. validateResult;
  763. for (var i = 0; i < total; i++) {
  764. var $field = fields.eq(i);
  765. if (this._isExcluded($field)) {
  766. continue;
  767. }
  768. var stop = false;
  769. for (validatorName in validators) {
  770. if ($field.data('bv.dfs.' + validatorName)) {
  771. $field.data('bv.dfs.' + validatorName).reject();
  772. }
  773. if (stop) {
  774. break;
  775. }
  776. // Don't validate field if it is already done
  777. var result = $field.data('bv.result.' + validatorName);
  778. if (result === this.STATUS_VALID || result === this.STATUS_INVALID) {
  779. this._onFieldValidated($field, validatorName);
  780. continue;
  781. } else if (validators[validatorName].enabled === false) {
  782. this.updateStatus(updateAll ? field : $field, this.STATUS_VALID, validatorName);
  783. continue;
  784. }
  785. $field.data('bv.result.' + validatorName, this.STATUS_VALIDATING);
  786. validateResult = $.fn.bootstrapValidator.validators[validatorName].validate(this, $field, validators[validatorName]);
  787. // validateResult can be a $.Deferred object ...
  788. if ('object' === typeof validateResult && validateResult.resolve) {
  789. this.updateStatus(updateAll ? field : $field, this.STATUS_VALIDATING, validatorName);
  790. $field.data('bv.dfs.' + validatorName, validateResult);
  791. validateResult.done(function($f, v, response) {
  792. // v is validator name
  793. $f.removeData('bv.dfs.' + v).data('bv.response.' + v, response);
  794. if (response.message) {
  795. that.updateMessage($f, v, response.message);
  796. }
  797. that.updateStatus(updateAll ? $f.attr('data-bv-field') : $f, response.valid ? that.STATUS_VALID : that.STATUS_INVALID, v);
  798. if (response.valid && that._submitIfValid === true) {
  799. // If a remote validator returns true and the form is ready to submit, then do it
  800. that._submit();
  801. } else if (!response.valid && !verbose) {
  802. stop = true;
  803. }
  804. });
  805. }
  806. // ... or object { valid: true/false, message: 'dynamic message' }
  807. else if ('object' === typeof validateResult && validateResult.valid !== undefined && validateResult.message !== undefined) {
  808. $field.data('bv.response.' + validatorName, validateResult);
  809. this.updateMessage(updateAll ? field : $field, validatorName, validateResult.message);
  810. this.updateStatus(updateAll ? field : $field, validateResult.valid ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  811. if (!validateResult.valid && !verbose) {
  812. break;
  813. }
  814. }
  815. // ... or a boolean value
  816. else if ('boolean' === typeof validateResult) {
  817. $field.data('bv.response.' + validatorName, validateResult);
  818. this.updateStatus(updateAll ? field : $field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  819. if (!validateResult && !verbose) {
  820. break;
  821. }
  822. }
  823. }
  824. }
  825. return this;
  826. },
  827. /**
  828. * Update the error message
  829. *
  830. * @param {String|jQuery} field The field name or field element
  831. * @param {String} validator The validator name
  832. * @param {String} message The message
  833. * @returns {BootstrapValidator}
  834. */
  835. updateMessage: function(field, validator, message) {
  836. var $fields = $([]);
  837. switch (typeof field) {
  838. case 'object':
  839. $fields = field;
  840. field = field.attr('data-bv-field');
  841. break;
  842. case 'string':
  843. $fields = this.getFieldElements(field);
  844. break;
  845. default:
  846. break;
  847. }
  848. $fields.each(function() {
  849. $(this).data('bv.messages').find('.help-block[data-bv-validator="' + validator + '"][data-bv-for="' + field + '"]').html(message);
  850. });
  851. },
  852. /**
  853. * Update all validating results of field
  854. *
  855. * @param {String|jQuery} field The field name or field element
  856. * @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
  857. * @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators
  858. * @returns {BootstrapValidator}
  859. */
  860. updateStatus: function(field, status, validatorName) {
  861. var fields = $([]);
  862. switch (typeof field) {
  863. case 'object':
  864. fields = field;
  865. field = field.attr('data-bv-field');
  866. break;
  867. case 'string':
  868. fields = this.getFieldElements(field);
  869. break;
  870. default:
  871. break;
  872. }
  873. if (status === this.STATUS_NOT_VALIDATED) {
  874. // Reset the flag
  875. this._submitIfValid = false;
  876. }
  877. var that = this,
  878. type = fields.attr('type'),
  879. group = this.options.fields[field].group || this.options.group,
  880. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  881. for (var i = 0; i < total; i++) {
  882. var $field = fields.eq(i);
  883. if (this._isExcluded($field)) {
  884. continue;
  885. }
  886. var $parent = $field.parents(group),
  887. $message = $field.data('bv.messages'),
  888. $allErrors = $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]'),
  889. $errors = validatorName ? $allErrors.filter('[data-bv-validator="' + validatorName + '"]') : $allErrors,
  890. $icon = $parent.find('.form-control-feedback[data-bv-icon-for="' + field + '"]'),
  891. 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),
  892. isValidField = null;
  893. // Update status
  894. if (validatorName) {
  895. $field.data('bv.result.' + validatorName, status);
  896. } else {
  897. for (var v in this.options.fields[field].validators) {
  898. $field.data('bv.result.' + v, status);
  899. }
  900. }
  901. // Show/hide error elements and feedback icons
  902. $errors.attr('data-bv-result', status);
  903. // Determine the tab containing the element
  904. var $tabPane = $field.parents('.tab-pane'),
  905. tabId, $tab;
  906. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  907. $tab = $('a[href="#' + tabId + '"][data-toggle="tab"]').parent();
  908. }
  909. switch (status) {
  910. case this.STATUS_VALIDATING:
  911. isValidField = null;
  912. this.disableSubmitButtons(true);
  913. $parent.removeClass('has-success').removeClass('has-error');
  914. if ($icon) {
  915. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).addClass(this.options.feedbackIcons.validating).show();
  916. }
  917. if ($tab) {
  918. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  919. }
  920. break;
  921. case this.STATUS_INVALID:
  922. isValidField = false;
  923. this.disableSubmitButtons(true);
  924. $parent.removeClass('has-success').addClass('has-error');
  925. if ($icon) {
  926. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.invalid).show();
  927. }
  928. if ($tab) {
  929. $tab.removeClass('bv-tab-success').addClass('bv-tab-error');
  930. }
  931. break;
  932. case this.STATUS_VALID:
  933. // If the field is valid (passes all validators)
  934. isValidField = ($allErrors.filter('[data-bv-result="' + this.STATUS_NOT_VALIDATED +'"]').length === 0)
  935. ? ($allErrors.filter('[data-bv-result="' + this.STATUS_VALID +'"]').length === $allErrors.length) // All validators are completed
  936. : null; // There are some validators that have not done
  937. if (isValidField !== null) {
  938. this.disableSubmitButtons(this.$submitButton ? !this.isValid() : !isValidField);
  939. if ($icon) {
  940. $icon
  941. .removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).removeClass(this.options.feedbackIcons.valid)
  942. .addClass(isValidField ? this.options.feedbackIcons.valid : this.options.feedbackIcons.invalid)
  943. .show();
  944. }
  945. }
  946. $parent.removeClass('has-error has-success').addClass(this.isValidContainer($parent) ? 'has-success' : 'has-error');
  947. if ($tab) {
  948. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error').addClass(this.isValidContainer($tabPane) ? 'bv-tab-success' : 'bv-tab-error');
  949. }
  950. break;
  951. case this.STATUS_NOT_VALIDATED:
  952. /* falls through */
  953. default:
  954. isValidField = null;
  955. this.disableSubmitButtons(false);
  956. $parent.removeClass('has-success').removeClass('has-error');
  957. if ($icon) {
  958. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).hide();
  959. }
  960. if ($tab) {
  961. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  962. }
  963. break;
  964. }
  965. switch (true) {
  966. // Only show the first error message if it is placed inside a tooltip ...
  967. case ($icon && 'tooltip' === container):
  968. (isValidField === false)
  969. ? $icon.css('cursor', 'pointer').tooltip('destroy').tooltip({
  970. container: 'body',
  971. html: true,
  972. placement: 'auto top',
  973. title: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html()
  974. })
  975. : $icon.css('cursor', '').tooltip('destroy');
  976. break;
  977. // ... or popover
  978. case ($icon && 'popover' === container):
  979. (isValidField === false)
  980. ? $icon.css('cursor', 'pointer').popover('destroy').popover({
  981. container: 'body',
  982. content: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html(),
  983. html: true,
  984. placement: 'auto top',
  985. trigger: 'hover click'
  986. })
  987. : $icon.css('cursor', '').popover('destroy');
  988. break;
  989. default:
  990. (status === this.STATUS_INVALID) ? $errors.show() : $errors.hide();
  991. break;
  992. }
  993. // Trigger an event
  994. $field.trigger($.Event(this.options.events.fieldStatus), {
  995. bv: this,
  996. field: field,
  997. element: $field,
  998. status: status
  999. });
  1000. this._onFieldValidated($field, validatorName);
  1001. }
  1002. return this;
  1003. },
  1004. /**
  1005. * Check the form validity
  1006. *
  1007. * @returns {Boolean}
  1008. */
  1009. isValid: function() {
  1010. for (var field in this.options.fields) {
  1011. if (!this.isValidField(field)) {
  1012. return false;
  1013. }
  1014. }
  1015. return true;
  1016. },
  1017. /**
  1018. * Check if the field is valid or not
  1019. *
  1020. * @param {String|jQuery} field The field name or field element
  1021. * @returns {Boolean}
  1022. */
  1023. isValidField: function(field) {
  1024. var fields = $([]);
  1025. switch (typeof field) {
  1026. case 'object':
  1027. fields = field;
  1028. field = field.attr('data-bv-field');
  1029. break;
  1030. case 'string':
  1031. fields = this.getFieldElements(field);
  1032. break;
  1033. default:
  1034. break;
  1035. }
  1036. if (fields.length === 0 || this.options.fields[field] === null || this.options.fields[field].enabled === false) {
  1037. return true;
  1038. }
  1039. var type = fields.attr('type'),
  1040. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  1041. $field, validatorName, status;
  1042. for (var i = 0; i < total; i++) {
  1043. $field = fields.eq(i);
  1044. if (this._isExcluded($field)) {
  1045. continue;
  1046. }
  1047. for (validatorName in this.options.fields[field].validators) {
  1048. if (this.options.fields[field].validators[validatorName].enabled === false) {
  1049. continue;
  1050. }
  1051. status = $field.data('bv.result.' + validatorName);
  1052. if (status !== this.STATUS_VALID) {
  1053. return false;
  1054. }
  1055. }
  1056. }
  1057. return true;
  1058. },
  1059. /**
  1060. * Check if all fields inside a given container are valid.
  1061. * It's useful when working with a wizard-like such as tab, collapse
  1062. *
  1063. * @param {String|jQuery} container The container selector or element
  1064. * @returns {Boolean}
  1065. */
  1066. isValidContainer: function(container) {
  1067. var that = this,
  1068. map = {},
  1069. $container = ('string' === typeof container) ? $(container) : container;
  1070. if ($container.length === 0) {
  1071. return true;
  1072. }
  1073. $container.find('[data-bv-field]').each(function() {
  1074. var $field = $(this),
  1075. field = $field.attr('data-bv-field');
  1076. if (!that._isExcluded($field) && !map[field]) {
  1077. map[field] = $field;
  1078. }
  1079. });
  1080. for (var field in map) {
  1081. var $f = map[field];
  1082. if ($f.data('bv.messages')
  1083. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]')
  1084. .filter('[data-bv-result="' + this.STATUS_INVALID +'"]')
  1085. .length > 0)
  1086. {
  1087. return false;
  1088. }
  1089. }
  1090. return true;
  1091. },
  1092. /**
  1093. * Submit the form using default submission.
  1094. * It also does not perform any validations when submitting the form
  1095. */
  1096. defaultSubmit: function() {
  1097. if (this.$submitButton) {
  1098. // Create hidden input to send the submit buttons
  1099. $('<input/>')
  1100. .attr('type', 'hidden')
  1101. .attr('data-bv-submit-hidden', '')
  1102. .attr('name', this.$submitButton.attr('name'))
  1103. .val(this.$submitButton.val())
  1104. .appendTo(this.$form);
  1105. }
  1106. // Submit form
  1107. this.$form.off('submit.bv').submit();
  1108. },
  1109. // ---
  1110. // Useful APIs which aren't used internally
  1111. // ---
  1112. /**
  1113. * Get the list of invalid fields
  1114. *
  1115. * @returns {jQuery[]}
  1116. */
  1117. getInvalidFields: function() {
  1118. return this.$invalidFields;
  1119. },
  1120. /**
  1121. * Returns the clicked submit button
  1122. *
  1123. * @returns {jQuery}
  1124. */
  1125. getSubmitButton: function() {
  1126. return this.$submitButton;
  1127. },
  1128. /**
  1129. * Get the error messages
  1130. *
  1131. * @param {String|jQuery} [field] The field name or field element
  1132. * If the field is not defined, the method returns all error messages of all fields
  1133. * @param {String} [validator] The name of validator
  1134. * If the validator is not defined, the method returns error messages of all validators
  1135. * @returns {String[]}
  1136. */
  1137. getMessages: function(field, validator) {
  1138. var that = this,
  1139. messages = [],
  1140. $fields = $([]);
  1141. switch (true) {
  1142. case (field && 'object' === typeof field):
  1143. $fields = field;
  1144. break;
  1145. case (field && 'string' === typeof field):
  1146. var f = this.getFieldElements(field);
  1147. if (f.length > 0) {
  1148. var type = f.attr('type');
  1149. $fields = ('radio' === type || 'checkbox' === type) ? f.eq(0) : f;
  1150. }
  1151. break;
  1152. default:
  1153. $fields = this.$invalidFields;
  1154. break;
  1155. }
  1156. var filter = validator ? '[data-bv-validator="' + validator + '"]' : '';
  1157. $fields.each(function() {
  1158. messages = messages.concat(
  1159. $(this)
  1160. .data('bv.messages')
  1161. .find('.help-block[data-bv-for="' + $(this).attr('data-bv-field') + '"][data-bv-result="' + that.STATUS_INVALID + '"]' + filter)
  1162. .map(function() {
  1163. var v = $(this).attr('data-bv-validator'),
  1164. f = $(this).attr('data-bv-for');
  1165. return (that.options.fields[f].validators[v].enabled === false) ? '' : $(this).html();
  1166. })
  1167. .get()
  1168. );
  1169. });
  1170. return messages;
  1171. },
  1172. /**
  1173. * Update the option of a specific validator
  1174. *
  1175. * @param {String|jQuery} field The field name or field element
  1176. * @param {String} validator The validator name
  1177. * @param {String} option The option name
  1178. * @param {String} value The value to set
  1179. * @returns {BootstrapValidator}
  1180. */
  1181. updateOption: function(field, validator, option, value) {
  1182. if ('object' === typeof field) {
  1183. field = field.attr('data-bv-field');
  1184. }
  1185. if (this.options.fields[field] && this.options.fields[field].validators[validator]) {
  1186. this.options.fields[field].validators[validator][option] = value;
  1187. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validator);
  1188. }
  1189. return this;
  1190. },
  1191. /**
  1192. * Add a new field
  1193. *
  1194. * @param {String|jQuery} field The field name or field element
  1195. * @param {Object} [options] The validator rules
  1196. * @returns {BootstrapValidator}
  1197. */
  1198. addField: function(field, options) {
  1199. var fields = $([]);
  1200. switch (typeof field) {
  1201. case 'object':
  1202. fields = field;
  1203. field = field.attr('data-bv-field') || field.attr('name');
  1204. break;
  1205. case 'string':
  1206. delete this._cacheFields[field];
  1207. fields = this.getFieldElements(field);
  1208. break;
  1209. default:
  1210. break;
  1211. }
  1212. fields.attr('data-bv-field', field);
  1213. var type = fields.attr('type'),
  1214. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1215. for (var i = 0; i < total; i++) {
  1216. var $field = fields.eq(i);
  1217. // Try to parse the options from HTML attributes
  1218. var opts = this._parseOptions($field);
  1219. opts = (opts === null) ? options : $.extend(true, options, opts);
  1220. this.options.fields[field] = $.extend(true, this.options.fields[field], opts);
  1221. // Update the cache
  1222. this._cacheFields[field] = this._cacheFields[field] ? this._cacheFields[field].add($field) : $field;
  1223. // Init the element
  1224. this._initField(('checkbox' === type || 'radio' === type) ? field : $field);
  1225. }
  1226. this.disableSubmitButtons(false);
  1227. // Trigger an event
  1228. this.$form.trigger($.Event(this.options.events.fieldAdded), {
  1229. field: field,
  1230. element: fields,
  1231. options: this.options.fields[field]
  1232. });
  1233. return this;
  1234. },
  1235. /**
  1236. * Remove a given field
  1237. *
  1238. * @param {String|jQuery} field The field name or field element
  1239. * @returns {BootstrapValidator}
  1240. */
  1241. removeField: function(field) {
  1242. var fields = $([]);
  1243. switch (typeof field) {
  1244. case 'object':
  1245. fields = field;
  1246. field = field.attr('data-bv-field') || field.attr('name');
  1247. fields.attr('data-bv-field', field);
  1248. break;
  1249. case 'string':
  1250. fields = this.getFieldElements(field);
  1251. break;
  1252. default:
  1253. break;
  1254. }
  1255. if (fields.length === 0) {
  1256. return this;
  1257. }
  1258. var type = fields.attr('type'),
  1259. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1260. for (var i = 0; i < total; i++) {
  1261. var $field = fields.eq(i);
  1262. // Remove from the list of invalid fields
  1263. this.$invalidFields = this.$invalidFields.not($field);
  1264. // Update the cache
  1265. this._cacheFields[field] = this._cacheFields[field].not($field);
  1266. }
  1267. if (!this._cacheFields[field] || this._cacheFields[field].length === 0) {
  1268. delete this.options.fields[field];
  1269. }
  1270. if ('checkbox' === type || 'radio' === type) {
  1271. this._initField(field);
  1272. }
  1273. this.disableSubmitButtons(false);
  1274. // Trigger an event
  1275. this.$form.trigger($.Event(this.options.events.fieldRemoved), {
  1276. field: field,
  1277. element: fields
  1278. });
  1279. return this;
  1280. },
  1281. /**
  1282. * Reset given field
  1283. *
  1284. * @param {String|jQuery} field The field name or field element
  1285. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1286. * @returns {BootstrapValidator}
  1287. */
  1288. resetField: function(field, resetValue) {
  1289. var $fields = $([]);
  1290. switch (typeof field) {
  1291. case 'object':
  1292. $fields = field;
  1293. field = field.attr('data-bv-field');
  1294. break;
  1295. case 'string':
  1296. $fields = this.getFieldElements(field);
  1297. break;
  1298. default:
  1299. break;
  1300. }
  1301. var total = $fields.length;
  1302. if (this.options.fields[field]) {
  1303. for (var i = 0; i < total; i++) {
  1304. for (var validator in this.options.fields[field].validators) {
  1305. $fields.eq(i).removeData('bv.dfs.' + validator);
  1306. }
  1307. }
  1308. }
  1309. // Mark field as not validated yet
  1310. this.updateStatus(field, this.STATUS_NOT_VALIDATED);
  1311. if (resetValue) {
  1312. var type = $fields.attr('type');
  1313. ('radio' === type || 'checkbox' === type) ? $fields.removeAttr('checked').removeAttr('selected') : $fields.val('');
  1314. }
  1315. return this;
  1316. },
  1317. /**
  1318. * Reset the form
  1319. *
  1320. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1321. * @returns {BootstrapValidator}
  1322. */
  1323. resetForm: function(resetValue) {
  1324. for (var field in this.options.fields) {
  1325. this.resetField(field, resetValue);
  1326. }
  1327. this.$invalidFields = $([]);
  1328. this.$submitButton = null;
  1329. // Enable submit buttons
  1330. this.disableSubmitButtons(false);
  1331. return this;
  1332. },
  1333. /**
  1334. * Revalidate given field
  1335. * It's used when you need to revalidate the field which its value is updated by other plugin
  1336. *
  1337. * @param {String|jQuery} field The field name of field element
  1338. * @returns {BootstrapValidator}
  1339. */
  1340. revalidateField: function(field) {
  1341. this.updateStatus(field, this.STATUS_NOT_VALIDATED)
  1342. .validateField(field);
  1343. return this;
  1344. },
  1345. /**
  1346. * Enable/Disable all validators to given field
  1347. *
  1348. * @param {String} field The field name
  1349. * @param {Boolean} enabled Enable/Disable field validators
  1350. * @param {String} [validatorName] The validator name. If null, all validators will be enabled/disabled
  1351. * @returns {BootstrapValidator}
  1352. */
  1353. enableFieldValidators: function(field, enabled, validatorName) {
  1354. var validators = this.options.fields[field].validators;
  1355. // Enable/disable particular validator
  1356. if (validatorName
  1357. && validators
  1358. && validators[validatorName] && validators[validatorName].enabled !== enabled)
  1359. {
  1360. this.options.fields[field].validators[validatorName].enabled = enabled;
  1361. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validatorName);
  1362. }
  1363. // Enable/disable all validators
  1364. else if (!validatorName && this.options.fields[field].enabled !== enabled) {
  1365. this.options.fields[field].enabled = enabled;
  1366. for (var v in validators) {
  1367. this.enableFieldValidators(field, enabled, v);
  1368. }
  1369. }
  1370. return this;
  1371. },
  1372. /**
  1373. * Some validators have option which its value is dynamic.
  1374. * For example, the zipCode validator has the country option which might be changed dynamically by a select element.
  1375. *
  1376. * @param {jQuery|String} field The field name or element
  1377. * @param {String|Function} option The option which can be determined by:
  1378. * - a string
  1379. * - name of field which defines the value
  1380. * - name of function which returns the value
  1381. * - a function returns the value
  1382. *
  1383. * The callback function has the format of
  1384. * callback: function(value, validator, $field) {
  1385. * // value is the value of field
  1386. * // validator is the BootstrapValidator instance
  1387. * // $field is the field element
  1388. * }
  1389. *
  1390. * @returns {String}
  1391. */
  1392. getDynamicOption: function(field, option) {
  1393. var $field = ('string' === typeof field) ? this.getFieldElements(field) : field,
  1394. value = $field.val();
  1395. // Option can be determined by
  1396. // ... a function
  1397. if ('function' === typeof option) {
  1398. return $.fn.bootstrapValidator.helpers.call(option, [value, this, $field]);
  1399. }
  1400. // ... value of other field
  1401. else if ('string' === typeof option) {
  1402. var $f = this.getFieldElements(option);
  1403. if ($f.length) {
  1404. return $f.val();
  1405. }
  1406. // ... return value of callback
  1407. else {
  1408. return $.fn.bootstrapValidator.helpers.call(option, [value, this, $field]) || option;
  1409. }
  1410. }
  1411. return null;
  1412. },
  1413. /**
  1414. * Destroy the plugin
  1415. * It will remove all error messages, feedback icons and turn off the events
  1416. */
  1417. destroy: function() {
  1418. var field, fields, $field, validator, $icon, group;
  1419. for (field in this.options.fields) {
  1420. fields = this.getFieldElements(field);
  1421. group = this.options.fields[field].group || this.options.group;
  1422. for (var i = 0; i < fields.length; i++) {
  1423. $field = fields.eq(i);
  1424. $field
  1425. // Remove all error messages
  1426. .data('bv.messages')
  1427. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]').remove().end()
  1428. .end()
  1429. .removeData('bv.messages')
  1430. // Remove feedback classes
  1431. .parents(group)
  1432. .removeClass('has-feedback has-error has-success')
  1433. .end()
  1434. // Turn off events
  1435. .off('.bv')
  1436. .removeAttr('data-bv-field');
  1437. // Remove feedback icons, tooltip/popover container
  1438. $icon = $field.parents(group).find('i[data-bv-icon-for="' + field + '"]');
  1439. if ($icon) {
  1440. 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);
  1441. switch (container) {
  1442. case 'tooltip':
  1443. $icon.tooltip('destroy').remove();
  1444. break;
  1445. case 'popover':
  1446. $icon.popover('destroy').remove();
  1447. break;
  1448. default:
  1449. $icon.remove();
  1450. break;
  1451. }
  1452. }
  1453. for (validator in this.options.fields[field].validators) {
  1454. if ($field.data('bv.dfs.' + validator)) {
  1455. $field.data('bv.dfs.' + validator).reject();
  1456. }
  1457. $field.removeData('bv.result.' + validator)
  1458. .removeData('bv.response.' + validator)
  1459. .removeData('bv.dfs.' + validator);
  1460. // Destroy the validator
  1461. if ('function' === typeof $.fn.bootstrapValidator.validators[validator].destroy) {
  1462. $.fn.bootstrapValidator.validators[validator].destroy(this, $field, this.options.fields[field].validators[validator]);
  1463. }
  1464. }
  1465. }
  1466. }
  1467. this.disableSubmitButtons(false); // Enable submit buttons
  1468. this.$hiddenButton.remove(); // Remove the hidden button
  1469. this.$form
  1470. .removeClass(this.options.elementClass)
  1471. .off('.bv')
  1472. .removeData('bootstrapValidator')
  1473. // Remove generated hidden elements
  1474. .find('[data-bv-submit-hidden]').remove().end()
  1475. .find('[type="submit"]').off('click.bv');
  1476. }
  1477. };
  1478. // Plugin definition
  1479. $.fn.bootstrapValidator = function(option) {
  1480. var params = arguments;
  1481. return this.each(function() {
  1482. var $this = $(this),
  1483. data = $this.data('bootstrapValidator'),
  1484. options = 'object' === typeof option && option;
  1485. if (!data) {
  1486. data = new BootstrapValidator(this, options);
  1487. $this.data('bootstrapValidator', data);
  1488. }
  1489. // Allow to call plugin method
  1490. if ('string' === typeof option) {
  1491. data[option].apply(data, Array.prototype.slice.call(params, 1));
  1492. }
  1493. });
  1494. };
  1495. // The default options
  1496. $.fn.bootstrapValidator.DEFAULT_OPTIONS = {
  1497. // The form CSS class
  1498. elementClass: 'bv-form',
  1499. // Default invalid message
  1500. message: 'This value is not valid',
  1501. // The CSS selector for indicating the element consists the field
  1502. // By default, each field is placed inside the <div class="form-group"></div>
  1503. // You should adjust this option if your form group consists of many fields which not all of them need to be validated
  1504. group: '.form-group',
  1505. //The error messages container. It can be:
  1506. // - 'tooltip' if you want to use Bootstrap tooltip to show error messages
  1507. // - 'popover' if you want to use Bootstrap popover to show error messages
  1508. // - a CSS selector indicating the container
  1509. // In the first two cases, since the tooltip/popover should be small enough, the plugin only shows only one error message
  1510. // You also can define the message container for particular field
  1511. container: null,
  1512. // The field will not be live validated if its length is less than this number of characters
  1513. threshold: null,
  1514. // Indicate fields which won't be validated
  1515. // By default, the plugin will not validate the following kind of fields:
  1516. // - disabled
  1517. // - hidden
  1518. // - invisible
  1519. //
  1520. // The setting consists of jQuery filters. Accept 3 formats:
  1521. // - A string. Use a comma to separate filter
  1522. // - An array. Each element is a filter
  1523. // - An array. Each element can be a callback function
  1524. // function($field, validator) {
  1525. // $field is jQuery object representing the field element
  1526. // validator is the BootstrapValidator instance
  1527. // return true or false;
  1528. // }
  1529. //
  1530. // The 3 following settings are equivalent:
  1531. //
  1532. // 1) ':disabled, :hidden, :not(:visible)'
  1533. // 2) [':disabled', ':hidden', ':not(:visible)']
  1534. // 3) [':disabled', ':hidden', function($field) {
  1535. // return !$field.is(':visible');
  1536. // }]
  1537. excluded: [':disabled', ':hidden', ':not(:visible)'],
  1538. // Shows ok/error/loading icons based on the field validity.
  1539. // This feature requires Bootstrap v3.1.0 or later (http://getbootstrap.com/css/#forms-control-validation).
  1540. // Since Bootstrap doesn't provide any methods to know its version, this option cannot be on/off automatically.
  1541. // In other word, to use this feature you have to upgrade your Bootstrap to v3.1.0 or later.
  1542. //
  1543. // Examples:
  1544. // - Use Glyphicons icons:
  1545. // feedbackIcons: {
  1546. // valid: 'glyphicon glyphicon-ok',
  1547. // invalid: 'glyphicon glyphicon-remove',
  1548. // validating: 'glyphicon glyphicon-refresh'
  1549. // }
  1550. // - Use FontAwesome icons:
  1551. // feedbackIcons: {
  1552. // valid: 'fa fa-check',
  1553. // invalid: 'fa fa-times',
  1554. // validating: 'fa fa-refresh'
  1555. // }
  1556. feedbackIcons: {
  1557. valid: null,
  1558. invalid: null,
  1559. validating: null
  1560. },
  1561. // The submit buttons selector
  1562. // These buttons will be disabled to prevent the valid form from multiple submissions
  1563. submitButtons: '[type="submit"]',
  1564. // Live validating option
  1565. // Can be one of 3 values:
  1566. // - enabled: The plugin validates fields as soon as they are changed
  1567. // - disabled: Disable the live validating. The error messages are only shown after the form is submitted
  1568. // - submitted: The live validating is enabled after the form is submitted
  1569. live: 'enabled',
  1570. // Map the field name with validator rules
  1571. fields: null,
  1572. // Use custom event name to avoid window.onerror being invoked by jQuery
  1573. // See https://github.com/nghuuphuoc/bootstrapvalidator/issues/630
  1574. events: {
  1575. formInit: 'init.form.bv',
  1576. formError: 'error.form.bv',
  1577. formSuccess: 'success.form.bv',
  1578. fieldAdded: 'added.field.bv',
  1579. fieldRemoved: 'removed.field.bv',
  1580. fieldInit: 'init.field.bv',
  1581. fieldError: 'error.field.bv',
  1582. fieldSuccess: 'success.field.bv',
  1583. fieldStatus: 'status.field.bv',
  1584. validatorError: 'error.validator.bv',
  1585. validatorSuccess: 'success.validator.bv'
  1586. },
  1587. // Whether to be verbose when validating a field or not.
  1588. // Possible values:
  1589. // - true: when a field has multiple validators, all of them will be checked, and respectively - if errors occur in
  1590. // multiple validators, all of them will be displayed to the user
  1591. // - false: when a field has multiple validators, validation for this field will be terminated upon the first encountered error.
  1592. // Thus, only the very first error message related to this field will be displayed to the user
  1593. verbose: true
  1594. };
  1595. // Available validators
  1596. $.fn.bootstrapValidator.validators = {};
  1597. // i18n
  1598. $.fn.bootstrapValidator.i18n = {};
  1599. $.fn.bootstrapValidator.Constructor = BootstrapValidator;
  1600. // Helper methods, which can be used in validator class
  1601. $.fn.bootstrapValidator.helpers = {
  1602. /**
  1603. * Execute a callback function
  1604. *
  1605. * @param {String|Function} functionName Can be
  1606. * - name of global function
  1607. * - name of namespace function (such as A.B.C)
  1608. * - a function
  1609. * @param {Array} args The callback arguments
  1610. */
  1611. call: function(functionName, args) {
  1612. if ('function' === typeof functionName) {
  1613. return functionName.apply(this, args);
  1614. } else if ('string' === typeof functionName) {
  1615. if ('()' === functionName.substring(functionName.length - 2)) {
  1616. functionName = functionName.substring(0, functionName.length - 2);
  1617. }
  1618. var ns = functionName.split('.'),
  1619. func = ns.pop(),
  1620. context = window;
  1621. for (var i = 0; i < ns.length; i++) {
  1622. context = context[ns[i]];
  1623. }
  1624. return (typeof context[func] === 'undefined') ? null : context[func].apply(this, args);
  1625. }
  1626. },
  1627. /**
  1628. * Format a string
  1629. * It's used to format the error message
  1630. * format('The field must between %s and %s', [10, 20]) = 'The field must between 10 and 20'
  1631. *
  1632. * @param {String} message
  1633. * @param {Array} parameters
  1634. * @returns {String}
  1635. */
  1636. format: function(message, parameters) {
  1637. if (!$.isArray(parameters)) {
  1638. parameters = [parameters];
  1639. }
  1640. for (var i in parameters) {
  1641. message = message.replace('%s', parameters[i]);
  1642. }
  1643. return message;
  1644. },
  1645. /**
  1646. * Validate a date
  1647. *
  1648. * @param {Number} year The full year in 4 digits
  1649. * @param {Number} month The month number
  1650. * @param {Number} day The day number
  1651. * @param {Boolean} [notInFuture] If true, the date must not be in the future
  1652. * @returns {Boolean}
  1653. */
  1654. date: function(year, month, day, notInFuture) {
  1655. if (isNaN(year) || isNaN(month) || isNaN(day)) {
  1656. return false;
  1657. }
  1658. if (day.length > 2 || month.length > 2 || year.length > 4) {
  1659. return false;
  1660. }
  1661. day = parseInt(day, 10);
  1662. month = parseInt(month, 10);
  1663. year = parseInt(year, 10);
  1664. if (year < 1000 || year > 9999 || month <= 0 || month > 12) {
  1665. return false;
  1666. }
  1667. var numDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  1668. // Update the number of days in Feb of leap year
  1669. if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
  1670. numDays[1] = 29;
  1671. }
  1672. // Check the day
  1673. if (day <= 0 || day > numDays[month - 1]) {
  1674. return false;
  1675. }
  1676. if (notInFuture === true) {
  1677. var currentDate = new Date(),
  1678. currentYear = currentDate.getFullYear(),
  1679. currentMonth = currentDate.getMonth(),
  1680. currentDay = currentDate.getDate();
  1681. return (year < currentYear
  1682. || (year === currentYear && month - 1 < currentMonth)
  1683. || (year === currentYear && month - 1 === currentMonth && day < currentDay));
  1684. }
  1685. return true;
  1686. },
  1687. /**
  1688. * Implement Luhn validation algorithm
  1689. * Credit to https://gist.github.com/ShirtlessKirk/2134376
  1690. *
  1691. * @see http://en.wikipedia.org/wiki/Luhn
  1692. * @param {String} value
  1693. * @returns {Boolean}
  1694. */
  1695. luhn: function(value) {
  1696. var length = value.length,
  1697. mul = 0,
  1698. prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
  1699. sum = 0;
  1700. while (length--) {
  1701. sum += prodArr[mul][parseInt(value.charAt(length), 10)];
  1702. mul ^= 1;
  1703. }
  1704. return (sum % 10 === 0 && sum > 0);
  1705. },
  1706. /**
  1707. * Implement modulus 11, 10 (ISO 7064) algorithm
  1708. *
  1709. * @param {String} value
  1710. * @returns {Boolean}
  1711. */
  1712. mod11And10: function(value) {
  1713. var check = 5,
  1714. length = value.length;
  1715. for (var i = 0; i < length; i++) {
  1716. check = (((check || 10) * 2) % 11 + parseInt(value.charAt(i), 10)) % 10;
  1717. }
  1718. return (check === 1);
  1719. },
  1720. /**
  1721. * Implements Mod 37, 36 (ISO 7064) algorithm
  1722. * Usages:
  1723. * mod37And36('A12425GABC1234002M')
  1724. * mod37And36('002006673085', '0123456789')
  1725. *
  1726. * @param {String} value
  1727. * @param {String} [alphabet]
  1728. * @returns {Boolean}
  1729. */
  1730. mod37And36: function(value, alphabet) {
  1731. alphabet = alphabet || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  1732. var modulus = alphabet.length,
  1733. length = value.length,
  1734. check = Math.floor(modulus / 2);
  1735. for (var i = 0; i < length; i++) {
  1736. check = (((check || modulus) * 2) % (modulus + 1) + alphabet.indexOf(value.charAt(i))) % modulus;
  1737. }
  1738. return (check === 1);
  1739. }
  1740. };
  1741. }(window.jQuery));