bootstrapValidator.js 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. /**
  2. * BootstrapValidator (http://bootstrapvalidator.com)
  3. *
  4. * The best jQuery plugin to validate form fields. Designed to use with Bootstrap 3
  5. *
  6. * @author http://twitter.com/nghuuphuoc
  7. * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc
  8. * @license MIT
  9. */
  10. (function($) {
  11. var BootstrapValidator = function(form, options) {
  12. this.$form = $(form);
  13. this.options = $.extend({}, BootstrapValidator.DEFAULT_OPTIONS, options);
  14. this.$invalidFields = $([]); // Array of invalid fields
  15. this.$submitButton = null; // The submit button which is clicked to submit form
  16. // Validating status
  17. this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED';
  18. this.STATUS_VALIDATING = 'VALIDATING';
  19. this.STATUS_INVALID = 'INVALID';
  20. this.STATUS_VALID = 'VALID';
  21. // Determine the event that is fired when user change the field value
  22. // Most modern browsers supports input event except IE 7, 8.
  23. // IE 9 supports input event but the event is still not fired if I press the backspace key.
  24. // Get IE version
  25. // https://gist.github.com/padolsey/527683/#comment-7595
  26. var ieVersion = (function() {
  27. var v = 3, div = document.createElement('div'), a = div.all || [];
  28. while (div.innerHTML = '<!--[if gt IE '+(++v)+']><br><![endif]-->', a[0]);
  29. return v > 4 ? v : !v;
  30. }());
  31. var el = document.createElement('div');
  32. this._changeEvent = (ieVersion === 9 || !('oninput' in el)) ? 'keyup' : 'input';
  33. // The flag to indicate that the form is ready to submit when a remote/callback validator returns
  34. this._submitIfValid = null;
  35. // Field elements
  36. this._cacheFields = {};
  37. this._init();
  38. };
  39. // The default options
  40. BootstrapValidator.DEFAULT_OPTIONS = {
  41. // The form CSS class
  42. elementClass: 'bv-form',
  43. // Default invalid message
  44. message: 'This value is not valid',
  45. // The error messages container
  46. // It can be:
  47. // * 'tooltip' if you want to use Bootstrap tooltip to show error messages
  48. // * 'popover' if you want to use Bootstrap popover to show error messages
  49. // * a CSS selector indicating the container
  50. //
  51. // In the first two cases, since the tooltip/popover should be small enough, the plugin only shows only one error message
  52. // You also can define the message container for particular field
  53. container: null,
  54. // The field will not be live validated if its length is less than this number of characters
  55. threshold: null,
  56. // Indicate fields which won't be validated
  57. // By default, the plugin will not validate the following kind of fields:
  58. // - disabled
  59. // - hidden
  60. // - invisible
  61. //
  62. // The setting consists of jQuery filters. Accept 3 formats:
  63. // - A string. Use a comma to separate filter
  64. // - An array. Each element is a filter
  65. // - An array. Each element can be a callback function
  66. // function($field, validator) {
  67. // $field is jQuery object representing the field element
  68. // validator is the BootstrapValidator instance
  69. // return true or false;
  70. // }
  71. //
  72. // The 3 following settings are equivalent:
  73. //
  74. // 1) ':disabled, :hidden, :not(:visible)'
  75. // 2) [':disabled', ':hidden', ':not(:visible)']
  76. // 3) [':disabled', ':hidden', function($field) {
  77. // return !$field.is(':visible');
  78. // }]
  79. excluded: [':disabled', ':hidden', ':not(:visible)'],
  80. // Shows ok/error/loading icons based on the field validity.
  81. // This feature requires Bootstrap v3.1.0 or later (http://getbootstrap.com/css/#forms-control-validation).
  82. // Since Bootstrap doesn't provide any methods to know its version, this option cannot be on/off automatically.
  83. // In other word, to use this feature you have to upgrade your Bootstrap to v3.1.0 or later.
  84. //
  85. // Examples:
  86. // - Use Glyphicons icons:
  87. // feedbackIcons: {
  88. // valid: 'glyphicon glyphicon-ok',
  89. // invalid: 'glyphicon glyphicon-remove',
  90. // validating: 'glyphicon glyphicon-refresh'
  91. // }
  92. // - Use FontAwesome icons:
  93. // feedbackIcons: {
  94. // valid: 'fa fa-check',
  95. // invalid: 'fa fa-times',
  96. // validating: 'fa fa-refresh'
  97. // }
  98. feedbackIcons: {
  99. valid: null,
  100. invalid: null,
  101. validating: null
  102. },
  103. // The submit buttons selector
  104. // These buttons will be disabled to prevent the valid form from multiple submissions
  105. submitButtons: '[type="submit"]',
  106. // The custom submit handler
  107. // It will prevent the form from the default submission
  108. //
  109. // submitHandler: function(validator, form) {
  110. // - validator is the BootstrapValidator instance
  111. // - form is the jQuery object present the current form
  112. // }
  113. submitHandler: null,
  114. // Live validating option
  115. // Can be one of 3 values:
  116. // - enabled: The plugin validates fields as soon as they are changed
  117. // - disabled: Disable the live validating. The error messages are only shown after the form is submitted
  118. // - submitted: The live validating is enabled after the form is submitted
  119. live: 'enabled',
  120. // Map the field name with validator rules
  121. fields: null
  122. };
  123. BootstrapValidator.prototype = {
  124. constructor: BootstrapValidator,
  125. /**
  126. * Init form
  127. */
  128. _init: function() {
  129. var that = this,
  130. options = {
  131. excluded: this.$form.attr('data-bv-excluded'),
  132. trigger: this.$form.attr('data-bv-trigger'),
  133. message: this.$form.attr('data-bv-message'),
  134. container: this.$form.attr('data-bv-container'),
  135. submitButtons: this.$form.attr('data-bv-submitbuttons'),
  136. threshold: this.$form.attr('data-bv-threshold'),
  137. live: this.$form.attr('data-bv-live'),
  138. fields: {},
  139. feedbackIcons: {
  140. valid: this.$form.attr('data-bv-feedbackicons-valid'),
  141. invalid: this.$form.attr('data-bv-feedbackicons-invalid'),
  142. validating: this.$form.attr('data-bv-feedbackicons-validating')
  143. }
  144. },
  145. validator,
  146. v, // Validator name
  147. enabled,
  148. optionName,
  149. optionValue,
  150. html5AttrName,
  151. html5Attrs;
  152. this.$form
  153. // Disable client side validation in HTML 5
  154. .attr('novalidate', 'novalidate')
  155. .addClass(this.options.elementClass)
  156. // Disable the default submission first
  157. .on('submit.bv', function(e) {
  158. e.preventDefault();
  159. that.validate();
  160. })
  161. .on('click', this.options.submitButtons, function() {
  162. that.$submitButton = $(this);
  163. // The user just click the submit button
  164. that._submitIfValid = true;
  165. })
  166. // Find all fields which have either "name" or "data-bv-field" attribute
  167. .find('[name], [data-bv-field]')
  168. .each(function() {
  169. var $field = $(this);
  170. if (that._isExcluded($field)) {
  171. return;
  172. }
  173. var field = $field.attr('name') || $field.attr('data-bv-field'),
  174. validators = {};
  175. for (v in $.fn.bootstrapValidator.validators) {
  176. validator = $.fn.bootstrapValidator.validators[v];
  177. enabled = $field.attr('data-bv-' + v.toLowerCase()) + '';
  178. html5Attrs = ('function' == typeof validator.enableByHtml5) ? validator.enableByHtml5($(this)) : null;
  179. if ((html5Attrs && enabled != 'false')
  180. || (html5Attrs !== true && ('' == enabled || 'true' == enabled)))
  181. {
  182. // Try to parse the options via attributes
  183. validator.html5Attributes = validator.html5Attributes || { message: 'message' };
  184. validators[v] = $.extend({}, html5Attrs == true ? {} : html5Attrs, validators[v]);
  185. for (html5AttrName in validator.html5Attributes) {
  186. optionName = validator.html5Attributes[html5AttrName];
  187. optionValue = $field.attr('data-bv-' + v.toLowerCase() + '-' + html5AttrName);
  188. if (optionValue) {
  189. if ('true' == optionValue) {
  190. optionValue = true;
  191. } else if ('false' == optionValue) {
  192. optionValue = false;
  193. }
  194. validators[v][optionName] = optionValue;
  195. }
  196. }
  197. }
  198. }
  199. var opts = {
  200. trigger: $field.attr('data-bv-trigger'),
  201. message: $field.attr('data-bv-message'),
  202. container: $field.attr('data-bv-container'),
  203. selector: $field.attr('data-bv-selector'),
  204. threshold: $field.attr('data-bv-threshold'),
  205. validators: validators
  206. };
  207. // Check if there is any validators set using HTML attributes
  208. if (!$.isEmptyObject(opts.validators) && !$.isEmptyObject(opts)) {
  209. $field.attr('data-bv-field', field);
  210. options.fields[field] = $.extend({}, opts, options.fields[field]);
  211. }
  212. })
  213. .end()
  214. // Create hidden inputs to send the submit buttons
  215. .find(this.options.submitButtons)
  216. .each(function() {
  217. $('<input/>')
  218. .attr('type', 'hidden')
  219. .attr('name', $(this).attr('name'))
  220. .val($(this).val())
  221. .appendTo(that.$form);
  222. });
  223. this.options = $.extend(true, this.options, options);
  224. for (var field in this.options.fields) {
  225. this._initField(field);
  226. }
  227. },
  228. /**
  229. * Init field
  230. *
  231. * @param {String} field The field name
  232. */
  233. _initField: function(field) {
  234. if (this.options.fields[field] == null || this.options.fields[field].validators == null) {
  235. return;
  236. }
  237. var fields = this.getFieldElements(field);
  238. // We don't need to validate non-existing fields
  239. if (fields == []) {
  240. delete this.options.fields[field];
  241. return;
  242. }
  243. for (var 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. for (var i = 0; i < fields.length; i++) {
  252. this._initFieldElement($(fields[i]));
  253. }
  254. },
  255. /**
  256. * Init field element
  257. *
  258. * @param {jQuery} $field The field element
  259. */
  260. _initFieldElement: function($field) {
  261. var that = this,
  262. field = $field.attr('name') || $field.attr('data-bv-field'),
  263. fields = this.getFieldElements(field),
  264. index = fields.index($field),
  265. type = $field.attr('type'),
  266. total = fields.length,
  267. updateAll = (total == 1) || ('radio' == type) || ('checkbox' == type),
  268. $parent = $field.parents('.form-group'),
  269. // Allow user to indicate where the error messages are shown
  270. container = this.options.fields[field].container || this.options.container,
  271. $message = (container && ['tooltip', 'popover'].indexOf(container) == -1) ? $(container) : this._getMessageContainer($field);
  272. if (container && ['tooltip', 'popover'].indexOf(container) == -1) {
  273. $message.addClass('has-error');
  274. }
  275. // Remove all error messages and feedback icons
  276. $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]').remove();
  277. $parent.find('i[data-bv-icon-for="' + field + '"]').remove();
  278. // Set the attribute to indicate the fields which are defined by selector
  279. if (!$field.attr('data-bv-field')) {
  280. $field.attr('data-bv-field', field);
  281. }
  282. // Whenever the user change the field value, mark it as not validated yet
  283. var event = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == $field.get(0).tagName) ? 'change' : this._changeEvent;
  284. $field.off(event + '.update.bv').on(event + '.update.bv', function() {
  285. // Reset the flag
  286. that._submitIfValid = false;
  287. that.updateElementStatus($(this), that.STATUS_NOT_VALIDATED);
  288. });
  289. // Create help block elements for showing the error messages
  290. $field.data('bv.messages', $message);
  291. for (var validatorName in this.options.fields[field].validators) {
  292. $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
  293. if (!updateAll || index == total - 1) {
  294. $('<small/>')
  295. .css('display', 'none')
  296. .addClass('help-block')
  297. .attr('data-bv-validator', validatorName)
  298. .attr('data-bv-for', field)
  299. .html(this.options.fields[field].validators[validatorName].message || this.options.fields[field].message || this.options.message)
  300. .appendTo($message);
  301. }
  302. }
  303. // Prepare the feedback icons
  304. // Available from Bootstrap 3.1 (http://getbootstrap.com/css/#forms-control-validation)
  305. if (this.options.feedbackIcons
  306. && this.options.feedbackIcons.validating && this.options.feedbackIcons.invalid && this.options.feedbackIcons.valid
  307. && (!updateAll || index == total - 1))
  308. {
  309. $parent.removeClass('has-success').removeClass('has-error').addClass('has-feedback');
  310. var $icon = $('<i/>').css('display', 'none').addClass('form-control-feedback').attr('data-bv-icon-for', field).insertAfter($field);
  311. // The feedback icon does not render correctly if there is no label
  312. // https://github.com/twbs/bootstrap/issues/12873
  313. if ($parent.find('label').length == 0) {
  314. $icon.css('top', 0);
  315. }
  316. // Fix feedback icons in input-group
  317. if ($parent.find('.input-group-addon').length != 0) {
  318. $icon.css({
  319. 'top': 0,
  320. 'z-index': 100
  321. });
  322. }
  323. }
  324. // Set live mode
  325. var trigger = this.options.fields[field].trigger || this.options.trigger || event,
  326. events = $.map(trigger.split(' '), function(item) {
  327. return item + '.live.bv';
  328. }).join(' ');
  329. switch (this.options.live) {
  330. case 'submitted':
  331. break;
  332. case 'disabled':
  333. $field.off(events);
  334. break;
  335. case 'enabled':
  336. default:
  337. $field.off(events).on(events, function() {
  338. that.validateFieldElement($(this));
  339. });
  340. break;
  341. }
  342. },
  343. /**
  344. * Get the element to place the error messages
  345. *
  346. * @param {jQuery} $field The field element
  347. * @returns {jQuery}
  348. */
  349. _getMessageContainer: function($field) {
  350. var $parent = $field.parent();
  351. if ($parent.hasClass('form-group')) {
  352. return $parent;
  353. }
  354. var cssClasses = $parent.attr('class');
  355. if (!cssClasses) {
  356. return this._getMessageContainer($parent);
  357. }
  358. cssClasses = cssClasses.split(' ');
  359. var n = cssClasses.length;
  360. for (var i = 0; i < n; i++) {
  361. if (/^col-(xs|sm|md|lg)-\d+$/.test(cssClasses[i]) || /^col-(xs|sm|md|lg)-offset-\d+$/.test(cssClasses[i])) {
  362. return $parent;
  363. }
  364. }
  365. return this._getMessageContainer($parent);
  366. },
  367. /**
  368. * Called when all validations are completed
  369. */
  370. _submit: function() {
  371. var isValid = this.isValid(),
  372. eventType = isValid ? 'success.form.bv' : 'error.form.bv',
  373. e = $.Event(eventType);
  374. this.$form.trigger(e);
  375. // Call default handler
  376. // Check if whether the submit button is clicked
  377. if (this.$submitButton) {
  378. isValid ? this._onSuccess(e) : this._onError(e);
  379. }
  380. },
  381. /**
  382. * Check if the field is excluded.
  383. * Returning true means that the field will not be validated
  384. *
  385. * @param {jQuery} $field The field element
  386. * @returns {Boolean}
  387. */
  388. _isExcluded: function($field) {
  389. if (this.options.excluded) {
  390. // Convert to array first
  391. if ('string' == typeof this.options.excluded) {
  392. this.options.excluded = $.map(this.options.excluded.split(','), function(item) {
  393. // Trim the spaces
  394. return $.trim(item);
  395. });
  396. }
  397. var length = this.options.excluded.length;
  398. for (var i = 0; i < length; i++) {
  399. if (('string' == typeof this.options.excluded[i] && $field.is(this.options.excluded[i]))
  400. || ('function' == typeof this.options.excluded[i] && this.options.excluded[i].call(this, $field, this) == true))
  401. {
  402. return true;
  403. }
  404. }
  405. }
  406. return false;
  407. },
  408. // --- Events ---
  409. /**
  410. * The default handler of error.form.bv event.
  411. * It will be called when there is a invalid field
  412. *
  413. * @param {jQuery.Event} e The jQuery event object
  414. */
  415. _onError: function(e) {
  416. if (e.isDefaultPrevented()) {
  417. return;
  418. }
  419. if ('submitted' == this.options.live) {
  420. // Enable live mode
  421. this.options.live = 'enabled';
  422. var that = this;
  423. for (var field in this.options.fields) {
  424. (function(f) {
  425. var fields = that.getFieldElements(f);
  426. if (fields.length) {
  427. var type = $(fields[0]).attr('type'),
  428. event = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == $(fields[0]).get(0).tagName) ? 'change' : that._changeEvent,
  429. trigger = that.options.fields[field].trigger || that.options.trigger || event,
  430. events = $.map(trigger.split(' '), function(item) {
  431. return item + '.live.bv';
  432. }).join(' ');
  433. for (var i = 0; i < fields.length; i++) {
  434. $(fields[i]).off(events).on(events, function() {
  435. that.validateFieldElement($(this));
  436. });
  437. }
  438. }
  439. })(field);
  440. }
  441. }
  442. // Focus to the first invalid field
  443. var $firstInvalidField = this.$invalidFields.eq(0);
  444. if ($firstInvalidField) {
  445. // Activate the tab containing the invalid field if exists
  446. var $tab = $firstInvalidField.parents('.tab-pane'),
  447. tabId;
  448. if ($tab && (tabId = $tab.attr('id'))) {
  449. $('a[href="#' + tabId + '"][data-toggle="tab"]').trigger('click.bs.tab.data-api');
  450. }
  451. $firstInvalidField.focus();
  452. }
  453. },
  454. /**
  455. * The default handler of success.form.bv event.
  456. * It will be called when all the fields are valid
  457. *
  458. * @param {jQuery.Event} e The jQuery event object
  459. */
  460. _onSuccess: function(e) {
  461. if (e.isDefaultPrevented()) {
  462. return;
  463. }
  464. // Call the custom submission if enabled
  465. if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) {
  466. // If you want to submit the form inside your submit handler, please call defaultSubmit() method
  467. this.options.submitHandler.call(this, this, this.$form, this.$submitButton);
  468. } else {
  469. this.disableSubmitButtons(true).defaultSubmit();
  470. }
  471. },
  472. /**
  473. * Called after validating a field element
  474. *
  475. * @param {jQuery} $field The field element
  476. */
  477. _onValidateFieldCompleted: function($field) {
  478. var field = $field.attr('data-bv-field'),
  479. validators = this.options.fields[field].validators,
  480. counter = {},
  481. numValidators = 0;
  482. counter[this.STATUS_NOT_VALIDATED] = 0;
  483. counter[this.STATUS_VALIDATING] = 0;
  484. counter[this.STATUS_INVALID] = 0;
  485. counter[this.STATUS_VALID] = 0;
  486. for (var validatorName in validators) {
  487. numValidators++;
  488. var result = $field.data('bv.result.' + validatorName);
  489. if (result) {
  490. counter[result]++;
  491. }
  492. }
  493. var index = this.$invalidFields.index($field);
  494. if (counter[this.STATUS_VALID] == numValidators) {
  495. // Remove from the list of invalid fields
  496. if (index != -1) {
  497. this.$invalidFields.splice(index, 1);
  498. }
  499. this.$form.trigger($.Event('success.field.bv'), [field, $field]);
  500. }
  501. // If all validators are completed and there is at least one validator which doesn't pass
  502. else if (counter[this.STATUS_NOT_VALIDATED] == 0 && counter[this.STATUS_VALIDATING] == 0 && counter[this.STATUS_INVALID] > 0) {
  503. // Add to the list of invalid fields
  504. if (index == -1) {
  505. this.$invalidFields = this.$invalidFields.add($field);
  506. }
  507. this.$form.trigger($.Event('error.field.bv'), [field, $field]);
  508. }
  509. },
  510. // --- Public methods ---
  511. /**
  512. * Retrieve the field elements by given name
  513. *
  514. * @param {String} field The field name
  515. * @returns {null|jQuery[]}
  516. */
  517. getFieldElements: function(field) {
  518. if (!this._cacheFields[field]) {
  519. this._cacheFields[field] = this.options.fields[field].selector
  520. ? $(this.options.fields[field].selector)
  521. : this.$form.find('[name="' + field + '"]');
  522. }
  523. return this._cacheFields[field];
  524. },
  525. /**
  526. * Disable/enable submit buttons
  527. *
  528. * @param {Boolean} disabled Can be true or false
  529. * @returns {BootstrapValidator}
  530. */
  531. disableSubmitButtons: function(disabled) {
  532. if (!disabled) {
  533. this.$form.find(this.options.submitButtons).removeAttr('disabled');
  534. } else if (this.options.live != 'disabled') {
  535. // Don't disable if the live validating mode is disabled
  536. this.$form.find(this.options.submitButtons).attr('disabled', 'disabled');
  537. }
  538. return this;
  539. },
  540. /**
  541. * Validate the form
  542. *
  543. * @returns {BootstrapValidator}
  544. */
  545. validate: function() {
  546. if (!this.options.fields) {
  547. return this;
  548. }
  549. this.disableSubmitButtons(true);
  550. for (var field in this.options.fields) {
  551. this.validateField(field);
  552. }
  553. this._submit();
  554. return this;
  555. },
  556. /**
  557. * Validate given field
  558. *
  559. * @param {String} field The field name
  560. * @returns {BootstrapValidator}
  561. */
  562. validateField: function(field) {
  563. var fields = this.getFieldElements(field),
  564. type = fields.attr('type'),
  565. n = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
  566. for (var i = 0; i < n; i++) {
  567. this.validateFieldElement($(fields[i]));
  568. }
  569. return this;
  570. },
  571. /**
  572. * Validate field element
  573. *
  574. * @param {jQuery} $field The field element
  575. * @returns {BootstrapValidator}
  576. */
  577. validateFieldElement: function($field) {
  578. var that = this,
  579. field = $field.attr('data-bv-field'),
  580. fields = this.getFieldElements(field),
  581. type = $field.attr('type'),
  582. updateAll = (fields && fields.length == 1) || ('radio' == type) || ('checkbox' == type),
  583. validators = this.options.fields[field].validators,
  584. validatorName,
  585. validateResult;
  586. if (!this.options.fields[field]['enabled'] || this._isExcluded($field)) {
  587. return this;
  588. }
  589. for (validatorName in validators) {
  590. if ($field.data('bv.dfs.' + validatorName)) {
  591. $field.data('bv.dfs.' + validatorName).reject();
  592. }
  593. // Don't validate field if it is already done
  594. var result = $field.data('bv.result.' + validatorName);
  595. if (result == this.STATUS_VALID || result == this.STATUS_INVALID) {
  596. this._onValidateFieldCompleted($field);
  597. continue;
  598. }
  599. $field.data('bv.result.' + validatorName, this.STATUS_VALIDATING);
  600. validateResult = $.fn.bootstrapValidator.validators[validatorName].validate(this, $field, validators[validatorName]);
  601. if ('object' == typeof validateResult) {
  602. updateAll ? this.updateStatus(field, this.STATUS_VALIDATING, validatorName)
  603. : this.updateElementStatus($field, this.STATUS_VALIDATING, validatorName);
  604. $field.data('bv.dfs.' + validatorName, validateResult);
  605. validateResult.done(function($f, v, isValid) {
  606. // v is validator name
  607. $f.removeData('bv.dfs.' + v);
  608. updateAll ? that.updateStatus($f.attr('data-bv-field'), isValid ? that.STATUS_VALID : that.STATUS_INVALID, v)
  609. : that.updateElementStatus($f, isValid ? that.STATUS_VALID : that.STATUS_INVALID, v);
  610. if (isValid && that._submitIfValid == true) {
  611. // If a remote validator returns true and the form is ready to submit, then do it
  612. that._submit();
  613. }
  614. });
  615. } else if ('boolean' == typeof validateResult) {
  616. updateAll ? this.updateStatus(field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName)
  617. : this.updateElementStatus($field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  618. }
  619. }
  620. return this;
  621. },
  622. /**
  623. * Update all validating results of elements which have the same field name
  624. *
  625. * @param {String} field The field name
  626. * @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
  627. * @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators
  628. * @returns {BootstrapValidator}
  629. */
  630. updateStatus: function(field, status, validatorName) {
  631. var fields = this.getFieldElements(field),
  632. type = fields.attr('type'),
  633. n = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
  634. for (var i = 0; i < n; i++) {
  635. this.updateElementStatus($(fields[i]), status, validatorName);
  636. }
  637. return this;
  638. },
  639. /**
  640. * Update validating result of given element
  641. *
  642. * @param {jQuery} $field The field element
  643. * @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
  644. * @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators
  645. * @returns {BootstrapValidator}
  646. */
  647. updateElementStatus: function($field, status, validatorName) {
  648. var that = this,
  649. field = $field.attr('data-bv-field'),
  650. $parent = $field.parents('.form-group'),
  651. $message = $field.data('bv.messages'),
  652. $allErrors = $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]'),
  653. $errors = validatorName ? $allErrors.filter('[data-bv-validator="' + validatorName + '"]') : $allErrors,
  654. $icon = $parent.find('.form-control-feedback[data-bv-icon-for="' + field + '"]'),
  655. container = this.options.fields[field].container || this.options.container;
  656. // Update status
  657. if (validatorName) {
  658. $field.data('bv.result.' + validatorName, status);
  659. } else {
  660. for (var v in this.options.fields[field].validators) {
  661. $field.data('bv.result.' + v, status);
  662. }
  663. }
  664. // Determine the tab containing the element
  665. var $tabPane = $field.parents('.tab-pane'),
  666. tabId,
  667. $tab;
  668. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  669. $tab = $('a[href="#' + tabId + '"][data-toggle="tab"]').parent();
  670. }
  671. // Show/hide error elements and feedback icons
  672. $errors.attr('data-bv-result', status);
  673. switch (status) {
  674. case this.STATUS_VALIDATING:
  675. this.disableSubmitButtons(true);
  676. $parent.removeClass('has-success').removeClass('has-error');
  677. if ($icon) {
  678. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).addClass(this.options.feedbackIcons.validating).show();
  679. }
  680. if ($tab) {
  681. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  682. }
  683. switch (true) {
  684. case ($icon && 'tooltip' == container):
  685. $icon.css('cursor', '').tooltip('destroy');
  686. break;
  687. case ($icon && 'popover' == container):
  688. $icon.css('cursor', '').popover('destroy');
  689. break;
  690. default:
  691. $errors.hide();
  692. break;
  693. }
  694. break;
  695. case this.STATUS_INVALID:
  696. this.disableSubmitButtons(true);
  697. $parent.removeClass('has-success').addClass('has-error');
  698. if ($icon) {
  699. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.invalid).show();
  700. }
  701. if ($tab) {
  702. $tab.removeClass('bv-tab-success').addClass('bv-tab-error');
  703. }
  704. switch (true) {
  705. // Only show the first error message if it is placed inside a tooltip or popover
  706. case ($icon && 'tooltip' == container):
  707. $icon.css('cursor', 'pointer').tooltip('destroy').tooltip({
  708. html: true,
  709. placement: 'top',
  710. title: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html()
  711. });
  712. break;
  713. case ($icon && 'popover' == container):
  714. $icon.css('cursor', 'pointer').popover('destroy').popover({
  715. content: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html(),
  716. html: true,
  717. placement: 'top',
  718. trigger: 'hover click'
  719. });
  720. break;
  721. default:
  722. $errors.show();
  723. break;
  724. }
  725. break;
  726. case this.STATUS_VALID:
  727. // If the field is valid (passes all validators)
  728. var validField = $allErrors.filter(function() {
  729. var v = $(this).attr('data-bv-validator');
  730. return $field.data('bv.result.' + v) != that.STATUS_VALID;
  731. }).length == 0;
  732. this.disableSubmitButtons(!validField);
  733. if ($icon) {
  734. $icon
  735. .removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).removeClass(this.options.feedbackIcons.valid)
  736. .addClass(validField ? this.options.feedbackIcons.valid : this.options.feedbackIcons.invalid)
  737. .show();
  738. }
  739. // Check if all elements in given container are valid
  740. var isValidContainer = function($container) {
  741. var map = {};
  742. $container.find('[data-bv-field]').each(function() {
  743. var field = $(this).attr('data-bv-field');
  744. if (!map[field]) {
  745. map[field] = $(this).data('bv.messages');
  746. }
  747. });
  748. for (var field in map) {
  749. if (map[field]
  750. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]')
  751. .filter(function() {
  752. var display = $(this).css('display'), v = $(this).attr('data-bv-validator');
  753. return ('block' == display) || ($field.data('bv.result.' + v) && $field.data('bv.result.' + v) != that.STATUS_VALID);
  754. })
  755. .length != 0)
  756. {
  757. // The field is not valid
  758. return false;
  759. }
  760. }
  761. return true;
  762. };
  763. $parent.removeClass('has-error has-success').addClass(isValidContainer($parent) ? 'has-success' : 'has-error');
  764. if ($tab) {
  765. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error').addClass(isValidContainer($tabPane) ? 'bv-tab-success' : 'bv-tab-error');
  766. }
  767. switch (true) {
  768. // Only show the first error message if it is placed inside a tooltip or popover
  769. case ($icon && 'tooltip' == container):
  770. validField ? $icon.css('cursor', '').tooltip('destroy')
  771. : $icon.css('cursor', 'pointer').tooltip('destroy').tooltip({
  772. html: true,
  773. placement: 'top',
  774. title: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html()
  775. });
  776. break;
  777. case ($icon && 'popover' == container):
  778. validField ? $icon.css('cursor', '').popover('destroy')
  779. : $icon.css('cursor', 'pointer').popover('destroy').popover({
  780. content: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html(),
  781. html: true,
  782. placement: 'top',
  783. trigger: 'hover click'
  784. });
  785. break;
  786. default:
  787. $errors.hide();
  788. break;
  789. }
  790. break;
  791. case this.STATUS_NOT_VALIDATED:
  792. default:
  793. this.disableSubmitButtons(false);
  794. $parent.removeClass('has-success').removeClass('has-error');
  795. if ($icon) {
  796. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).hide();
  797. }
  798. if ($tab) {
  799. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  800. }
  801. switch (true) {
  802. case ($icon && 'tooltip' == container):
  803. $icon.css('cursor', '').tooltip('destroy');
  804. break;
  805. case ($icon && 'popover' == container):
  806. $icon.css('cursor', '').popover('destroy');
  807. break;
  808. default:
  809. $errors.hide();
  810. break;
  811. }
  812. break;
  813. }
  814. this._onValidateFieldCompleted($field);
  815. return this;
  816. },
  817. /**
  818. * Check the form validity
  819. *
  820. * @returns {Boolean}
  821. */
  822. isValid: function() {
  823. var fields, field, $field,
  824. type, status, validatorName,
  825. n, i;
  826. for (field in this.options.fields) {
  827. if (this.options.fields[field] == null || !this.options.fields[field]['enabled']) {
  828. continue;
  829. }
  830. fields = this.getFieldElements(field);
  831. type = fields.attr('type');
  832. n = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
  833. for (i = 0; i < n; i++) {
  834. $field = $(fields[i]);
  835. if (this._isExcluded($field)) {
  836. continue;
  837. }
  838. for (validatorName in this.options.fields[field].validators) {
  839. status = $field.data('bv.result.' + validatorName);
  840. if (status != this.STATUS_VALID) {
  841. return false;
  842. }
  843. }
  844. }
  845. }
  846. return true;
  847. },
  848. /**
  849. * Submit the form using default submission.
  850. * It also does not perform any validations when submitting the form
  851. *
  852. * It might be used when you want to submit the form right inside the submitHandler()
  853. */
  854. defaultSubmit: function() {
  855. this.$form.off('submit.bv').submit();
  856. },
  857. // Useful APIs which aren't used internally
  858. /**
  859. * Get the list of invalid fields
  860. *
  861. * @returns {jQuery[]}
  862. */
  863. getInvalidFields: function() {
  864. return this.$invalidFields;
  865. },
  866. /**
  867. * Get the error messages
  868. *
  869. * @param {jQuery|String} [field] The field, which can be
  870. * - a string: The field name
  871. * - a jQuery object representing the field element
  872. * If the field is not defined, the method returns all error messages of all fields
  873. * @returns {String[]}
  874. */
  875. getErrors: function(field) {
  876. var that = this,
  877. messages = [],
  878. $fields = $([]);
  879. switch (true) {
  880. case (field && 'object' == typeof field):
  881. $fields = field;
  882. break;
  883. case (field && 'string' == typeof field):
  884. var f = this.getFieldElements(field);
  885. if (f.length > 0) {
  886. var type = f.attr('type');
  887. $fields = ('radio' == type || 'checkbox' == type) ? $(f[0]) : f;
  888. }
  889. break;
  890. default:
  891. $fields = this.$invalidFields;
  892. break;
  893. }
  894. $fields.each(function() {
  895. messages = messages.concat(
  896. $(this)
  897. .data('bv.messages')
  898. .find('.help-block[data-bv-for="' + $(this).attr('data-bv-field') + '"][data-bv-result="' + that.STATUS_INVALID + '"]')
  899. .map(function() {
  900. return $(this).html()
  901. })
  902. .get()
  903. );
  904. });
  905. return messages;
  906. },
  907. /**
  908. * Add new field element
  909. *
  910. * @param {jQuery} $field The field element
  911. * @param {Object} options The field options
  912. * @returns {BootstrapValidator}
  913. */
  914. addFieldElement: function($field, options) {
  915. var field = $field.attr('name') || $field.attr('data-bv-field'),
  916. type = $field.attr('type'),
  917. isNewField = !this._cacheFields[field];
  918. // Update cache
  919. if (!isNewField && this._cacheFields[field].index($field) == -1) {
  920. this._cacheFields[field] = this._cacheFields[field].add($field);
  921. }
  922. if ('checkbox' == type || 'radio' == type || isNewField) {
  923. this._initField(field);
  924. } else {
  925. this._initFieldElement($field);
  926. }
  927. return this;
  928. },
  929. /**
  930. * Remove given field element
  931. *
  932. * @param {jQuery} $field The field element
  933. * @returns {BootstrapValidator}
  934. */
  935. removeFieldElement: function($field) {
  936. var field = $field.attr('name') || $field.attr('data-bv-field'),
  937. type = $field.attr('type'),
  938. index = this._cacheFields[field].index($field);
  939. (index == -1) ? (delete this._cacheFields[field]) : this._cacheFields[field].splice(index, 1);
  940. // Remove from the list of invalid fields
  941. index = this.$invalidFields.index($field);
  942. if (index != -1) {
  943. this.$invalidFields.splice(index, 1);
  944. }
  945. if ('checkbox' == type || 'radio' == type) {
  946. this._initField(field);
  947. }
  948. return this;
  949. },
  950. /**
  951. * Reset the form
  952. *
  953. * @param {Boolean} resetFormData Reset current form data
  954. * @return {BootstrapValidator}
  955. */
  956. resetForm: function(resetFormData) {
  957. var field, fields, total, type, validator;
  958. for (field in this.options.fields) {
  959. fields = this.getFieldElements(field);
  960. total = fields.length;
  961. for (var i = 0; i < total; i++) {
  962. for (validator in this.options.fields[field].validators) {
  963. $(fields[i]).removeData('bv.dfs.' + validator);
  964. }
  965. }
  966. // Mark field as not validated yet
  967. this.updateStatus(field, this.STATUS_NOT_VALIDATED);
  968. if (resetFormData) {
  969. type = fields.attr('type');
  970. ('radio' == type || 'checkbox' == type) ? fields.removeAttr('checked').removeAttr('selected') : fields.val('');
  971. }
  972. }
  973. this.$invalidFields = $([]);
  974. this.$submitButton = null;
  975. // Enable submit buttons
  976. this.disableSubmitButtons(false);
  977. return this;
  978. },
  979. /**
  980. * Enable/Disable all validators to given field
  981. *
  982. * @param {String} field The field name
  983. * @param {Boolean} enabled Enable/Disable field validators
  984. * @returns {BootstrapValidator}
  985. */
  986. enableFieldValidators: function(field, enabled) {
  987. this.options.fields[field]['enabled'] = enabled;
  988. this.updateStatus(field, this.STATUS_NOT_VALIDATED);
  989. return this;
  990. }
  991. };
  992. // Plugin definition
  993. $.fn.bootstrapValidator = function(option) {
  994. var params = arguments;
  995. return this.each(function() {
  996. var $this = $(this),
  997. data = $this.data('bootstrapValidator'),
  998. options = 'object' == typeof option && option;
  999. if (!data) {
  1000. data = new BootstrapValidator(this, options);
  1001. $this.data('bootstrapValidator', data);
  1002. }
  1003. // Allow to call plugin method
  1004. if ('string' == typeof option) {
  1005. data[option].apply(data, Array.prototype.slice.call(params, 1));
  1006. }
  1007. });
  1008. };
  1009. // Available validators
  1010. $.fn.bootstrapValidator.validators = {};
  1011. $.fn.bootstrapValidator.Constructor = BootstrapValidator;
  1012. // Helper methods, which can be used in validator class
  1013. $.fn.bootstrapValidator.helpers = {
  1014. /**
  1015. * Validate a date
  1016. *
  1017. * @param {Number} year The full year in 4 digits
  1018. * @param {Number} month The month number
  1019. * @param {Number} day The day number
  1020. * @param {Boolean} [notInFuture] If true, the date must not be in the future
  1021. * @returns {Boolean}
  1022. */
  1023. date: function(year, month, day, notInFuture) {
  1024. if (year < 1000 || year > 9999 || month == 0 || month > 12) {
  1025. return false;
  1026. }
  1027. var numDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  1028. // Update the number of days in Feb of leap year
  1029. if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
  1030. numDays[1] = 29;
  1031. }
  1032. // Check the day
  1033. if (day < 0 || day > numDays[month - 1]) {
  1034. return false;
  1035. }
  1036. if (notInFuture === true) {
  1037. var currentDate = new Date(),
  1038. currentYear = currentDate.getFullYear(),
  1039. currentMonth = currentDate.getMonth(),
  1040. currentDay = currentDate.getDate();
  1041. return (year < currentYear
  1042. || (year == currentYear && month - 1 < currentMonth)
  1043. || (year == currentYear && month - 1 == currentMonth && day < currentDay));
  1044. }
  1045. return true;
  1046. },
  1047. /**
  1048. * Implement Luhn validation algorithm
  1049. * Credit to https://gist.github.com/ShirtlessKirk/2134376
  1050. *
  1051. * @see http://en.wikipedia.org/wiki/Luhn
  1052. * @param {String} value
  1053. * @returns {Boolean}
  1054. */
  1055. luhn: function(value) {
  1056. var length = value.length,
  1057. mul = 0,
  1058. prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
  1059. sum = 0;
  1060. while (length--) {
  1061. sum += prodArr[mul][parseInt(value.charAt(length), 10)];
  1062. mul ^= 1;
  1063. }
  1064. return (sum % 10 === 0 && sum > 0);
  1065. },
  1066. /**
  1067. * Implement modulus 11, 10 (ISO 7064) algorithm
  1068. *
  1069. * @param {String} value
  1070. * @returns {Boolean}
  1071. */
  1072. mod_11_10: function(value) {
  1073. var check = 5,
  1074. length = value.length;
  1075. for (var i = 0; i < length; i++) {
  1076. check = (((check || 10) * 2) % 11 + parseInt(value.charAt(i), 10)) % 10;
  1077. }
  1078. return (check == 1);
  1079. },
  1080. /**
  1081. * Implements Mod 37, 36 (ISO 7064) algorithm
  1082. * Usages:
  1083. * mod_37_36('A12425GABC1234002M')
  1084. * mod_37_36('002006673085', '0123456789')
  1085. *
  1086. * @param {String} value
  1087. * @param {String} alphabet
  1088. * @returns {Boolean}
  1089. */
  1090. mod_37_36: function(value, alphabet) {
  1091. alphabet = alphabet || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  1092. var modulus = alphabet.length,
  1093. length = value.length,
  1094. check = Math.floor(modulus / 2);
  1095. for (var i = 0; i < length; i++) {
  1096. check = (((check || modulus) * 2) % (modulus + 1) + alphabet.indexOf(value.charAt(i))) % modulus;
  1097. }
  1098. return (check == 1);
  1099. }
  1100. };
  1101. }(window.jQuery));