mirror of
				https://github.com/twbs/bootstrap.git
				synced 2025-11-04 00:03:15 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			599 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			599 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/** =======================================================================
 | 
						|
 * Bootstrap: modal.js v4.0.0
 | 
						|
 * http://getbootstrap.com/javascript/#modal
 | 
						|
 * ========================================================================
 | 
						|
 * Copyright 2011-2015 Twitter, Inc.
 | 
						|
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 | 
						|
 * ========================================================================
 | 
						|
 * @fileoverview - Bootstrap's modal plugin. Modals are streamlined, but
 | 
						|
 * flexible, dialog prompts with the minimum required functionality and
 | 
						|
 * smart defaults.
 | 
						|
 *
 | 
						|
 * Public Methods & Properties:
 | 
						|
 *
 | 
						|
 *   + $.modal
 | 
						|
 *   + $.modal.noConflict
 | 
						|
 *   + $.modal.Constructor
 | 
						|
 *   + $.modal.Constructor.VERSION
 | 
						|
 *   + $.modal.Constructor.Defaults
 | 
						|
 *   + $.modal.Constructor.Defaults.backdrop
 | 
						|
 *   + $.modal.Constructor.Defaults.keyboard
 | 
						|
 *   + $.modal.Constructor.Defaults.show
 | 
						|
 *   + $.modal.Constructor.prototype.toggle
 | 
						|
 *   + $.modal.Constructor.prototype.show
 | 
						|
 *   + $.modal.Constructor.prototype.hide
 | 
						|
 *
 | 
						|
 * ========================================================================
 | 
						|
 */
 | 
						|
 | 
						|
'use strict';
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Our modal class.
 | 
						|
 * @param {Element} element
 | 
						|
 * @param {Object} config
 | 
						|
 * @constructor
 | 
						|
 */
 | 
						|
