bootstrapValidator.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. /**
  2. * BootstrapValidator (https://github.com/nghuuphuoc/bootstrapvalidator)
  3. *
  4. * A jQuery plugin to validate form fields. 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.dfds = {}; // Array of deferred
  15. this.results = {}; // Validating results
  16. this.invalidField = null; // First invalid field
  17. this.$submitButton = null; // The submit button which is clicked to submit form
  18. this._init();
  19. this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED';
  20. this.STATUS_VALIDATING = 'VALIDATING';
  21. this.STATUS_INVALID = 'INVALID';
  22. this.STATUS_VALID = 'VALID';
  23. };
  24. // The default options
  25. BootstrapValidator.DEFAULT_OPTIONS = {
  26. // The form CSS class
  27. elementClass: 'bootstrap-validator-form',
  28. // Default invalid message
  29. message: 'This value is not valid',
  30. // Shows ok/error/loading icons based on the field validity.
  31. // This feature requires Bootstrap v3.1.0 or later (http://getbootstrap.com/css/#forms-control-validation).
  32. // Since Bootstrap doesn't provide any methods to know its version, this option cannot be on/off automatically.
  33. // In other word, to use this feature you have to upgrade your Bootstrap to v3.1.0 or later.
  34. //
  35. // Examples:
  36. // - Use Glyphicons icons:
  37. // feedbackIcons: {
  38. // valid: 'glyphicon glyphicon-ok',
  39. // invalid: 'glyphicon glyphicon-remove',
  40. // validating: 'glyphicon glyphicon-refresh'
  41. // }
  42. // - Use FontAwesome icons:
  43. // feedbackIcons: {
  44. // valid: 'fa fa-check',
  45. // invalid: 'fa fa-times',
  46. // validating: 'fa fa-refresh'
  47. // }
  48. feedbackIcons: null,
  49. // The submit buttons selector
  50. // These buttons will be disabled to prevent the valid form from multiple submissions
  51. submitButtons: 'button[type="submit"]',
  52. // The custom submit handler
  53. // It will prevent the form from the default submission
  54. //
  55. // submitHandler: function(validator, form) {
  56. // - validator is the BootstrapValidator instance
  57. // - form is the jQuery object present the current form
  58. // }
  59. submitHandler: null,
  60. // Live validating option
  61. // Can be one of 3 values:
  62. // - enabled: The plugin validates fields as soon as they are changed
  63. // - disabled: Disable the live validating. The error messages are only shown after the form is submitted
  64. // - submitted: The live validating is enabled after the form is submitted
  65. live: 'enabled',
  66. // Map the field name with validator rules
  67. fields: null
  68. };
  69. BootstrapValidator.prototype = {
  70. constructor: BootstrapValidator,
  71. /**
  72. * Init form
  73. */
  74. _init: function() {
  75. if (this.options.fields == null) {
  76. return;
  77. }
  78. var that = this;
  79. this.$form
  80. // Disable client side validation in HTML 5
  81. .attr('novalidate', 'novalidate')
  82. .addClass(this.options.elementClass)
  83. // Disable the default submission first
  84. .on('submit.bootstrapValidator', function(e) {
  85. e.preventDefault();
  86. that.validate();
  87. })
  88. .find(this.options.submitButtons)
  89. .on('click', function() {
  90. that.$submitButton = $(this);
  91. });
  92. for (var field in this.options.fields) {
  93. this._initField(field);
  94. }
  95. this._setLiveValidating();
  96. },
  97. /**
  98. * Init field
  99. *
  100. * @param {String} field The field name
  101. */
  102. _initField: function(field) {
  103. if (this.options.fields[field] == null || this.options.fields[field].validators == null) {
  104. return;
  105. }
  106. this.dfds[field] = {};
  107. this.results[field] = {};
  108. var fields = this.getFieldElements(field);
  109. // We don't need to validate non-existing fields
  110. if (fields == null) {
  111. delete this.options.fields[field];
  112. delete this.dfds[field];
  113. return;
  114. }
  115. // Create help block elements for showing the error messages
  116. var $field = $(fields[0]),
  117. $parent = $field.parents('.form-group'),
  118. $message = this._getMessageContainer($field);
  119. $field.data('bootstrapValidator.messageContainer', $message);
  120. for (var validatorName in this.options.fields[field].validators) {
  121. if (!$.fn.bootstrapValidator.validators[validatorName]) {
  122. delete this.options.fields[field].validators[validatorName];
  123. continue;
  124. }
  125. this.results[field][validatorName] = this.STATUS_NOT_VALIDATED;
  126. $('<small/>')
  127. .css('display', 'none')
  128. .attr('data-bs-validator', validatorName)
  129. .html(this.options.fields[field].validators[validatorName].message || this.options.message)
  130. .addClass('help-block')
  131. .appendTo($message);
  132. }
  133. // Prepare the feedback icons
  134. // Available from Bootstrap 3.1 (http://getbootstrap.com/css/#forms-control-validation)
  135. if (this.options.feedbackIcons) {
  136. $parent.addClass('has-feedback');
  137. var $icon = $('<i/>').css('display', 'none').addClass('form-control-feedback').insertAfter($(fields[fields.length - 1]));
  138. // The feedback icon does not render correctly if there is no label
  139. // https://github.com/twbs/bootstrap/issues/12873
  140. if ($parent.find('label').length == 0) {
  141. $icon.css('top', 0);
  142. }
  143. }
  144. if (this.options.fields[field]['enabled'] == null) {
  145. this.options.fields[field]['enabled'] = true;
  146. }
  147. // Whenever the user change the field value, mark it as not validated yet
  148. var that = this,
  149. type = fields.attr('type'),
  150. event = ('radio' == type || 'checkbox' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup';
  151. fields.on(event, function() {
  152. that.setNotValidated(field);
  153. });
  154. },
  155. /**
  156. * Get the element to place the error messages
  157. *
  158. * @param {jQuery} $field The field element
  159. * @returns {jQuery}
  160. */
  161. _getMessageContainer: function($field) {
  162. var $parent = $field.parent();
  163. if ($parent.hasClass('form-group')) {
  164. return $parent;
  165. }
  166. var cssClasses = $parent.attr('class');
  167. if (!cssClasses) {
  168. return this._getMessageContainer($parent);
  169. }
  170. cssClasses = cssClasses.split(' ');
  171. var n = cssClasses.length;
  172. for (var i = 0; i < n; i++) {
  173. if (/^col-(xs|sm|md|lg)-\d+$/.test(cssClasses[i]) || /^col-(xs|sm|md|lg)-offset-\d+$/.test(cssClasses[i])) {
  174. return $parent;
  175. }
  176. }
  177. return this._getMessageContainer($parent);
  178. },
  179. /**
  180. * Enable live validating
  181. */
  182. _setLiveValidating: function() {
  183. if ('enabled' == this.options.live) {
  184. var that = this;
  185. for (var field in this.options.fields) {
  186. (function(f) {
  187. var fields = that.getFieldElements(f);
  188. if (fields) {
  189. var type = fields.attr('type'),
  190. event = ('radio' == type || 'checkbox' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup';
  191. fields.on(event, function() {
  192. that.validateField(f);
  193. });
  194. }
  195. })(field);
  196. }
  197. }
  198. },
  199. /**
  200. * Disable/Enable submit buttons
  201. *
  202. * @param {Boolean} disabled
  203. */
  204. _disableSubmitButtons: function(disabled) {
  205. if (!disabled) {
  206. this.$form.find(this.options.submitButtons).removeAttr('disabled');
  207. } else if (this.options.live != 'disabled') {
  208. // Don't disable if the live validating mode is disabled
  209. this.$form.find(this.options.submitButtons).attr('disabled', 'disabled');
  210. }
  211. },
  212. /**
  213. * Called when all validations are completed
  214. */
  215. _submit: function() {
  216. if (!this.isValid()) {
  217. if ('submitted' == this.options.live) {
  218. this.options.live = 'enabled';
  219. this._setLiveValidating();
  220. }
  221. // Focus to the first invalid field
  222. if (this.invalidField) {
  223. this.getFieldElements(this.invalidField).focus();
  224. }
  225. return;
  226. }
  227. this._disableSubmitButtons(true);
  228. // Call the custom submission if enabled
  229. if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) {
  230. this.options.submitHandler.call(this, this, this.$form, this.$submitButton);
  231. } else {
  232. // Submit form
  233. this.$form.off('submit.bootstrapValidator').submit();
  234. }
  235. },
  236. // --- Public methods ---
  237. /**
  238. * Retrieve the field elements by given name
  239. *
  240. * @param {String} field The field name
  241. * @returns {null|jQuery[]}
  242. */
  243. getFieldElements: function(field) {
  244. var fields = this.$form.find('[name="' + field + '"]');
  245. return (fields.length == 0) ? null : fields;
  246. },
  247. /**
  248. * Validate the form
  249. *
  250. * @return {BootstrapValidator}
  251. */
  252. validate: function() {
  253. if (!this.options.fields) {
  254. return this;
  255. }
  256. this._disableSubmitButtons(true);
  257. for (var field in this.options.fields) {
  258. this.validateField(field);
  259. }
  260. this._submit();
  261. return this;
  262. },
  263. /**
  264. * Validate given field
  265. *
  266. * @param {String} field The field name
  267. */
  268. validateField: function(field) {
  269. if (!this.options.fields[field]['enabled']) {
  270. return;
  271. }
  272. var that = this,
  273. fields = this.getFieldElements(field),
  274. $field = $(fields[0]),
  275. validators = this.options.fields[field].validators,
  276. validatorName,
  277. validateResult;
  278. // We don't need to validate disabled field
  279. if (fields.length == 1 && fields.is(':disabled')) {
  280. delete this.options.fields[field];
  281. delete this.dfds[field];
  282. return;
  283. }
  284. for (validatorName in validators) {
  285. if (this.dfds[field][validatorName]) {
  286. this.dfds[field][validatorName].reject();
  287. }
  288. // Don't validate field if it is already done
  289. if (this.results[field][validatorName] == this.STATUS_VALID || this.results[field][validatorName] == this.STATUS_INVALID) {
  290. continue;
  291. }
  292. this.results[field][validatorName] = this.STATUS_VALIDATING;
  293. validateResult = $.fn.bootstrapValidator.validators[validatorName].validate(this, $field, validators[validatorName]);
  294. if ('object' == typeof validateResult) {
  295. this.updateStatus($field, this.STATUS_VALIDATING, validatorName);
  296. this.dfds[field][validatorName] = validateResult;
  297. validateResult.done(function(isValid, v) {
  298. // v is validator name
  299. delete that.dfds[field][v];
  300. that.updateStatus($field, isValid ? that.STATUS_VALID : that.STATUS_INVALID, v);
  301. if (isValid && 'disabled' == that.options.live) {
  302. that._submit();
  303. }
  304. });
  305. } else if ('boolean' == typeof validateResult) {
  306. this.updateStatus($field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  307. }
  308. }
  309. },
  310. /**
  311. * Check the form validity
  312. *
  313. * @returns {Boolean}
  314. */
  315. isValid: function() {
  316. var field, validatorName;
  317. for (field in this.results) {
  318. if (!this.options.fields[field]['enabled']) {
  319. continue;
  320. }
  321. for (validatorName in this.results[field]) {
  322. if (this.results[field][validatorName] == this.STATUS_NOT_VALIDATED || this.results[field][validatorName] == this.STATUS_VALIDATING) {
  323. return false;
  324. }
  325. if (this.results[field][validatorName] == this.STATUS_INVALID) {
  326. this.invalidField = field;
  327. return false;
  328. }
  329. }
  330. }
  331. return true;
  332. },
  333. /**
  334. * Update field status
  335. *
  336. * @param {jQuery} $field The field element
  337. * @param {String} validatorName The validator name
  338. * @param {String} status The status
  339. * Can be STATUS_VALIDATING, STATUS_INVALID, STATUS_VALID
  340. * @return {BootstrapValidator}
  341. */
  342. updateStatus: function($field, status, validatorName) {
  343. var that = this,
  344. field = $field.attr('name'),
  345. $parent = $field.parents('.form-group'),
  346. $message = $field.data('bootstrapValidator.messageContainer'),
  347. $errors = $message.find('.help-block[data-bs-validator]');
  348. switch (status) {
  349. case this.STATUS_VALIDATING:
  350. this.results[field][validatorName] = this.STATUS_VALIDATING;
  351. this._disableSubmitButtons(true);
  352. $parent.removeClass('has-success').removeClass('has-error');
  353. // TODO: Show validating message
  354. $errors.filter('.help-block[data-bs-validator="' + validatorName + '"]').hide();
  355. if (this.options.feedbackIcons) {
  356. // Show validating icon
  357. $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).addClass(this.options.feedbackIcons.validating).show();
  358. }
  359. break;
  360. case this.STATUS_INVALID:
  361. this.results[field][validatorName] = this.STATUS_INVALID;
  362. this._disableSubmitButtons(true);
  363. // Add has-error class to parent element
  364. $parent.removeClass('has-success').addClass('has-error');
  365. $errors.filter('[data-bs-validator="' + validatorName + '"]').show();
  366. if (this.options.feedbackIcons) {
  367. // Show invalid icon
  368. $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.invalid).show();
  369. }
  370. break;
  371. case this.STATUS_VALID:
  372. this.results[field][validatorName] = this.STATUS_VALID;
  373. // Hide error element
  374. $errors.filter('[data-bs-validator="' + validatorName + '"]').hide();
  375. // If the field is valid
  376. if ($errors.filter(function() {
  377. var display = $(this).css('display'), v = $(this).attr('data-bs-validator');
  378. return ('block' == display) || (that.results[field][v] != that.STATUS_VALID);
  379. }).length == 0
  380. ) {
  381. this._disableSubmitButtons(false);
  382. $parent.removeClass('has-error').addClass('has-success');
  383. // Show valid icon
  384. if (this.options.feedbackIcons) {
  385. $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.valid).show();
  386. }
  387. }
  388. break;
  389. default:
  390. break;
  391. }
  392. return this;
  393. },
  394. /**
  395. * Mark a field as not validated yet
  396. * The plugin doesn't re-validate a field if it is marked as valid.
  397. * In some cases, we need to force the plugin validate it again
  398. *
  399. * @param {String} field The field name
  400. * @returns {BootstrapValidator}
  401. */
  402. setNotValidated: function(field) {
  403. for (var v in this.options.fields[field].validators) {
  404. this.results[field][v] = this.STATUS_NOT_VALIDATED;
  405. }
  406. return this;
  407. },
  408. // Useful APIs which aren't used internally
  409. /**
  410. * Reset the form
  411. *
  412. * @param {Boolean} resetFormData Reset current form data
  413. * @return {BootstrapValidator}
  414. */
  415. resetForm: function(resetFormData) {
  416. for (var field in this.options.fields) {
  417. this.dfds[field] = {};
  418. this.results[field] = {};
  419. // Mark field as not validated yet
  420. this.setNotValidated(field);
  421. }
  422. this.invalidField = null;
  423. this.$submitButton = null;
  424. // Hide all error elements
  425. this.$form
  426. .find('.has-error').removeClass('has-error').end()
  427. .find('.has-success').removeClass('has-success').end()
  428. .find('.help-block[data-bs-validator]').hide();
  429. // Enable submit buttons
  430. this._disableSubmitButtons(false);
  431. // Hide all feedback icons
  432. if (this.options.feedbackIcons) {
  433. this.$form.find('.form-control-feedback').removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).hide();
  434. }
  435. if (resetFormData) {
  436. this.$form.get(0).reset();
  437. }
  438. return this;
  439. },
  440. /**
  441. * Enable/Disable all validators to given field
  442. *
  443. * @param {String} field The field name
  444. * @param {Boolean} enabled Enable/Disable field validators
  445. * @return {BootstrapValidator}
  446. */
  447. enableFieldValidators: function(field, enabled) {
  448. this.options.fields[field]['enabled'] = enabled;
  449. if (enabled) {
  450. this.setNotValidated(field);
  451. } else {
  452. var $field = this.getFieldElements(field),
  453. $message = $field.data('bootstrapValidator.messageContainer');
  454. $field.parents('.form-group').removeClass('has-success has-error');
  455. $message.find('.help-block[data-bs-validator]').hide();
  456. if (this.options.feedbackIcons) {
  457. $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).removeClass(this.options.feedbackIcons.valid).hide();
  458. }
  459. this._disableSubmitButtons(false);
  460. }
  461. return this;
  462. }
  463. };
  464. // Plugin definition
  465. $.fn.bootstrapValidator = function(options) {
  466. return this.each(function() {
  467. var $this = $(this), data = $this.data('bootstrapValidator');
  468. if (!data) {
  469. $this.data('bootstrapValidator', (data = new BootstrapValidator(this, options)));
  470. }
  471. if ('string' == typeof options) {
  472. data[options]();
  473. }
  474. });
  475. };
  476. // Available validators
  477. $.fn.bootstrapValidator.validators = {};
  478. $.fn.bootstrapValidator.Constructor = BootstrapValidator;
  479. }(window.jQuery));