bootstrapValidator.js 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899
  1. /**
  2. * BootstrapValidator (http://bootstrapvalidator.com)
  3. * The best jQuery plugin to validate form fields. Designed to use with Bootstrap 3
  4. *
  5. * @author https://twitter.com/nghuuphuoc
  6. * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc
  7. * @license MIT
  8. */
  9. (function($) {
  10. var BootstrapValidator = function(form, options) {
  11. this.$form = $(form);
  12. this.options = $.extend({}, $.fn.bootstrapValidator.DEFAULT_OPTIONS, options);
  13. this.$invalidFields = $([]); // Array of invalid fields
  14. this.$submitButton = null; // The submit button which is clicked to submit form
  15. this.$hiddenButton = null;
  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. BootstrapValidator.prototype = {
  40. constructor: BootstrapValidator,
  41. /**
  42. * Init form
  43. */
  44. _init: function() {
  45. var that = this,
  46. options = {
  47. container: this.$form.attr('data-bv-container'),
  48. events: {
  49. formInit: this.$form.attr('data-bv-events-form-init'),
  50. formError: this.$form.attr('data-bv-events-form-error'),
  51. formSuccess: this.$form.attr('data-bv-events-form-success'),
  52. fieldAdded: this.$form.attr('data-bv-events-field-added'),
  53. fieldRemoved: this.$form.attr('data-bv-events-field-removed'),
  54. fieldInit: this.$form.attr('data-bv-events-field-init'),
  55. fieldError: this.$form.attr('data-bv-events-field-error'),
  56. fieldSuccess: this.$form.attr('data-bv-events-field-success'),
  57. fieldStatus: this.$form.attr('data-bv-events-field-status'),
  58. validatorError: this.$form.attr('data-bv-events-validator-error'),
  59. validatorSuccess: this.$form.attr('data-bv-events-validator-success')
  60. },
  61. excluded: this.$form.attr('data-bv-excluded'),
  62. feedbackIcons: {
  63. valid: this.$form.attr('data-bv-feedbackicons-valid'),
  64. invalid: this.$form.attr('data-bv-feedbackicons-invalid'),
  65. validating: this.$form.attr('data-bv-feedbackicons-validating')
  66. },
  67. group: this.$form.attr('data-bv-group'),
  68. live: this.$form.attr('data-bv-live'),
  69. message: this.$form.attr('data-bv-message'),
  70. onError: this.$form.attr('data-bv-onerror'),
  71. onSuccess: this.$form.attr('data-bv-onsuccess'),
  72. submitButtons: this.$form.attr('data-bv-submitbuttons'),
  73. threshold: this.$form.attr('data-bv-threshold'),
  74. trigger: this.$form.attr('data-bv-trigger'),
  75. verbose: this.$form.attr('data-bv-verbose'),
  76. fields: {}
  77. };
  78. this.$form
  79. // Disable client side validation in HTML 5
  80. .attr('novalidate', 'novalidate')
  81. .addClass(this.options.elementClass)
  82. // Disable the default submission first
  83. .on('submit.bv', function(e) {
  84. e.preventDefault();
  85. that.validate();
  86. })
  87. .on('click.bv', this.options.submitButtons, function() {
  88. that.$submitButton = $(this);
  89. // The user just click the submit button
  90. that._submitIfValid = true;
  91. })
  92. // Find all fields which have either "name" or "data-bv-field" attribute
  93. .find('[name], [data-bv-field]')
  94. .each(function() {
  95. var $field = $(this),
  96. field = $field.attr('name') || $field.attr('data-bv-field'),
  97. opts = that._parseOptions($field);
  98. if (opts) {
  99. $field.attr('data-bv-field', field);
  100. options.fields[field] = $.extend({}, opts, options.fields[field]);
  101. }
  102. });
  103. this.options = $.extend(true, this.options, options);
  104. // When pressing Enter on any field in the form, the first submit button will do its job.
  105. // The form then will be submitted.
  106. // I create a first hidden submit button
  107. this.$hiddenButton = $('<button/>')
  108. .attr('type', 'submit')
  109. .prependTo(this.$form)
  110. .addClass('bv-hidden-submit')
  111. .css({ display: 'none', width: 0, height: 0 });
  112. this.$form
  113. .on('click.bv', '[type="submit"]', function(e) {
  114. // Don't perform validation when clicking on the submit button/input
  115. // which aren't defined by the 'submitButtons' option
  116. var $button = $(e.target).eq(0);
  117. if (that.options.submitButtons && !$button.is(that.options.submitButtons) && !$button.is(that.$hiddenButton)) {
  118. that.$form.off('submit.bv').submit();
  119. }
  120. });
  121. for (var field in this.options.fields) {
  122. this._initField(field);
  123. }
  124. this.$form.trigger($.Event(this.options.events.formInit), {
  125. bv: this,
  126. options: this.options
  127. });
  128. // Prepare the events
  129. if (this.options.onSuccess) {
  130. this.$form.on(this.options.events.formSuccess, function(e) {
  131. $.fn.bootstrapValidator.helpers.call(that.options.onSuccess, [e]);
  132. });
  133. }
  134. if (this.options.onError) {
  135. this.$form.on(this.options.events.formError, function(e) {
  136. $.fn.bootstrapValidator.helpers.call(that.options.onError, [e]);
  137. });
  138. }
  139. },
  140. /**
  141. * Parse the validator options from HTML attributes
  142. *
  143. * @param {jQuery} $field The field element
  144. * @returns {Object}
  145. */
  146. _parseOptions: function($field) {
  147. var field = $field.attr('name') || $field.attr('data-bv-field'),
  148. validators = {},
  149. validator,
  150. v, // Validator name
  151. enabled,
  152. optionName,
  153. optionValue,
  154. html5AttrName,
  155. html5AttrMap;
  156. for (v in $.fn.bootstrapValidator.validators) {
  157. validator = $.fn.bootstrapValidator.validators[v];
  158. enabled = $field.attr('data-bv-' + v.toLowerCase()) + '';
  159. html5AttrMap = ('function' === typeof validator.enableByHtml5) ? validator.enableByHtml5($field) : null;
  160. if ((html5AttrMap && enabled !== 'false')
  161. || (html5AttrMap !== true && ('' === enabled || 'true' === enabled)))
  162. {
  163. // Try to parse the options via attributes
  164. validator.html5Attributes = $.extend({}, { message: 'message', onerror: 'onError', onsuccess: 'onSuccess' }, validator.html5Attributes);
  165. validators[v] = $.extend({}, html5AttrMap === true ? {} : html5AttrMap, validators[v]);
  166. for (html5AttrName in validator.html5Attributes) {
  167. optionName = validator.html5Attributes[html5AttrName];
  168. optionValue = $field.attr('data-bv-' + v.toLowerCase() + '-' + html5AttrName);
  169. if (optionValue) {
  170. if ('true' === optionValue) {
  171. optionValue = true;
  172. } else if ('false' === optionValue) {
  173. optionValue = false;
  174. }
  175. validators[v][optionName] = optionValue;
  176. }
  177. }
  178. }
  179. }
  180. var opts = {
  181. container: $field.attr('data-bv-container'),
  182. excluded: $field.attr('data-bv-excluded'),
  183. feedbackIcons: $field.attr('data-bv-feedbackicons'),
  184. group: $field.attr('data-bv-group'),
  185. message: $field.attr('data-bv-message'),
  186. onError: $field.attr('data-bv-onerror'),
  187. onStatus: $field.attr('data-bv-onstatus'),
  188. onSuccess: $field.attr('data-bv-onsuccess'),
  189. selector: $field.attr('data-bv-selector'),
  190. threshold: $field.attr('data-bv-threshold'),
  191. trigger: $field.attr('data-bv-trigger'),
  192. verbose: $field.attr('data-bv-verbose'),
  193. validators: validators
  194. },
  195. emptyOptions = $.isEmptyObject(opts), // Check if the field options are set using HTML attributes
  196. emptyValidators = $.isEmptyObject(validators); // Check if the field validators are set using HTML attributes
  197. if (!emptyValidators || (!emptyOptions && this.options.fields && this.options.fields[field])) {
  198. opts.validators = validators;
  199. return opts;
  200. } else {
  201. return null;
  202. }
  203. },
  204. /**
  205. * Init field
  206. *
  207. * @param {String|jQuery} field The field name or field element
  208. */
  209. _initField: function(field) {
  210. var fields = $([]);
  211. switch (typeof field) {
  212. case 'object':
  213. fields = field;
  214. field = field.attr('data-bv-field');
  215. break;
  216. case 'string':
  217. fields = this.getFieldElements(field);
  218. fields.attr('data-bv-field', field);
  219. break;
  220. default:
  221. break;
  222. }
  223. // We don't need to validate non-existing fields
  224. if (fields.length === 0) {
  225. return;
  226. }
  227. if (this.options.fields[field] === null || this.options.fields[field].validators === null) {
  228. return;
  229. }
  230. var validatorName;
  231. for (validatorName in this.options.fields[field].validators) {
  232. if (!$.fn.bootstrapValidator.validators[validatorName]) {
  233. delete this.options.fields[field].validators[validatorName];
  234. }
  235. }
  236. if (this.options.fields[field].enabled === null) {
  237. this.options.fields[field].enabled = true;
  238. }
  239. var that = this,
  240. total = fields.length,
  241. type = fields.attr('type'),
  242. updateAll = (total === 1) || ('radio' === type) || ('checkbox' === type),
  243. event = ('radio' === type || 'checkbox' === type || 'file' === type || 'SELECT' === fields.eq(0).get(0).tagName) ? 'change' : this._changeEvent,
  244. trigger = (this.options.fields[field].trigger || this.options.trigger || event).split(' '),
  245. events = $.map(trigger, function(item) {
  246. return item + '.update.bv';
  247. }).join(' ');
  248. for (var i = 0; i < total; i++) {
  249. var $field = fields.eq(i),
  250. group = this.options.fields[field].group || this.options.group,
  251. $parent = $field.parents(group),
  252. // Allow user to indicate where the error messages are shown
  253. container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container),
  254. $message = (container && container !== 'tooltip' && container !== 'popover') ? $(container) : this._getMessageContainer($field, group);
  255. if (container && container !== 'tooltip' && container !== 'popover') {
  256. $message.addClass('has-error');
  257. }
  258. // Remove all error messages and feedback icons
  259. $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]').remove();
  260. $parent.find('i[data-bv-icon-for="' + field + '"]').remove();
  261. // Whenever the user change the field value, mark it as not validated yet
  262. $field.off(events).on(events, function() {
  263. that.updateStatus($(this), that.STATUS_NOT_VALIDATED);
  264. });
  265. // Create help block elements for showing the error messages
  266. $field.data('bv.messages', $message);
  267. for (validatorName in this.options.fields[field].validators) {
  268. $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
  269. if (!updateAll || i === total - 1) {
  270. $('<small/>')
  271. .css('display', 'none')
  272. .addClass('help-block')
  273. .attr('data-bv-validator', validatorName)
  274. .attr('data-bv-for', field)
  275. .attr('data-bv-result', this.STATUS_NOT_VALIDATED)
  276. .html(this._getMessage(field, validatorName))
  277. .appendTo($message);
  278. }
  279. // Init the validator
  280. if ('function' === typeof $.fn.bootstrapValidator.validators[validatorName].init) {
  281. $.fn.bootstrapValidator.validators[validatorName].init(this, $field, this.options.fields[field].validators[validatorName]);
  282. }
  283. // Prepare the validator events
  284. if (this.options.fields[field].validators[validatorName].onSuccess) {
  285. $field.on(this.options.events.validatorSuccess, function(e, data) {
  286. $.fn.bootstrapValidator.helpers.call(that.options.fields[field].validators[validatorName].onSuccess, [e, data]);
  287. });
  288. }
  289. if (this.options.fields[field].validators[validatorName].onError) {
  290. $field.on(this.options.events.validatorError, function(e, data) {
  291. $.fn.bootstrapValidator.helpers.call(that.options.fields[field].validators[validatorName].onError, [e, data]);
  292. });
  293. }
  294. }
  295. // Prepare the feedback icons
  296. // Available from Bootstrap 3.1 (http://getbootstrap.com/css/#forms-control-validation)
  297. if (this.options.fields[field].feedbackIcons !== false && this.options.fields[field].feedbackIcons !== 'false'
  298. && this.options.feedbackIcons
  299. && this.options.feedbackIcons.validating && this.options.feedbackIcons.invalid && this.options.feedbackIcons.valid
  300. && (!updateAll || i === total - 1))
  301. {
  302. $parent.removeClass('has-success').removeClass('has-error').addClass('has-feedback');
  303. var $icon = $('<i/>')
  304. .css('display', 'none')
  305. .addClass('form-control-feedback')
  306. .attr('data-bv-icon-for', field)
  307. .insertAfter($field);
  308. // Place it after the container of checkbox/radio
  309. // so when clicking the icon, it doesn't effect to the checkbox/radio element
  310. if ('checkbox' === type || 'radio' === type) {
  311. var $fieldParent = $field.parent();
  312. if ($fieldParent.hasClass(type)) {
  313. $icon.insertAfter($fieldParent);
  314. } else if ($fieldParent.parent().hasClass(type)) {
  315. $icon.insertAfter($fieldParent.parent());
  316. }
  317. }
  318. // The feedback icon does not render correctly if there is no label
  319. // https://github.com/twbs/bootstrap/issues/12873
  320. if ($parent.find('label').length === 0) {
  321. $icon.css('top', 0);
  322. }
  323. // Fix feedback icons in input-group
  324. if ($parent.find('.input-group').length !== 0) {
  325. $icon.css({
  326. 'top': 0,
  327. 'z-index': 100
  328. }).insertAfter($parent.find('.input-group').eq(0));
  329. }
  330. }
  331. }
  332. // Prepare the events
  333. if (this.options.fields[field].onSuccess) {
  334. fields.on(this.options.events.fieldSuccess, function(e, data) {
  335. $.fn.bootstrapValidator.helpers.call(that.options.fields[field].onSuccess, [e, data]);
  336. });
  337. }
  338. if (this.options.fields[field].onError) {
  339. fields.on(this.options.events.fieldError, function(e, data) {
  340. $.fn.bootstrapValidator.helpers.call(that.options.fields[field].onError, [e, data]);
  341. });
  342. }
  343. if (this.options.fields[field].onStatus) {
  344. fields.on(this.options.events.fieldStatus, function(e, data) {
  345. $.fn.bootstrapValidator.helpers.call(that.options.fields[field].onStatus, [e, data]);
  346. });
  347. }
  348. // Set live mode
  349. events = $.map(trigger, function(item) {
  350. return item + '.live.bv';
  351. }).join(' ');
  352. switch (this.options.live) {
  353. case 'submitted':
  354. break;
  355. case 'disabled':
  356. fields.off(events);
  357. break;
  358. case 'enabled':
  359. /* falls through */
  360. default:
  361. fields.off(events).on(events, function() {
  362. if (that._exceedThreshold($(this))) {
  363. that.validateField($(this));
  364. }
  365. });
  366. break;
  367. }
  368. fields.trigger($.Event(this.options.events.fieldInit), {
  369. bv: this,
  370. field: field,
  371. element: fields
  372. });
  373. },
  374. /**
  375. * Get the error message for given field and validator
  376. *
  377. * @param {String} field The field name
  378. * @param {String} validatorName The validator name
  379. * @returns {String}
  380. */
  381. _getMessage: function(field, validatorName) {
  382. if (!this.options.fields[field] || !$.fn.bootstrapValidator.validators[validatorName]
  383. || !this.options.fields[field].validators || !this.options.fields[field].validators[validatorName])
  384. {
  385. return '';
  386. }
  387. var options = this.options.fields[field].validators[validatorName];
  388. switch (true) {
  389. case (!!options.message):
  390. return options.message;
  391. case (!!this.options.fields[field].message):
  392. return this.options.fields[field].message;
  393. case (!!$.fn.bootstrapValidator.i18n[validatorName]):
  394. return $.fn.bootstrapValidator.i18n[validatorName]['default'];
  395. default:
  396. return this.options.message;
  397. }
  398. },
  399. /**
  400. * Get the element to place the error messages
  401. *
  402. * @param {jQuery} $field The field element
  403. * @param {String} group
  404. * @returns {jQuery}
  405. */
  406. _getMessageContainer: function($field, group) {
  407. var $parent = $field.parent();
  408. if ($parent.is(group)) {
  409. return $parent;
  410. }
  411. var cssClasses = $parent.attr('class');
  412. if (!cssClasses) {
  413. return this._getMessageContainer($parent, group);
  414. }
  415. cssClasses = cssClasses.split(' ');
  416. var n = cssClasses.length;
  417. for (var i = 0; i < n; i++) {
  418. if (/^col-(xs|sm|md|lg)-\d+$/.test(cssClasses[i]) || /^col-(xs|sm|md|lg)-offset-\d+$/.test(cssClasses[i])) {
  419. return $parent;
  420. }
  421. }
  422. return this._getMessageContainer($parent, group);
  423. },
  424. /**
  425. * Called when all validations are completed
  426. */
  427. _submit: function() {
  428. var isValid = this.isValid(),
  429. eventType = isValid ? this.options.events.formSuccess : this.options.events.formError,
  430. e = $.Event(eventType);
  431. this.$form.trigger(e);
  432. // Call default handler
  433. // Check if whether the submit button is clicked
  434. if (this.$submitButton) {
  435. isValid ? this._onSuccess(e) : this._onError(e);
  436. }
  437. },
  438. /**
  439. * Check if the field is excluded.
  440. * Returning true means that the field will not be validated
  441. *
  442. * @param {jQuery} $field The field element
  443. * @returns {Boolean}
  444. */
  445. _isExcluded: function($field) {
  446. var excludedAttr = $field.attr('data-bv-excluded'),
  447. // I still need to check the 'name' attribute while initializing the field
  448. field = $field.attr('data-bv-field') || $field.attr('name');
  449. switch (true) {
  450. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'true' || this.options.fields[field].excluded === true)):
  451. case (excludedAttr === 'true'):
  452. case (excludedAttr === ''):
  453. return true;
  454. case (!!field && this.options.fields && this.options.fields[field] && (this.options.fields[field].excluded === 'false' || this.options.fields[field].excluded === false)):
  455. case (excludedAttr === 'false'):
  456. return false;
  457. default:
  458. if (this.options.excluded) {
  459. // Convert to array first
  460. if ('string' === typeof this.options.excluded) {
  461. this.options.excluded = $.map(this.options.excluded.split(','), function(item) {
  462. // Trim the spaces
  463. return $.trim(item);
  464. });
  465. }
  466. var length = this.options.excluded.length;
  467. for (var i = 0; i < length; i++) {
  468. if (('string' === typeof this.options.excluded[i] && $field.is(this.options.excluded[i]))
  469. || ('function' === typeof this.options.excluded[i] && this.options.excluded[i].call(this, $field, this) === true))
  470. {
  471. return true;
  472. }
  473. }
  474. }
  475. return false;
  476. }
  477. },
  478. /**
  479. * Check if the number of characters of field value exceed the threshold or not
  480. *
  481. * @param {jQuery} $field The field element
  482. * @returns {Boolean}
  483. */
  484. _exceedThreshold: function($field) {
  485. var field = $field.attr('data-bv-field'),
  486. threshold = this.options.fields[field].threshold || this.options.threshold;
  487. if (!threshold) {
  488. return true;
  489. }
  490. var cannotType = $.inArray($field.attr('type'), ['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit']) !== -1;
  491. return (cannotType || $field.val().length >= threshold);
  492. },
  493. // ---
  494. // Events
  495. // ---
  496. /**
  497. * The default handler of error.form.bv event.
  498. * It will be called when there is a invalid field
  499. *
  500. * @param {jQuery.Event} e The jQuery event object
  501. */
  502. _onError: function(e) {
  503. if (e.isDefaultPrevented()) {
  504. return;
  505. }
  506. if ('submitted' === this.options.live) {
  507. // Enable live mode
  508. this.options.live = 'enabled';
  509. var that = this;
  510. for (var field in this.options.fields) {
  511. (function(f) {
  512. var fields = that.getFieldElements(f);
  513. if (fields.length) {
  514. var type = $(fields[0]).attr('type'),
  515. event = ('radio' === type || 'checkbox' === type || 'file' === type || 'SELECT' === $(fields[0]).get(0).tagName) ? 'change' : that._changeEvent,
  516. trigger = that.options.fields[field].trigger || that.options.trigger || event,
  517. events = $.map(trigger.split(' '), function(item) {
  518. return item + '.live.bv';
  519. }).join(' ');
  520. fields.off(events).on(events, function() {
  521. if (that._exceedThreshold($(this))) {
  522. that.validateField($(this));
  523. }
  524. });
  525. }
  526. })(field);
  527. }
  528. }
  529. var $invalidField = this.$invalidFields.eq(0);
  530. if ($invalidField) {
  531. // Activate the tab containing the invalid field if exists
  532. var $tabPane = $invalidField.parents('.tab-pane'), tabId;
  533. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  534. $('a[href="#' + tabId + '"][data-toggle="tab"]').tab('show');
  535. }
  536. // Focus to the first invalid field
  537. $invalidField.focus();
  538. }
  539. },
  540. /**
  541. * The default handler of success.form.bv event.
  542. * It will be called when all the fields are valid
  543. *
  544. * @param {jQuery.Event} e The jQuery event object
  545. */
  546. _onSuccess: function(e) {
  547. if (e.isDefaultPrevented()) {
  548. return;
  549. }
  550. // Submit the form
  551. this.disableSubmitButtons(true).defaultSubmit();
  552. },
  553. /**
  554. * Called after validating a field element
  555. *
  556. * @param {jQuery} $field The field element
  557. * @param {String} [validatorName] The validator name
  558. */
  559. _onFieldValidated: function($field, validatorName) {
  560. var field = $field.attr('data-bv-field'),
  561. validators = this.options.fields[field].validators,
  562. counter = {},
  563. numValidators = 0,
  564. data = {
  565. bv: this,
  566. field: field,
  567. element: $field,
  568. validator: validatorName
  569. };
  570. // Trigger an event after given validator completes
  571. if (validatorName) {
  572. switch ($field.data('bv.result.' + validatorName)) {
  573. case this.STATUS_INVALID:
  574. $field.trigger($.Event(this.options.events.validatorError), data);
  575. break;
  576. case this.STATUS_VALID:
  577. $field.trigger($.Event(this.options.events.validatorSuccess), data);
  578. break;
  579. default:
  580. break;
  581. }
  582. }
  583. counter[this.STATUS_NOT_VALIDATED] = 0;
  584. counter[this.STATUS_VALIDATING] = 0;
  585. counter[this.STATUS_INVALID] = 0;
  586. counter[this.STATUS_VALID] = 0;
  587. for (var v in validators) {
  588. if (validators[v].enabled === false) {
  589. continue;
  590. }
  591. numValidators++;
  592. var result = $field.data('bv.result.' + v);
  593. if (result) {
  594. counter[result]++;
  595. }
  596. }
  597. if (counter[this.STATUS_VALID] === numValidators) {
  598. // Remove from the list of invalid fields
  599. this.$invalidFields = this.$invalidFields.not($field);
  600. $field.trigger($.Event(this.options.events.fieldSuccess), data);
  601. }
  602. // If all validators are completed and there is at least one validator which doesn't pass
  603. else if (counter[this.STATUS_NOT_VALIDATED] === 0 && counter[this.STATUS_VALIDATING] === 0 && counter[this.STATUS_INVALID] > 0) {
  604. // Add to the list of invalid fields
  605. this.$invalidFields = this.$invalidFields.add($field);
  606. $field.trigger($.Event(this.options.events.fieldError), data);
  607. }
  608. },
  609. // ---
  610. // Public methods
  611. // ---
  612. /**
  613. * Retrieve the field elements by given name
  614. *
  615. * @param {String} field The field name
  616. * @returns {null|jQuery[]}
  617. */
  618. getFieldElements: function(field) {
  619. if (!this._cacheFields[field]) {
  620. this._cacheFields[field] = (this.options.fields[field] && this.options.fields[field].selector)
  621. ? $(this.options.fields[field].selector)
  622. : this.$form.find('[name="' + field + '"]');
  623. }
  624. return this._cacheFields[field];
  625. },
  626. /**
  627. * Disable/enable submit buttons
  628. *
  629. * @param {Boolean} disabled Can be true or false
  630. * @returns {BootstrapValidator}
  631. */
  632. disableSubmitButtons: function(disabled) {
  633. if (!disabled) {
  634. this.$form.find(this.options.submitButtons).removeAttr('disabled');
  635. } else if (this.options.live !== 'disabled') {
  636. // Don't disable if the live validating mode is disabled
  637. this.$form.find(this.options.submitButtons).attr('disabled', 'disabled');
  638. }
  639. return this;
  640. },
  641. /**
  642. * Validate the form
  643. *
  644. * @returns {BootstrapValidator}
  645. */
  646. validate: function() {
  647. if (!this.options.fields) {
  648. return this;
  649. }
  650. this.disableSubmitButtons(true);
  651. for (var field in this.options.fields) {
  652. this.validateField(field);
  653. }
  654. this._submit();
  655. return this;
  656. },
  657. /**
  658. * Validate given field
  659. *
  660. * @param {String|jQuery} field The field name or field element
  661. * @returns {BootstrapValidator}
  662. */
  663. validateField: function(field) {
  664. var fields = $([]);
  665. switch (typeof field) {
  666. case 'object':
  667. fields = field;
  668. field = field.attr('data-bv-field');
  669. break;
  670. case 'string':
  671. fields = this.getFieldElements(field);
  672. break;
  673. default:
  674. break;
  675. }
  676. if (fields.length === 0 || (this.options.fields[field] && this.options.fields[field].enabled === false)) {
  677. return this;
  678. }
  679. var that = this,
  680. type = fields.attr('type'),
  681. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  682. updateAll = ('radio' === type || 'checkbox' === type),
  683. validators = this.options.fields[field].validators,
  684. verbose = this.options.fields[field].verbose === 'true' || this.options.fields[field].verbose === true || this.options.verbose === 'true' || this.options.verbose === true,
  685. validatorName,
  686. validateResult;
  687. for (var i = 0; i < total; i++) {
  688. var $field = fields.eq(i);
  689. if (this._isExcluded($field)) {
  690. continue;
  691. }
  692. var stop = false;
  693. for (validatorName in validators) {
  694. if ($field.data('bv.dfs.' + validatorName)) {
  695. $field.data('bv.dfs.' + validatorName).reject();
  696. }
  697. if (stop) {
  698. break;
  699. }
  700. // Don't validate field if it is already done
  701. var result = $field.data('bv.result.' + validatorName);
  702. if (result === this.STATUS_VALID || result === this.STATUS_INVALID) {
  703. this._onFieldValidated($field, validatorName);
  704. continue;
  705. } else if (validators[validatorName].enabled === false) {
  706. this.updateStatus(updateAll ? field : $field, this.STATUS_VALID, validatorName);
  707. continue;
  708. }
  709. $field.data('bv.result.' + validatorName, this.STATUS_VALIDATING);
  710. validateResult = $.fn.bootstrapValidator.validators[validatorName].validate(this, $field, validators[validatorName]);
  711. // validateResult can be a $.Deferred object ...
  712. if ('object' === typeof validateResult && validateResult.resolve) {
  713. this.updateStatus(updateAll ? field : $field, this.STATUS_VALIDATING, validatorName);
  714. $field.data('bv.dfs.' + validatorName, validateResult);
  715. validateResult.done(function($f, v, isValid, message) {
  716. // v is validator name
  717. $f.removeData('bv.dfs.' + v);
  718. if (message) {
  719. that.updateMessage($f, v, message);
  720. }
  721. that.updateStatus(updateAll ? $f.attr('data-bv-field') : $f, isValid ? that.STATUS_VALID : that.STATUS_INVALID, v);
  722. if (isValid && that._submitIfValid === true) {
  723. // If a remote validator returns true and the form is ready to submit, then do it
  724. that._submit();
  725. } else if (!isValid && !verbose) {
  726. stop = true;
  727. }
  728. });
  729. }
  730. // ... or object { valid: true/false, message: 'dynamic message' }
  731. else if ('object' === typeof validateResult && validateResult.valid !== undefined && validateResult.message !== undefined) {
  732. this.updateMessage(updateAll ? field : $field, validatorName, validateResult.message);
  733. this.updateStatus(updateAll ? field : $field, validateResult.valid ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  734. if (!validateResult.valid && !verbose) {
  735. break;
  736. }
  737. }
  738. // ... or a boolean value
  739. else if ('boolean' === typeof validateResult) {
  740. this.updateStatus(updateAll ? field : $field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
  741. if (!validateResult && !verbose) {
  742. break;
  743. }
  744. }
  745. }
  746. }
  747. return this;
  748. },
  749. /**
  750. * Update the error message
  751. *
  752. * @param {String|jQuery} field The field name or field element
  753. * @param {String} validator The validator name
  754. * @param {String} message The message
  755. * @returns {BootstrapValidator}
  756. */
  757. updateMessage: function(field, validator, message) {
  758. var $fields = $([]);
  759. switch (typeof field) {
  760. case 'object':
  761. $fields = field;
  762. field = field.attr('data-bv-field');
  763. break;
  764. case 'string':
  765. $fields = this.getFieldElements(field);
  766. break;
  767. default:
  768. break;
  769. }
  770. $fields.each(function() {
  771. $(this).data('bv.messages').find('.help-block[data-bv-validator="' + validator + '"][data-bv-for="' + field + '"]').html(message);
  772. });
  773. },
  774. /**
  775. * Update all validating results of field
  776. *
  777. * @param {String|jQuery} field The field name or field element
  778. * @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
  779. * @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators
  780. * @returns {BootstrapValidator}
  781. */
  782. updateStatus: function(field, status, validatorName) {
  783. var fields = $([]);
  784. switch (typeof field) {
  785. case 'object':
  786. fields = field;
  787. field = field.attr('data-bv-field');
  788. break;
  789. case 'string':
  790. fields = this.getFieldElements(field);
  791. break;
  792. default:
  793. break;
  794. }
  795. if (status === this.STATUS_NOT_VALIDATED) {
  796. // Reset the flag
  797. this._submitIfValid = false;
  798. }
  799. var that = this,
  800. type = fields.attr('type'),
  801. group = this.options.fields[field].group || this.options.group,
  802. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  803. for (var i = 0; i < total; i++) {
  804. var $field = fields.eq(i);
  805. if (this._isExcluded($field)) {
  806. continue;
  807. }
  808. var $parent = $field.parents(group),
  809. $message = $field.data('bv.messages'),
  810. $allErrors = $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]'),
  811. $errors = validatorName ? $allErrors.filter('[data-bv-validator="' + validatorName + '"]') : $allErrors,
  812. $icon = $parent.find('.form-control-feedback[data-bv-icon-for="' + field + '"]'),
  813. container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container),
  814. isValidField = null;
  815. // Update status
  816. if (validatorName) {
  817. $field.data('bv.result.' + validatorName, status);
  818. } else {
  819. for (var v in this.options.fields[field].validators) {
  820. $field.data('bv.result.' + v, status);
  821. }
  822. }
  823. // Show/hide error elements and feedback icons
  824. $errors.attr('data-bv-result', status);
  825. // Determine the tab containing the element
  826. var $tabPane = $field.parents('.tab-pane'),
  827. tabId, $tab;
  828. if ($tabPane && (tabId = $tabPane.attr('id'))) {
  829. $tab = $('a[href="#' + tabId + '"][data-toggle="tab"]').parent();
  830. }
  831. switch (status) {
  832. case this.STATUS_VALIDATING:
  833. isValidField = null;
  834. this.disableSubmitButtons(true);
  835. $parent.removeClass('has-success').removeClass('has-error');
  836. if ($icon) {
  837. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).addClass(this.options.feedbackIcons.validating).show();
  838. }
  839. if ($tab) {
  840. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  841. }
  842. break;
  843. case this.STATUS_INVALID:
  844. isValidField = false;
  845. this.disableSubmitButtons(true);
  846. $parent.removeClass('has-success').addClass('has-error');
  847. if ($icon) {
  848. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.invalid).show();
  849. }
  850. if ($tab) {
  851. $tab.removeClass('bv-tab-success').addClass('bv-tab-error');
  852. }
  853. break;
  854. case this.STATUS_VALID:
  855. // If the field is valid (passes all validators)
  856. isValidField = ($allErrors.filter('[data-bv-result="' + this.STATUS_NOT_VALIDATED +'"]').length === 0)
  857. ? ($allErrors.filter('[data-bv-result="' + this.STATUS_VALID +'"]').length === $allErrors.length) // All validators are completed
  858. : null; // There are some validators that have not done
  859. if (isValidField !== null) {
  860. this.disableSubmitButtons(this.$submitButton ? !this.isValid() : !isValidField);
  861. if ($icon) {
  862. $icon
  863. .removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).removeClass(this.options.feedbackIcons.valid)
  864. .addClass(isValidField ? this.options.feedbackIcons.valid : this.options.feedbackIcons.invalid)
  865. .show();
  866. }
  867. }
  868. $parent.removeClass('has-error has-success').addClass(this.isValidContainer($parent) ? 'has-success' : 'has-error');
  869. if ($tab) {
  870. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error').addClass(this.isValidContainer($tabPane) ? 'bv-tab-success' : 'bv-tab-error');
  871. }
  872. break;
  873. case this.STATUS_NOT_VALIDATED:
  874. /* falls through */
  875. default:
  876. isValidField = null;
  877. this.disableSubmitButtons(false);
  878. $parent.removeClass('has-success').removeClass('has-error');
  879. if ($icon) {
  880. $icon.removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).hide();
  881. }
  882. if ($tab) {
  883. $tab.removeClass('bv-tab-success').removeClass('bv-tab-error');
  884. }
  885. break;
  886. }
  887. switch (true) {
  888. // Only show the first error message if it is placed inside a tooltip ...
  889. case ($icon && 'tooltip' === container):
  890. (isValidField === false)
  891. ? $icon.css('cursor', 'pointer').tooltip('destroy').tooltip({
  892. container: 'body',
  893. html: true,
  894. placement: 'top',
  895. title: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html()
  896. })
  897. : $icon.css('cursor', '').tooltip('destroy');
  898. break;
  899. // ... or popover
  900. case ($icon && 'popover' === container):
  901. (isValidField === false)
  902. ? $icon.css('cursor', 'pointer').popover('destroy').popover({
  903. container: 'body',
  904. content: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html(),
  905. html: true,
  906. placement: 'top',
  907. trigger: 'hover click'
  908. })
  909. : $icon.css('cursor', '').popover('destroy');
  910. break;
  911. default:
  912. (status === this.STATUS_INVALID) ? $errors.show() : $errors.hide();
  913. break;
  914. }
  915. // Trigger an event
  916. $field.trigger($.Event(this.options.events.fieldStatus), {
  917. bv: this,
  918. field: field,
  919. element: $field,
  920. status: status
  921. });
  922. this._onFieldValidated($field, validatorName);
  923. }
  924. return this;
  925. },
  926. /**
  927. * Check the form validity
  928. *
  929. * @returns {Boolean}
  930. */
  931. isValid: function() {
  932. for (var field in this.options.fields) {
  933. if (!this.isValidField(field)) {
  934. return false;
  935. }
  936. }
  937. return true;
  938. },
  939. /**
  940. * Check if the field is valid or not
  941. *
  942. * @param {String|jQuery} field The field name or field element
  943. * @returns {Boolean}
  944. */
  945. isValidField: function(field) {
  946. var fields = $([]);
  947. switch (typeof field) {
  948. case 'object':
  949. fields = field;
  950. field = field.attr('data-bv-field');
  951. break;
  952. case 'string':
  953. fields = this.getFieldElements(field);
  954. break;
  955. default:
  956. break;
  957. }
  958. if (fields.length === 0 || this.options.fields[field] === null || this.options.fields[field].enabled === false) {
  959. return true;
  960. }
  961. var type = fields.attr('type'),
  962. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length,
  963. $field, validatorName, status;
  964. for (var i = 0; i < total; i++) {
  965. $field = fields.eq(i);
  966. if (this._isExcluded($field)) {
  967. continue;
  968. }
  969. for (validatorName in this.options.fields[field].validators) {
  970. if (this.options.fields[field].validators[validatorName].enabled === false) {
  971. continue;
  972. }
  973. status = $field.data('bv.result.' + validatorName);
  974. if (status !== this.STATUS_VALID) {
  975. return false;
  976. }
  977. }
  978. }
  979. return true;
  980. },
  981. /**
  982. * Check if all fields inside a given container are valid.
  983. * It's useful when working with a wizard-like such as tab, collapse
  984. *
  985. * @param {String|jQuery} container The container selector or element
  986. * @returns {Boolean}
  987. */
  988. isValidContainer: function(container) {
  989. var that = this,
  990. map = {},
  991. $container = ('string' === typeof container) ? $(container) : container;
  992. if ($container.length === 0) {
  993. return true;
  994. }
  995. $container.find('[data-bv-field]').each(function() {
  996. var $field = $(this),
  997. field = $field.attr('data-bv-field');
  998. if (!that._isExcluded($field) && !map[field]) {
  999. map[field] = $field;
  1000. }
  1001. });
  1002. for (var field in map) {
  1003. var $f = map[field];
  1004. if ($f.data('bv.messages')
  1005. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]')
  1006. .filter('[data-bv-result="' + this.STATUS_INVALID +'"]')
  1007. .length > 0)
  1008. {
  1009. return false;
  1010. }
  1011. }
  1012. return true;
  1013. },
  1014. /**
  1015. * Submit the form using default submission.
  1016. * It also does not perform any validations when submitting the form
  1017. */
  1018. defaultSubmit: function() {
  1019. if (this.$submitButton) {
  1020. // Create hidden input to send the submit buttons
  1021. $('<input/>')
  1022. .attr('type', 'hidden')
  1023. .attr('data-bv-submit-hidden', '')
  1024. .attr('name', this.$submitButton.attr('name'))
  1025. .val(this.$submitButton.val())
  1026. .appendTo(this.$form);
  1027. }
  1028. // Submit form
  1029. this.$form.off('submit.bv').submit();
  1030. },
  1031. // ---
  1032. // Useful APIs which aren't used internally
  1033. // ---
  1034. /**
  1035. * Get the list of invalid fields
  1036. *
  1037. * @returns {jQuery[]}
  1038. */
  1039. getInvalidFields: function() {
  1040. return this.$invalidFields;
  1041. },
  1042. /**
  1043. * Returns the clicked submit button
  1044. *
  1045. * @returns {jQuery}
  1046. */
  1047. getSubmitButton: function() {
  1048. return this.$submitButton;
  1049. },
  1050. /**
  1051. * Get the error messages
  1052. *
  1053. * @param {String|jQuery} [field] The field name or field element
  1054. * If the field is not defined, the method returns all error messages of all fields
  1055. * @param {String} [validator] The name of validator
  1056. * If the validator is not defined, the method returns error messages of all validators
  1057. * @returns {String[]}
  1058. */
  1059. getMessages: function(field, validator) {
  1060. var that = this,
  1061. messages = [],
  1062. $fields = $([]);
  1063. switch (true) {
  1064. case (field && 'object' === typeof field):
  1065. $fields = field;
  1066. break;
  1067. case (field && 'string' === typeof field):
  1068. var f = this.getFieldElements(field);
  1069. if (f.length > 0) {
  1070. var type = f.attr('type');
  1071. $fields = ('radio' === type || 'checkbox' === type) ? f.eq(0) : f;
  1072. }
  1073. break;
  1074. default:
  1075. $fields = this.$invalidFields;
  1076. break;
  1077. }
  1078. var filter = validator ? '[data-bv-validator="' + validator + '"]' : '';
  1079. $fields.each(function() {
  1080. messages = messages.concat(
  1081. $(this)
  1082. .data('bv.messages')
  1083. .find('.help-block[data-bv-for="' + $(this).attr('data-bv-field') + '"][data-bv-result="' + that.STATUS_INVALID + '"]' + filter)
  1084. .map(function() {
  1085. var v = $(this).attr('data-bv-validator'),
  1086. f = $(this).attr('data-bv-for');
  1087. return (that.options.fields[f].validators[v].enabled === false) ? '' : $(this).html();
  1088. })
  1089. .get()
  1090. );
  1091. });
  1092. return messages;
  1093. },
  1094. /**
  1095. * Get the field options
  1096. *
  1097. * @param {String|jQuery} [field] The field name or field element. If it is not set, the method returns the form options
  1098. * @param {String} [validator] The name of validator. It null, the method returns form options
  1099. * @param {String} [option] The option name
  1100. * @return {String|Object}
  1101. */
  1102. getOptions: function(field, validator, option) {
  1103. if (!field) {
  1104. return this.options;
  1105. }
  1106. if ('object' === typeof field) {
  1107. field = field.attr('data-bv-field');
  1108. }
  1109. if (!this.options.fields[field]) {
  1110. return null;
  1111. }
  1112. var options = this.options.fields[field];
  1113. if (!validator) {
  1114. return options;
  1115. }
  1116. if (!options.validators || !options.validators[validator]) {
  1117. return null;
  1118. }
  1119. return option ? options.validators[validator][option] : options.validators[validator];
  1120. },
  1121. /**
  1122. * Update the option of a specific validator
  1123. *
  1124. * @param {String|jQuery} field The field name or field element
  1125. * @param {String} validator The validator name
  1126. * @param {String} option The option name
  1127. * @param {String} value The value to set
  1128. * @returns {BootstrapValidator}
  1129. */
  1130. updateOption: function(field, validator, option, value) {
  1131. if ('object' === typeof field) {
  1132. field = field.attr('data-bv-field');
  1133. }
  1134. if (this.options.fields[field] && this.options.fields[field].validators[validator]) {
  1135. this.options.fields[field].validators[validator][option] = value;
  1136. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validator);
  1137. }
  1138. return this;
  1139. },
  1140. /**
  1141. * Add a new field
  1142. *
  1143. * @param {String|jQuery} field The field name or field element
  1144. * @param {Object} [options] The validator rules
  1145. * @returns {BootstrapValidator}
  1146. */
  1147. addField: function(field, options) {
  1148. var fields = $([]);
  1149. switch (typeof field) {
  1150. case 'object':
  1151. fields = field;
  1152. field = field.attr('data-bv-field') || field.attr('name');
  1153. break;
  1154. case 'string':
  1155. delete this._cacheFields[field];
  1156. fields = this.getFieldElements(field);
  1157. break;
  1158. default:
  1159. break;
  1160. }
  1161. fields.attr('data-bv-field', field);
  1162. var type = fields.attr('type'),
  1163. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1164. for (var i = 0; i < total; i++) {
  1165. var $field = fields.eq(i);
  1166. // Try to parse the options from HTML attributes
  1167. var opts = this._parseOptions($field);
  1168. opts = (opts === null) ? options : $.extend(true, options, opts);
  1169. this.options.fields[field] = $.extend(true, this.options.fields[field], opts);
  1170. // Update the cache
  1171. this._cacheFields[field] = this._cacheFields[field] ? this._cacheFields[field].add($field) : $field;
  1172. // Init the element
  1173. this._initField(('checkbox' === type || 'radio' === type) ? field : $field);
  1174. }
  1175. this.disableSubmitButtons(false);
  1176. // Trigger an event
  1177. this.$form.trigger($.Event(this.options.events.fieldAdded), {
  1178. field: field,
  1179. element: fields,
  1180. options: this.options.fields[field]
  1181. });
  1182. return this;
  1183. },
  1184. /**
  1185. * Remove a given field
  1186. *
  1187. * @param {String|jQuery} field The field name or field element
  1188. * @returns {BootstrapValidator}
  1189. */
  1190. removeField: function(field) {
  1191. var fields = $([]);
  1192. switch (typeof field) {
  1193. case 'object':
  1194. fields = field;
  1195. field = field.attr('data-bv-field') || field.attr('name');
  1196. fields.attr('data-bv-field', field);
  1197. break;
  1198. case 'string':
  1199. fields = this.getFieldElements(field);
  1200. break;
  1201. default:
  1202. break;
  1203. }
  1204. if (fields.length === 0) {
  1205. return this;
  1206. }
  1207. var type = fields.attr('type'),
  1208. total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length;
  1209. for (var i = 0; i < total; i++) {
  1210. var $field = fields.eq(i);
  1211. // Remove from the list of invalid fields
  1212. this.$invalidFields = this.$invalidFields.not($field);
  1213. // Update the cache
  1214. this._cacheFields[field] = this._cacheFields[field].not($field);
  1215. }
  1216. if (!this._cacheFields[field] || this._cacheFields[field].length === 0) {
  1217. delete this.options.fields[field];
  1218. }
  1219. if ('checkbox' === type || 'radio' === type) {
  1220. this._initField(field);
  1221. }
  1222. this.disableSubmitButtons(false);
  1223. // Trigger an event
  1224. this.$form.trigger($.Event(this.options.events.fieldRemoved), {
  1225. field: field,
  1226. element: fields
  1227. });
  1228. return this;
  1229. },
  1230. /**
  1231. * Reset given field
  1232. *
  1233. * @param {String|jQuery} field The field name or field element
  1234. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1235. * @returns {BootstrapValidator}
  1236. */
  1237. resetField: function(field, resetValue) {
  1238. var $fields = $([]);
  1239. switch (typeof field) {
  1240. case 'object':
  1241. $fields = field;
  1242. field = field.attr('data-bv-field');
  1243. break;
  1244. case 'string':
  1245. $fields = this.getFieldElements(field);
  1246. break;
  1247. default:
  1248. break;
  1249. }
  1250. var total = $fields.length;
  1251. if (this.options.fields[field]) {
  1252. for (var i = 0; i < total; i++) {
  1253. for (var validator in this.options.fields[field].validators) {
  1254. $fields.eq(i).removeData('bv.dfs.' + validator);
  1255. }
  1256. }
  1257. }
  1258. // Mark field as not validated yet
  1259. this.updateStatus(field, this.STATUS_NOT_VALIDATED);
  1260. if (resetValue) {
  1261. var type = $fields.attr('type');
  1262. ('radio' === type || 'checkbox' === type) ? $fields.removeAttr('checked').removeAttr('selected') : $fields.val('');
  1263. }
  1264. return this;
  1265. },
  1266. /**
  1267. * Reset the form
  1268. *
  1269. * @param {Boolean} [resetValue] If true, the method resets field value to empty or remove checked/selected attribute (for radio/checkbox)
  1270. * @returns {BootstrapValidator}
  1271. */
  1272. resetForm: function(resetValue) {
  1273. for (var field in this.options.fields) {
  1274. this.resetField(field, resetValue);
  1275. }
  1276. this.$invalidFields = $([]);
  1277. this.$submitButton = null;
  1278. // Enable submit buttons
  1279. this.disableSubmitButtons(false);
  1280. return this;
  1281. },
  1282. /**
  1283. * Revalidate given field
  1284. * It's used when you need to revalidate the field which its value is updated by other plugin
  1285. *
  1286. * @param {String|jQuery} field The field name of field element
  1287. * @returns {BootstrapValidator}
  1288. */
  1289. revalidateField: function(field) {
  1290. this.updateStatus(field, this.STATUS_NOT_VALIDATED)
  1291. .validateField(field);
  1292. return this;
  1293. },
  1294. /**
  1295. * Enable/Disable all validators to given field
  1296. *
  1297. * @param {String} field The field name
  1298. * @param {Boolean} enabled Enable/Disable field validators
  1299. * @param {String} [validatorName] The validator name. If null, all validators will be enabled/disabled
  1300. * @returns {BootstrapValidator}
  1301. */
  1302. enableFieldValidators: function(field, enabled, validatorName) {
  1303. var validators = this.options.fields[field].validators;
  1304. // Enable/disable particular validator
  1305. if (validatorName
  1306. && validators
  1307. && validators[validatorName] && validators[validatorName].enabled !== enabled)
  1308. {
  1309. this.options.fields[field].validators[validatorName].enabled = enabled;
  1310. this.updateStatus(field, this.STATUS_NOT_VALIDATED, validatorName);
  1311. }
  1312. // Enable/disable all validators
  1313. else if (!validatorName && this.options.fields[field].enabled !== enabled) {
  1314. this.options.fields[field].enabled = enabled;
  1315. for (var v in validators) {
  1316. this.enableFieldValidators(field, enabled, v);
  1317. }
  1318. }
  1319. return this;
  1320. },
  1321. /**
  1322. * Some validators have option which its value is dynamic.
  1323. * For example, the zipCode validator has the country option which might be changed dynamically by a select element.
  1324. *
  1325. * @param {jQuery|String} field The field name or element
  1326. * @param {String|Function} option The option which can be determined by:
  1327. * - a string
  1328. * - name of field which defines the value
  1329. * - name of function which returns the value
  1330. * - a function returns the value
  1331. *
  1332. * The callback function has the format of
  1333. * callback: function(value, validator, $field) {
  1334. * // value is the value of field
  1335. * // validator is the BootstrapValidator instance
  1336. * // $field is the field element
  1337. * }
  1338. *
  1339. * @returns {String}
  1340. */
  1341. getDynamicOption: function(field, option) {
  1342. var $field = ('string' === typeof field) ? this.getFieldElements(field) : field,
  1343. value = $field.val();
  1344. // Option can be determined by
  1345. // ... a function
  1346. if ('function' === typeof option) {
  1347. return $.fn.bootstrapValidator.helpers.call(option, [value, this, $field]);
  1348. }
  1349. // ... value of other field
  1350. else if ('string' === typeof option) {
  1351. var $f = this.getFieldElements(option);
  1352. if ($f.length) {
  1353. return $f.val();
  1354. }
  1355. // ... return value of callback
  1356. else {
  1357. return $.fn.bootstrapValidator.helpers.call(option, [value, this, $field]) || option;
  1358. }
  1359. }
  1360. return null;
  1361. },
  1362. /**
  1363. * Destroy the plugin
  1364. * It will remove all error messages, feedback icons and turn off the events
  1365. */
  1366. destroy: function() {
  1367. var field, fields, $field, validator, $icon, group;
  1368. for (field in this.options.fields) {
  1369. fields = this.getFieldElements(field);
  1370. group = this.options.fields[field].group || this.options.group;
  1371. for (var i = 0; i < fields.length; i++) {
  1372. $field = fields.eq(i);
  1373. $field
  1374. // Remove all error messages
  1375. .data('bv.messages')
  1376. .find('.help-block[data-bv-validator][data-bv-for="' + field + '"]').remove().end()
  1377. .end()
  1378. .removeData('bv.messages')
  1379. // Remove feedback classes
  1380. .parents(group)
  1381. .removeClass('has-feedback has-error has-success')
  1382. .end()
  1383. // Turn off events
  1384. .off('.bv')
  1385. .removeAttr('data-bv-field');
  1386. // Remove feedback icons, tooltip/popover container
  1387. $icon = $field.parents(group).find('i[data-bv-icon-for="' + field + '"]');
  1388. if ($icon) {
  1389. var container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container);
  1390. switch (container) {
  1391. case 'tooltip':
  1392. $icon.tooltip('destroy').remove();
  1393. break;
  1394. case 'popover':
  1395. $icon.popover('destroy').remove();
  1396. break;
  1397. default:
  1398. $icon.remove();
  1399. break;
  1400. }
  1401. }
  1402. for (validator in this.options.fields[field].validators) {
  1403. if ($field.data('bv.dfs.' + validator)) {
  1404. $field.data('bv.dfs.' + validator).reject();
  1405. }
  1406. $field.removeData('bv.result.' + validator).removeData('bv.dfs.' + validator);
  1407. // Destroy the validator
  1408. if ('function' === typeof $.fn.bootstrapValidator.validators[validator].destroy) {
  1409. $.fn.bootstrapValidator.validators[validator].destroy(this, $field, this.options.fields[field].validators[validator]);
  1410. }
  1411. }
  1412. }
  1413. }
  1414. this.disableSubmitButtons(false); // Enable submit buttons
  1415. this.$hiddenButton.remove(); // Remove the hidden button
  1416. this.$form
  1417. .removeClass(this.options.elementClass)
  1418. .off('.bv')
  1419. .removeData('bootstrapValidator')
  1420. // Remove generated hidden elements
  1421. .find('[data-bv-submit-hidden]').remove().end()
  1422. .find('[type="submit"]').off('click.bv');
  1423. }
  1424. };
  1425. // Plugin definition
  1426. $.fn.bootstrapValidator = function(option) {
  1427. var params = arguments;
  1428. return this.each(function() {
  1429. var $this = $(this),
  1430. data = $this.data('bootstrapValidator'),
  1431. options = 'object' === typeof option && option;
  1432. if (!data) {
  1433. data = new BootstrapValidator(this, options);
  1434. $this.data('bootstrapValidator', data);
  1435. }
  1436. // Allow to call plugin method
  1437. if ('string' === typeof option) {
  1438. data[option].apply(data, Array.prototype.slice.call(params, 1));
  1439. }
  1440. });
  1441. };
  1442. // The default options
  1443. $.fn.bootstrapValidator.DEFAULT_OPTIONS = {
  1444. // The form CSS class
  1445. elementClass: 'bv-form',
  1446. // Default invalid message
  1447. message: 'This value is not valid',
  1448. // The CSS selector for indicating the element consists the field
  1449. // By default, each field is placed inside the <div class="form-group"></div>
  1450. // You should adjust this option if your form group consists of many fields which not all of them need to be validated
  1451. group: '.form-group',
  1452. //The error messages container. It can be:
  1453. // - 'tooltip' if you want to use Bootstrap tooltip to show error messages
  1454. // - 'popover' if you want to use Bootstrap popover to show error messages
  1455. // - a CSS selector indicating the container
  1456. // In the first two cases, since the tooltip/popover should be small enough, the plugin only shows only one error message
  1457. // You also can define the message container for particular field
  1458. container: null,
  1459. // The field will not be live validated if its length is less than this number of characters
  1460. threshold: null,
  1461. // Indicate fields which won't be validated
  1462. // By default, the plugin will not validate the following kind of fields:
  1463. // - disabled
  1464. // - hidden
  1465. // - invisible
  1466. //
  1467. // The setting consists of jQuery filters. Accept 3 formats:
  1468. // - A string. Use a comma to separate filter
  1469. // - An array. Each element is a filter
  1470. // - An array. Each element can be a callback function
  1471. // function($field, validator) {
  1472. // $field is jQuery object representing the field element
  1473. // validator is the BootstrapValidator instance
  1474. // return true or false;
  1475. // }
  1476. //
  1477. // The 3 following settings are equivalent:
  1478. //
  1479. // 1) ':disabled, :hidden, :not(:visible)'
  1480. // 2) [':disabled', ':hidden', ':not(:visible)']
  1481. // 3) [':disabled', ':hidden', function($field) {
  1482. // return !$field.is(':visible');
  1483. // }]
  1484. excluded: [':disabled', ':hidden', ':not(:visible)'],
  1485. // Shows ok/error/loading icons based on the field validity.
  1486. // This feature requires Bootstrap v3.1.0 or later (http://getbootstrap.com/css/#forms-control-validation).
  1487. // Since Bootstrap doesn't provide any methods to know its version, this option cannot be on/off automatically.
  1488. // In other word, to use this feature you have to upgrade your Bootstrap to v3.1.0 or later.
  1489. //
  1490. // Examples:
  1491. // - Use Glyphicons icons:
  1492. // feedbackIcons: {
  1493. // valid: 'glyphicon glyphicon-ok',
  1494. // invalid: 'glyphicon glyphicon-remove',
  1495. // validating: 'glyphicon glyphicon-refresh'
  1496. // }
  1497. // - Use FontAwesome icons:
  1498. // feedbackIcons: {
  1499. // valid: 'fa fa-check',
  1500. // invalid: 'fa fa-times',
  1501. // validating: 'fa fa-refresh'
  1502. // }
  1503. feedbackIcons: {
  1504. valid: null,
  1505. invalid: null,
  1506. validating: null
  1507. },
  1508. // The submit buttons selector
  1509. // These buttons will be disabled to prevent the valid form from multiple submissions
  1510. submitButtons: '[type="submit"]',
  1511. // Live validating option
  1512. // Can be one of 3 values:
  1513. // - enabled: The plugin validates fields as soon as they are changed
  1514. // - disabled: Disable the live validating. The error messages are only shown after the form is submitted
  1515. // - submitted: The live validating is enabled after the form is submitted
  1516. live: 'enabled',
  1517. // Map the field name with validator rules
  1518. fields: null,
  1519. // Use custom event name to avoid window.onerror being invoked by jQuery
  1520. // See https://github.com/nghuuphuoc/bootstrapvalidator/issues/630
  1521. events: {
  1522. formInit: 'init.form.bv',
  1523. formError: 'error.form.bv',
  1524. formSuccess: 'success.form.bv',
  1525. fieldAdded: 'added.field.bv',
  1526. fieldRemoved: 'removed.field.bv',
  1527. fieldInit: 'init.field.bv',
  1528. fieldError: 'error.field.bv',
  1529. fieldSuccess: 'success.field.bv',
  1530. fieldStatus: 'status.field.bv',
  1531. validatorError: 'error.validator.bv',
  1532. validatorSuccess: 'success.validator.bv'
  1533. },
  1534. // Whether to be verbose when validating a field or not.
  1535. // Possible values:
  1536. // - true: when a field has multiple validators, all of them will be checked, and respectively - if errors occur in
  1537. // multiple validators, all of them will be displayed to the user
  1538. // - false: when a field has multiple validators, validation for this field will be terminated upon the first encountered error.
  1539. // Thus, only the very first error message related to this field will be displayed to the user
  1540. verbose: true
  1541. };
  1542. // Available validators
  1543. $.fn.bootstrapValidator.validators = {};
  1544. // i18n
  1545. $.fn.bootstrapValidator.i18n = {};
  1546. $.fn.bootstrapValidator.Constructor = BootstrapValidator;
  1547. // Helper methods, which can be used in validator class
  1548. $.fn.bootstrapValidator.helpers = {
  1549. /**
  1550. * Execute a callback function
  1551. *
  1552. * @param {String|Function} functionName Can be
  1553. * - name of global function
  1554. * - name of namespace function (such as A.B.C)
  1555. * - a function
  1556. * @param {Array} args The callback arguments
  1557. */
  1558. call: function(functionName, args) {
  1559. if ('function' === typeof functionName) {
  1560. return functionName.apply(this, args);
  1561. } else if ('string' === typeof functionName) {
  1562. if ('()' === functionName.substring(functionName.length - 2)) {
  1563. functionName = functionName.substring(0, functionName.length - 2);
  1564. }
  1565. var ns = functionName.split('.'),
  1566. func = ns.pop(),
  1567. context = window;
  1568. for (var i = 0; i < ns.length; i++) {
  1569. context = context[ns[i]];
  1570. }
  1571. return (typeof context[func] === 'undefined') ? null : context[func].apply(this, args);
  1572. }
  1573. },
  1574. /**
  1575. * Format a string
  1576. * It's used to format the error message
  1577. * format('The field must between %s and %s', [10, 20]) = 'The field must between 10 and 20'
  1578. *
  1579. * @param {String} message
  1580. * @param {Array} parameters
  1581. * @returns {String}
  1582. */
  1583. format: function(message, parameters) {
  1584. if (!$.isArray(parameters)) {
  1585. parameters = [parameters];
  1586. }
  1587. for (var i in parameters) {
  1588. message = message.replace('%s', parameters[i]);
  1589. }
  1590. return message;
  1591. },
  1592. /**
  1593. * Validate a date
  1594. *
  1595. * @param {Number} year The full year in 4 digits
  1596. * @param {Number} month The month number
  1597. * @param {Number} day The day number
  1598. * @param {Boolean} [notInFuture] If true, the date must not be in the future
  1599. * @returns {Boolean}
  1600. */
  1601. date: function(year, month, day, notInFuture) {
  1602. if (isNaN(year) || isNaN(month) || isNaN(day)) {
  1603. return false;
  1604. }
  1605. if (day.length > 2 || month.length > 2 || year.length > 4) {
  1606. return false;
  1607. }
  1608. day = parseInt(day, 10);
  1609. month = parseInt(month, 10);
  1610. year = parseInt(year, 10);
  1611. if (year < 1000 || year > 9999 || month <= 0 || month > 12) {
  1612. return false;
  1613. }
  1614. var numDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  1615. // Update the number of days in Feb of leap year
  1616. if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
  1617. numDays[1] = 29;
  1618. }
  1619. // Check the day
  1620. if (day <= 0 || day > numDays[month - 1]) {
  1621. return false;
  1622. }
  1623. if (notInFuture === true) {
  1624. var currentDate = new Date(),
  1625. currentYear = currentDate.getFullYear(),
  1626. currentMonth = currentDate.getMonth(),
  1627. currentDay = currentDate.getDate();
  1628. return (year < currentYear
  1629. || (year === currentYear && month - 1 < currentMonth)
  1630. || (year === currentYear && month - 1 === currentMonth && day < currentDay));
  1631. }
  1632. return true;
  1633. },
  1634. /**
  1635. * Implement Luhn validation algorithm
  1636. * Credit to https://gist.github.com/ShirtlessKirk/2134376
  1637. *
  1638. * @see http://en.wikipedia.org/wiki/Luhn
  1639. * @param {String} value
  1640. * @returns {Boolean}
  1641. */
  1642. luhn: function(value) {
  1643. var length = value.length,
  1644. mul = 0,
  1645. prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
  1646. sum = 0;
  1647. while (length--) {
  1648. sum += prodArr[mul][parseInt(value.charAt(length), 10)];
  1649. mul ^= 1;
  1650. }
  1651. return (sum % 10 === 0 && sum > 0);
  1652. },
  1653. /**
  1654. * Implement modulus 11, 10 (ISO 7064) algorithm
  1655. *
  1656. * @param {String} value
  1657. * @returns {Boolean}
  1658. */
  1659. mod11And10: function(value) {
  1660. var check = 5,
  1661. length = value.length;
  1662. for (var i = 0; i < length; i++) {
  1663. check = (((check || 10) * 2) % 11 + parseInt(value.charAt(i), 10)) % 10;
  1664. }
  1665. return (check === 1);
  1666. },
  1667. /**
  1668. * Implements Mod 37, 36 (ISO 7064) algorithm
  1669. * Usages:
  1670. * mod37And36('A12425GABC1234002M')
  1671. * mod37And36('002006673085', '0123456789')
  1672. *
  1673. * @param {String} value
  1674. * @param {String} [alphabet]
  1675. * @returns {Boolean}
  1676. */
  1677. mod37And36: function(value, alphabet) {
  1678. alphabet = alphabet || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  1679. var modulus = alphabet.length,
  1680. length = value.length,
  1681. check = Math.floor(modulus / 2);
  1682. for (var i = 0; i < length; i++) {
  1683. check = (((check || modulus) * 2) % (modulus + 1) + alphabet.indexOf(value.charAt(i))) % modulus;
  1684. }
  1685. return (check === 1);
  1686. }
  1687. };
  1688. }(window.jQuery));