bootstrapValidator.js 48 KB

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