var Modal = function (element, config) {
 | 
						|
 | 
						|
  /** @private {Object} */
 | 
						|
  this._config = config
 | 
						|
 | 
						|
  /** @private {Element} */
 | 
						|
  this._element = element
 | 
						|
 | 
						|
  /** @private {Element} */
 | 
						|
  this._backdrop = null
 | 
						|
 | 
						|
  /** @private {boolean} */
 | 
						|
  this._isShown = false
 | 
						|
 | 
						|
  /** @private {boolean} */
 | 
						|
  this._isBodyOverflowing = false
 | 
						|
 | 
						|
  /** @private {number} */
 | 
						|
  this._scrollbarWidth = 0
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {string}
 | 
						|
 */
 | 
						|
Modal['VERSION']  = '4.0.0'
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {Object}
 | 
						|
 */
 | 
						|
Modal['Defaults'] = {
 | 
						|
  'backdrop' : true,
 | 
						|
  'keyboard' : true,
 | 
						|
  'show'     : true
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {string}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._NAME = 'modal'
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {string}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._DATA_KEY = 'bs.modal'
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {number}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._TRANSITION_DURATION = 300
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {number}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._BACKDROP_TRANSITION_DURATION = 150
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {Function}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._JQUERY_NO_CONFLICT = $.fn[Modal._NAME]
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @enum {string}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._Event = {
 | 
						|
  HIDE   : 'hide.bs.modal',
 | 
						|
  HIDDEN : 'hidden.bs.modal',
 | 
						|
  SHOW   : 'show.bs.modal',
 | 
						|
  SHOWN  : 'shown.bs.modal'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @enum {string}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._ClassName = {
 | 
						|
  BACKDROP : 'modal-backdrop',
 | 
						|
  OPEN     : 'modal-open',
 | 
						|
  FADE     : 'fade',
 | 
						|
  IN       : 'in'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @enum {string}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._Selector = {
 | 
						|
  DIALOG             : '.modal-dialog',
 | 
						|
  DATA_TOGGLE        : '[data-toggle="modal"]',
 | 
						|
  DATA_DISMISS       : '[data-dismiss="modal"]',
 | 
						|
  SCROLLBAR_MEASURER : 'modal-scrollbar-measure'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Provides the jQuery Interface for the alert component.
 | 
						|
 * @param {Object|string=} opt_config
 | 
						|
 * @param {Element=} opt_relatedTarget
 | 
						|
 * @this {jQuery}
 | 
						|
 * @return {jQuery}
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal._jQueryInterface = function Plugin(opt_config, opt_relatedTarget) {
 | 
						|
  return this.each(function () {
 | 
						|
    var data   = $(this).data(Modal._DATA_KEY)
 | 
						|
    var config = $.extend({}, Modal['Defaults'], $(this).data(), typeof opt_config == 'object' && opt_config)
 | 
						|
 | 
						|
    if (!data) {
 | 
						|
      data = new Modal(this, config)
 | 
						|
      $(this).data(Modal._DATA_KEY, data)
 | 
						|
    }
 | 
						|
 | 
						|
    if (typeof opt_config == 'string') {
 | 
						|
      data[opt_config](opt_relatedTarget)
 | 
						|
 | 
						|
    } else if (config['show']) {
 | 
						|
      data['show'](opt_relatedTarget)
 | 
						|
    }
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Element} relatedTarget
 | 
						|
 */
 | 
						|
Modal.prototype['toggle'] = function (relatedTarget) {
 | 
						|
  return this._isShown ? this['hide']() : this['show'](relatedTarget)
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Element} relatedTarget
 | 
						|
 */
 | 
						|
Modal.prototype['show'] = function (relatedTarget) {
 | 
						|
  var showEvent = $.Event(Modal._Event.SHOW, { relatedTarget: relatedTarget })
 | 
						|
 | 
						|
  $(this._element).trigger(showEvent)
 | 
						|
 | 
						|
  if (this._isShown || showEvent.isDefaultPrevented()) {
 | 
						|
    return
 | 
						|
  }
 | 
						|
 | 
						|
  this._isShown = true
 | 
						|
 | 
						|
  this._checkScrollbar()
 | 
						|
  this._setScrollbar()
 | 
						|
 | 
						|
  $(document.body).addClass(Modal._ClassName.OPEN)
 | 
						|
 | 
						|
  this._escape()
 | 
						|
  this._resize()
 | 
						|
 | 
						|
  $(this._element).on('click.dismiss.bs.modal', Modal._Selector.DATA_DISMISS, this['hide'].bind(this))
 | 
						|
 | 
						|
  this._showBackdrop(this._showElement.bind(this, relatedTarget))
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Event} event
 | 
						|
 */
 | 
						|
Modal.prototype['hide'] = function (event) {
 | 
						|
  if (event) {
 | 
						|
    event.preventDefault()
 | 
						|
  }
 | 
						|
 | 
						|
  var hideEvent = $.Event(Modal._Event.HIDE)
 | 
						|
 | 
						|
  $(this._element).trigger(hideEvent)
 | 
						|
 | 
						|
  if (!this._isShown || hideEvent.isDefaultPrevented()) {
 | 
						|
    return
 | 
						|
  }
 | 
						|
 | 
						|
  this._isShown = false
 | 
						|
 | 
						|
  this._escape()
 | 
						|
  this._resize()
 | 
						|
 | 
						|
  $(document).off('focusin.bs.modal')
 | 
						|
 | 
						|
  $(this._element).removeClass(Modal._ClassName.IN)
 | 
						|
  this._element.setAttribute('aria-hidden', true)
 | 
						|
 | 
						|
  $(this._element).off('click.dismiss.bs.modal')
 | 
						|
 | 
						|
  if (Bootstrap.transition && $(this._element).hasClass(Modal._ClassName.FADE)) {
 | 
						|
    $(this._element)
 | 
						|
      .one(Bootstrap.TRANSITION_END, this._hideModal.bind(this))
 | 
						|
      .emulateTransitionEnd(Modal._TRANSITION_DURATION)
 | 
						|
  } else {
 | 
						|
    this._hideModal()
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Element} relatedTarget
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._showElement = function (relatedTarget) {
 | 
						|
  var transition = Bootstrap.transition && $(this._element).hasClass(Modal._ClassName.FADE)
 | 
						|
 | 
						|
  if (!this._element.parentNode || this._element.parentNode.nodeType != Node.ELEMENT_NODE) {
 | 
						|
    document.body.appendChild(this._element) // don't move modals dom position
 | 
						|
  }
 | 
						|
 | 
						|
  this._element.style.display = 'block'
 | 
						|
  this._element.scrollTop = 0
 | 
						|
 | 
						|
  if (this._config['backdrop']) {
 | 
						|
    this._adjustBackdrop()
 | 
						|
  }
 | 
						|
 | 
						|
  if (transition) {
 | 
						|
    Bootstrap.reflow(this._element)
 | 
						|
  }
 | 
						|
 | 
						|
  $(this._element).addClass(Modal._ClassName.IN)
 | 
						|
  this._element.setAttribute('aria-hidden', false)
 | 
						|
 | 
						|
  this._enforceFocus()
 | 
						|
 | 
						|
  var shownEvent = $.Event(Modal._Event.SHOWN, { relatedTarget: relatedTarget })
 | 
						|
 | 
						|
  var transitionComplete = function () {
 | 
						|
    this._element.focus()
 | 
						|
    $(this._element).trigger(shownEvent)
 | 
						|
  }.bind(this)
 | 
						|
 | 
						|
  if (transition) {
 | 
						|
    var dialog = $(this._element).find(Modal._Selector.DIALOG)[0]
 | 
						|
    $(dialog)
 | 
						|
      .one(Bootstrap.TRANSITION_END, transitionComplete)
 | 
						|
      .emulateTransitionEnd(Modal._TRANSITION_DURATION)
 | 
						|
  } else {
 | 
						|
    transitionComplete()
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._enforceFocus = function () {
 | 
						|
  $(document)
 | 
						|
    .off('focusin.bs.modal') // guard against infinite focus loop
 | 
						|
    .on('focusin.bs.modal', function (e) {
 | 
						|
      if (this._element !== e.target && !$(this._element).has(e.target).length) {
 | 
						|
        this._element.focus()
 | 
						|
      }
 | 
						|
    }.bind(this))
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._escape = function () {
 | 
						|
  if (this._isShown && this._config['keyboard']) {
 | 
						|
    $(this._element).on('keydown.dismiss.bs.modal', function (event) {
 | 
						|
      if (event.which === 27) {
 | 
						|
        this['hide']()
 | 
						|
      }
 | 
						|
    }.bind(this))
 | 
						|
 | 
						|
  } else if (!this._isShown) {
 | 
						|
    $(this._element).off('keydown.dismiss.bs.modal')
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._resize = function () {
 | 
						|
  if (this._isShown) {
 | 
						|
    $(window).on('resize.bs.modal', this._handleUpdate.bind(this))
 | 
						|
  } else {
 | 
						|
    $(window).off('resize.bs.modal')
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._hideModal = function () {
 | 
						|
  this._element.style.display = 'none'
 | 
						|
  this._showBackdrop(function () {
 | 
						|
    $(document.body).removeClass(Modal._ClassName.OPEN)
 | 
						|
    this._resetAdjustments()
 | 
						|
    this._resetScrollbar()
 | 
						|
    $(this._element).trigger(Modal._Event.HIDDEN)
 | 
						|
  }.bind(this))
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._removeBackdrop = function () {
 | 
						|
  if (this._backdrop) {
 | 
						|
    this._backdrop.parentNode.removeChild(this._backdrop)
 | 
						|
    this._backdrop = null
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Function} callback
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._showBackdrop = function (callback) {
 | 
						|
  var animate = $(this._element).hasClass(Modal._ClassName.FADE) ? Modal._ClassName.FADE : ''
 | 
						|
 | 
						|
  if (this._isShown && this._config['backdrop']) {
 | 
						|
    var doAnimate = Bootstrap.transition && animate
 | 
						|
 | 
						|
    this._backdrop = document.createElement('div')
 | 
						|
    this._backdrop.className = Modal._ClassName.BACKDROP
 | 
						|
 | 
						|
    if (animate) {
 | 
						|
      $(this._backdrop).addClass(animate)
 | 
						|
    }
 | 
						|
 | 
						|
    $(this._element).prepend(this._backdrop)
 | 
						|
 | 
						|
    $(this._backdrop).on('click.dismiss.bs.modal', function (event) {
 | 
						|
      if (event.target !== event.currentTarget) return
 | 
						|
      this._config['backdrop'] === 'static'
 | 
						|
        ? this._element.focus()
 | 
						|
        : this['hide']()
 | 
						|
    }.bind(this))
 | 
						|
 | 
						|
    if (doAnimate) {
 | 
						|
      Bootstrap.reflow(this._backdrop)
 | 
						|
    }
 | 
						|
 | 
						|
    $(this._backdrop).addClass(Modal._ClassName.IN)
 | 
						|
 | 
						|
    if (!callback) {
 | 
						|
      return
 | 
						|
    }
 | 
						|
 | 
						|
    if (!doAnimate) {
 | 
						|
      callback()
 | 
						|
      return
 | 
						|
    }
 | 
						|
 | 
						|
    $(this._backdrop)
 | 
						|
      .one(Bootstrap.TRANSITION_END, callback)
 | 
						|
      .emulateTransitionEnd(Modal._BACKDROP_TRANSITION_DURATION)
 | 
						|
 | 
						|
  } else if (!this._isShown && this._backdrop) {
 | 
						|
    $(this._backdrop).removeClass(Modal._ClassName.IN)
 | 
						|
 | 
						|
    var callbackRemove = function () {
 | 
						|
      this._removeBackdrop()
 | 
						|
      if (callback) {
 | 
						|
        callback()
 | 
						|
      }
 | 
						|
    }.bind(this)
 | 
						|
 | 
						|
    if (Bootstrap.transition && $(this._element).hasClass(Modal._ClassName.FADE)) {
 | 
						|
      $(this._backdrop)
 | 
						|
        .one(Bootstrap.TRANSITION_END, callbackRemove)
 | 
						|
        .emulateTransitionEnd(Modal._BACKDROP_TRANSITION_DURATION)
 | 
						|
    } else {
 | 
						|
      callbackRemove()
 | 
						|
    }
 | 
						|
 | 
						|
  } else if (callback) {
 | 
						|
    callback()
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * ------------------------------------------------------------------------
 | 
						|
 * the following methods are used to handle overflowing modals
 | 
						|
 * todo (fat): these should probably be refactored into a
 | 
						|
 * ------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._handleUpdate = function () {
 | 
						|
  if (this._config['backdrop']) this._adjustBackdrop()
 | 
						|
  this._adjustDialog()
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._adjustBackdrop = function () {
 | 
						|
  this._backdrop.style.height = 0 // todo (fat): no clue why we do this
 | 
						|
  this._backdrop.style.height = this._element.scrollHeight + 'px'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._adjustDialog = function () {
 | 
						|
  var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
 | 
						|
 | 
						|
  if (!this._isBodyOverflowing && isModalOverflowing) {
 | 
						|
    this._element.style.paddingLeft = this._scrollbarWidth + 'px'
 | 
						|
  }
 | 
						|
 | 
						|
  if (this._isBodyOverflowing && !isModalOverflowing) {
 | 
						|
    this._element.style.paddingRight = this._scrollbarWidth + 'px'
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._resetAdjustments = function () {
 | 
						|
  this._element.style.paddingLeft = ''
 | 
						|
  this._element.style.paddingRight = ''
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._checkScrollbar = function () {
 | 
						|
  this._isBodyOverflowing = document.body.scrollHeight > document.documentElement.clientHeight
 | 
						|
  this._scrollbarWidth = this._getScrollbarWidth()
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._setScrollbar = function () {
 | 
						|
  var bodyPadding = parseInt(($(document.body).css('padding-right') || 0), 10)
 | 
						|
 | 
						|
  if (this._isBodyOverflowing) {
 | 
						|
    document.body.style.paddingRight = bodyPadding + this._scrollbarWidth + 'px'
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._resetScrollbar = function () {
 | 
						|
  document.body.style.paddingRight = ''
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @private
 | 
						|
 */
 | 
						|
Modal.prototype._getScrollbarWidth = function () { // thx walsh
 | 
						|
  var scrollDiv = document.createElement('div')
 | 
						|
  scrollDiv.className = Modal._Selector.SCROLLBAR_MEASURER
 | 
						|
  document.body.appendChild(scrollDiv)
 | 
						|
  var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
 | 
						|
  document.body.removeChild(scrollDiv)
 | 
						|
  return scrollbarWidth
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * ------------------------------------------------------------------------
 | 
						|
 * jQuery Interface + noConflict implementaiton
 | 
						|
 * ------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {Function}
 | 
						|
 */
 | 
						|
$.fn[Modal._NAME] = Modal._jQueryInterface
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {Function}
 | 
						|
 */
 | 
						|
$.fn[Modal._NAME]['Constructor'] = Modal
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @const
 | 
						|
 * @type {Function}
 | 
						|
 */
 | 
						|
$.fn[Modal._NAME]['noConflict'] = function () {
 | 
						|
  $.fn[Modal._NAME] = Modal._JQUERY_NO_CONFLICT
 | 
						|
  return this
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * ------------------------------------------------------------------------
 | 
						|
 * Data Api implementation
 | 
						|
 * ------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
 | 
						|
$(document).on('click.bs.modal.data-api', Modal._Selector.DATA_TOGGLE, function (event) {
 | 
						|
  var selector = Bootstrap.getSelectorFromElement(this)
 | 
						|
 | 
						|
  if (selector) {
 | 
						|
    var target = $(selector)[0]
 | 
						|
  }
 | 
						|
 | 
						|
  var config = $(target).data(Modal._DATA_KEY) ? 'toggle' : $.extend({}, $(target).data(), $(this).data())
 | 
						|
 | 
						|
  if (this.tagName == 'A') {
 | 
						|
    event.preventDefault()
 | 
						|
  }
 | 
						|
 | 
						|
  var $target = $(target).one(Modal._Event.SHOW, function (showEvent) {
 | 
						|
    if (showEvent.isDefaultPrevented()) {
 | 
						|
      return // only register focus restorer if modal will actually get shown
 | 
						|
    }
 | 
						|
 | 
						|
    $target.one(Modal._Event.HIDDEN, function () {
 | 
						|
      if ($(this).is(':visible')) {
 | 
						|
        this.focus()
 | 
						|
      }
 | 
						|
    }.bind(this))
 | 
						|
  }.bind(this))
 | 
						|
 | 
						|
  Modal._jQueryInterface.call($(target), config, this)
 | 
						|
})
 |