bootstrap-validator-v0.9.0.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /* ========================================================================
  2. * Bootstrap (plugin): validator.js v0.9.0
  3. * ========================================================================
  4. * The MIT License (MIT)
  5. *
  6. * Copyright (c) 2015 Cina Saffary.
  7. * Made by @1000hz in the style of Bootstrap 3 era @fat
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. * ======================================================================== */
  27. +function ($) {
  28. 'use strict';
  29. // VALIDATOR CLASS DEFINITION
  30. // ==========================
  31. var Validator = function (element, options) {
  32. this.$element = $(element)
  33. this.options = options
  34. options.errors = $.extend({}, Validator.DEFAULTS.errors, options.errors)
  35. for (var custom in options.custom) {
  36. if (!options.errors[custom]) throw new Error('Missing default error message for custom validator: ' + custom)
  37. }
  38. $.extend(Validator.VALIDATORS, options.custom)
  39. this.$element.attr('novalidate', true) // disable automatic native validation
  40. this.toggleSubmit()
  41. this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.validateInput, this))
  42. this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this))
  43. this.$element.find('[data-match]').each(function () {
  44. var $this = $(this)
  45. var target = $this.data('match')
  46. $(target).on('input.bs.validator', function (e) {
  47. $this.val() && $this.trigger('input.bs.validator')
  48. })
  49. })
  50. }
  51. Validator.INPUT_SELECTOR = ':input:not([type="submit"], button):enabled:visible'
  52. Validator.DEFAULTS = {
  53. delay: 500,
  54. html: false,
  55. disable: true,
  56. custom: {},
  57. errors: {
  58. match: 'Does not match',
  59. minlength: 'Not long enough'
  60. },
  61. feedback: {
  62. success: 'glyphicon-ok',
  63. error: 'glyphicon-remove'
  64. }
  65. }
  66. Validator.VALIDATORS = {
  67. 'native': function ($el) {
  68. var el = $el[0]
  69. return el.checkValidity ? el.checkValidity() : true
  70. },
  71. 'match': function ($el) {
  72. var target = $el.data('match')
  73. return !$el.val() || $el.val() === $(target).val()
  74. },
  75. 'minlength': function ($el) {
  76. var minlength = $el.data('minlength')
  77. return !$el.val() || $el.val().length >= minlength
  78. }
  79. }
  80. Validator.prototype.validateInput = function (e) {
  81. var $el = $(e.target)
  82. var prevErrors = $el.data('bs.validator.errors')
  83. var errors
  84. if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]')
  85. this.$element.trigger(e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}))
  86. if (e.isDefaultPrevented()) return
  87. var self = this
  88. this.runValidators($el).done(function (errors) {
  89. $el.data('bs.validator.errors', errors)
  90. errors.length ? self.showErrors($el) : self.clearErrors($el)
  91. if (!prevErrors || errors.toString() !== prevErrors.toString()) {
  92. e = errors.length
  93. ? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors})
  94. : $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors})
  95. self.$element.trigger(e)
  96. }
  97. self.toggleSubmit()
  98. self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]}))
  99. })
  100. }
  101. Validator.prototype.runValidators = function ($el) {
  102. var errors = []
  103. var deferred = $.Deferred()
  104. var options = this.options
  105. $el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject()
  106. $el.data('bs.validator.deferred', deferred)
  107. function getErrorMessage(key) {
  108. return $el.data(key + '-error')
  109. || $el.data('error')
  110. || key == 'native' && $el[0].validationMessage
  111. || options.errors[key]
  112. }
  113. $.each(Validator.VALIDATORS, $.proxy(function (key, validator) {
  114. if (($el.data(key) || key == 'native') && !validator.call(this, $el)) {
  115. var error = getErrorMessage(key)
  116. !~errors.indexOf(error) && errors.push(error)
  117. }
  118. }, this))
  119. if (!errors.length && $el.val() && $el.data('remote')) {
  120. this.defer($el, function () {
  121. var data = {}
  122. data[$el.attr('name')] = $el.val()
  123. $.get($el.data('remote'), data)
  124. .fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) })
  125. .always(function () { deferred.resolve(errors)})
  126. })
  127. } else deferred.resolve(errors)
  128. return deferred.promise()
  129. }
  130. Validator.prototype.validate = function () {
  131. var delay = this.options.delay
  132. this.options.delay = 0
  133. this.$element.find(Validator.INPUT_SELECTOR).trigger('input.bs.validator')
  134. this.options.delay = delay
  135. return this
  136. }
  137. Validator.prototype.showErrors = function ($el) {
  138. var method = this.options.html ? 'html' : 'text'
  139. this.defer($el, function () {
  140. var $group = $el.closest('.form-group')
  141. var $block = $group.find('.help-block.with-errors')
  142. var $feedback = $group.find('.form-control-feedback')
  143. var errors = $el.data('bs.validator.errors')
  144. if (!errors.length) return
  145. errors = $('<ul/>')
  146. .addClass('list-unstyled')
  147. .append($.map(errors, function (error) { return $('<li/>')[method](error) }))
  148. $block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html())
  149. $block.empty().append(errors)
  150. $group.addClass('has-error')
  151. $feedback.length
  152. && $feedback.removeClass(this.options.feedback.success)
  153. && $feedback.addClass(this.options.feedback.error)
  154. && $group.removeClass('has-success')
  155. })
  156. }
  157. Validator.prototype.clearErrors = function ($el) {
  158. var $group = $el.closest('.form-group')
  159. var $block = $group.find('.help-block.with-errors')
  160. var $feedback = $group.find('.form-control-feedback')
  161. $block.html($block.data('bs.validator.originalContent'))
  162. $group.removeClass('has-error')
  163. $feedback.length
  164. && $feedback.removeClass(this.options.feedback.error)
  165. && $feedback.addClass(this.options.feedback.success)
  166. && $group.addClass('has-success')
  167. }
  168. Validator.prototype.hasErrors = function () {
  169. function fieldErrors() {
  170. return !!($(this).data('bs.validator.errors') || []).length
  171. }
  172. return !!this.$element.find(Validator.INPUT_SELECTOR).filter(fieldErrors).length
  173. }
  174. Validator.prototype.isIncomplete = function () {
  175. function fieldIncomplete() {
  176. return this.type === 'checkbox' ? !this.checked :
  177. this.type === 'radio' ? !$('[name="' + this.name + '"]:checked').length :
  178. $.trim(this.value) === ''
  179. }
  180. return !!this.$element.find(Validator.INPUT_SELECTOR).filter('[required]').filter(fieldIncomplete).length
  181. }
  182. Validator.prototype.onSubmit = function (e) {
  183. this.validate()
  184. if (this.isIncomplete() || this.hasErrors()) e.preventDefault()
  185. }
  186. Validator.prototype.toggleSubmit = function () {
  187. if(!this.options.disable) return
  188. var $btn = $('button[type="submit"], input[type="submit"]')
  189. .filter('[form="' + this.$element.attr('id') + '"]')
  190. .add(this.$element.find('input[type="submit"], button[type="submit"]'))
  191. $btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
  192. }
  193. Validator.prototype.defer = function ($el, callback) {
  194. callback = $.proxy(callback, this)
  195. if (!this.options.delay) return callback()
  196. window.clearTimeout($el.data('bs.validator.timeout'))
  197. $el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay))
  198. }
  199. Validator.prototype.destroy = function () {
  200. this.$element
  201. .removeAttr('novalidate')
  202. .removeData('bs.validator')
  203. .off('.bs.validator')
  204. this.$element.find(Validator.INPUT_SELECTOR)
  205. .off('.bs.validator')
  206. .removeData(['bs.validator.errors', 'bs.validator.deferred'])
  207. .each(function () {
  208. var $this = $(this)
  209. var timeout = $this.data('bs.validator.timeout')
  210. window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout')
  211. })
  212. this.$element.find('.help-block.with-errors').each(function () {
  213. var $this = $(this)
  214. var originalContent = $this.data('bs.validator.originalContent')
  215. $this
  216. .removeData('bs.validator.originalContent')
  217. .html(originalContent)
  218. })
  219. this.$element.find('input[type="submit"], button[type="submit"]').removeClass('disabled')
  220. this.$element.find('.has-error').removeClass('has-error')
  221. return this
  222. }
  223. // VALIDATOR PLUGIN DEFINITION
  224. // ===========================
  225. function Plugin(option) {
  226. return this.each(function () {
  227. var $this = $(this)
  228. var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option)
  229. var data = $this.data('bs.validator')
  230. if (!data && option == 'destroy') return
  231. if (!data) $this.data('bs.validator', (data = new Validator(this, options)))
  232. if (typeof option == 'string') data[option]()
  233. })
  234. }
  235. var old = $.fn.validator
  236. $.fn.validator = Plugin
  237. $.fn.validator.Constructor = Validator
  238. // VALIDATOR NO CONFLICT
  239. // =====================
  240. $.fn.validator.noConflict = function () {
  241. $.fn.validator = old
  242. return this
  243. }
  244. // VALIDATOR DATA-API
  245. // ==================
  246. $(window).on('load', function () {
  247. $('form[data-toggle="validator"]').each(function () {
  248. var $form = $(this)
  249. Plugin.call($form, $form.data())
  250. })
  251. })
  252. }(jQuery);