bootstrapValidator.js 21 KB

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