diff options
| author | fat <[email protected]> | 2015-05-06 13:34:14 -0700 |
|---|---|---|
| committer | fat <[email protected]> | 2015-05-06 13:34:14 -0700 |
| commit | d1fbe200f46002431cdeebf965c4b789ef7ed267 (patch) | |
| tree | 43a7cc7667492e519b906f8a428935da2972ac14 /js/tooltip.js | |
| parent | 09fb80568a52af6c440db971cdc6fd88eab8f8b5 (diff) | |
| download | bootstrap-d1fbe200f46002431cdeebf965c4b789ef7ed267.tar.xz bootstrap-d1fbe200f46002431cdeebf965c4b789ef7ed267.zip | |
remove closureness from plugins
Diffstat (limited to 'js/tooltip.js')
| -rw-r--r-- | js/tooltip.js | 1124 |
1 files changed, 381 insertions, 743 deletions
diff --git a/js/tooltip.js b/js/tooltip.js index c787c6820..0779f139d 100644 --- a/js/tooltip.js +++ b/js/tooltip.js @@ -1,876 +1,514 @@ -/** ======================================================================= - * Bootstrap: tooltip.js v4.0.0 +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.4 * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame * ======================================================================== * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== - * @fileoverview - Bootstrap's tooltip plugin. - * (Inspired by jQuery.tipsy by Jason Frame) - * - * Public Methods & Properties: - * - * + $.tooltip - * + $.tooltip.noConflict - * + $.tooltip.Constructor - * + $.tooltip.Constructor.VERSION - * + $.tooltip.Constructor.Defaults - * + $.tooltip.Constructor.Defaults.container - * + $.tooltip.Constructor.Defaults.animation - * + $.tooltip.Constructor.Defaults.placement - * + $.tooltip.Constructor.Defaults.selector - * + $.tooltip.Constructor.Defaults.template - * + $.tooltip.Constructor.Defaults.trigger - * + $.tooltip.Constructor.Defaults.title - * + $.tooltip.Constructor.Defaults.delay - * + $.tooltip.Constructor.Defaults.html - * + $.tooltip.Constructor.Defaults.viewport - * + $.tooltip.Constructor.Defaults.viewport.selector - * + $.tooltip.Constructor.Defaults.viewport.padding - * + $.tooltip.Constructor.prototype.enable - * + $.tooltip.Constructor.prototype.disable - * + $.tooltip.Constructor.prototype.destroy - * + $.tooltip.Constructor.prototype.toggleEnabled - * + $.tooltip.Constructor.prototype.toggle - * + $.tooltip.Constructor.prototype.show - * + $.tooltip.Constructor.prototype.hide - * - * ======================================================================== - */ - -'use strict'; + * ======================================================================== */ -/** - * Our tooltip class. - * @param {Element!} element - * @param {Object=} opt_config - * @constructor - */ -var Tooltip = function (element, opt_config) { ++function ($) { + 'use strict'; - /** @private {boolean} */ - this._isEnabled = true + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== - /** @private {number} */ - this._timeout = 0 + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null - /** @private {string} */ - this._hoverState = '' - - /** @protected {Element} */ - this.element = element + this.init('tooltip', element, options) + } - /** @protected {Object} */ - this.config = this._getConfig(opt_config) + Tooltip.VERSION = '3.3.4' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } - /** @protected {Element} */ - this.tip = null + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } - /** @protected {Element} */ - this.arrow = null + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } - if (this.config['viewport']) { + var triggers = this.options.trigger.split(' ') - /** @private {Element} */ - this._viewport = $(this.config['viewport']['selector'] || this.config['viewport'])[0] + for (var i = triggers.length; i--;) { + var trigger = triggers[i] - } + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' - this._setListeners() -} - - -/** - * @const - * @type {string} - */ -Tooltip['VERSION'] = '4.0.0' - - -/** - * @const - * @type {Object} - */ -Tooltip['Defaults'] = { - 'container' : false, - 'animation' : true, - 'placement' : 'top', - 'selector' : false, - 'template' : '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', - 'trigger' : 'hover focus', - 'title' : '', - 'delay' : 0, - 'html' : false, - 'viewport': { - 'selector': 'body', - 'padding' : 0 - } -} - - -/** - * @const - * @enum {string} - * @protected - */ -Tooltip.Direction = { - TOP: 'top', - LEFT: 'left', - RIGHT: 'right', - BOTTOM: 'bottom' -} - - -/** - * @const - * @type {string} - * @private - */ -Tooltip._NAME = 'tooltip' - - -/** - * @const - * @type {string} - * @private - */ -Tooltip._DATA_KEY = 'bs.tooltip' - - -/** - * @const - * @type {number} - * @private - */ -Tooltip._TRANSITION_DURATION = 150 - - -/** - * @const - * @enum {string} - * @private - */ -Tooltip._HoverState = { - IN: 'in', - OUT: 'out' -} - - -/** - * @const - * @enum {string} - * @private - */ -Tooltip._Event = { - HIDE : 'hide.bs.tooltip', - HIDDEN : 'hidden.bs.tooltip', - SHOW : 'show.bs.tooltip', - SHOWN : 'shown.bs.tooltip' -} - - -/** - * @const - * @enum {string} - * @private - */ -Tooltip._ClassName = { - FADE : 'fade', - IN : 'in' -} - - -/** - * @const - * @enum {string} - * @private - */ -Tooltip._Selector = { - TOOLTIP : '.tooltip', - TOOLTIP_INNER : '.tooltip-inner', - TOOLTIP_ARROW : '.tooltip-arrow' -} - - -/** - * @const - * @type {Function} - * @private - */ -Tooltip._JQUERY_NO_CONFLICT = $.fn[Tooltip._NAME] - - -/** - * @param {Object=} opt_config - * @this {jQuery} - * @return {jQuery} - * @private - */ -Tooltip._jQueryInterface = function (opt_config) { - return this.each(function () { - var data = $(this).data(Tooltip._DATA_KEY) - var config = typeof opt_config == 'object' ? opt_config : null - - if (!data && opt_config == 'destroy') { - return + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } } - if (!data) { - data = new Tooltip(this, config) - $(this).data(Tooltip._DATA_KEY, data) - } - - if (typeof opt_config === 'string') { - data[opt_config]() - } - }) -} - - -/** - * Enable tooltip - */ -Tooltip.prototype['enable'] = function () { - this._isEnabled = true -} - - -/** - * Disable tooltip - */ -Tooltip.prototype['disable'] = function () { - this._isEnabled = false -} - - -/** - * Toggle the tooltip enable state - */ -Tooltip.prototype['toggleEnabled'] = function () { - this._isEnabled = !this._isEnabled -} - -/** - * Toggle the tooltips display - * @param {Event} opt_event - */ -Tooltip.prototype['toggle'] = function (opt_event) { - var context = this - var dataKey = this.getDataKey() - - if (opt_event) { - context = $(opt_event.currentTarget).data(dataKey) - - if (!context) { - context = new this.constructor(opt_event.currentTarget, this._getDelegateConfig()) - $(opt_event.currentTarget).data(dataKey, context) - } + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() } - $(context.getTipElement()).hasClass(Tooltip._ClassName.IN) ? - context._leave(null, context) : - context._enter(null, context) -} - - -/** - * Remove tooltip functionality - */ -Tooltip.prototype['destroy'] = function () { - clearTimeout(this._timeout) - this['hide'](function () { - $(this.element) - .off(Tooltip._Selector.TOOLTIP) - .removeData(this.getDataKey()) - }.bind(this)) -} - - -/** - * Show the tooltip - * todo (fat): ~fuck~ this is a big function - refactor out all of positioning logic - * and replace with external lib - */ -Tooltip.prototype['show'] = function () { - // jQuery's :hidden gives false positives for SVG elements - // See https://github.com/jquery/jquery/pull/939 - // Since this hiddenness check is just a nicety anyway, simply assume SVGs are always visible. - var isHidden = $(this.element).is(':hidden') && !(window.SVGElement && this.element instanceof window.SVGElement) - if (isHidden) { - throw new Error('Can\'t show a tooltip/popover on a hidden element') + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS } - var showEvent = $.Event(this.getEventObject().SHOW) - - if (this.isWithContent() && this._isEnabled) { - $(this.element).trigger(showEvent) + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) - var isInTheDom = $.contains(this.element.ownerDocument.documentElement, this.element) - - if (showEvent.isDefaultPrevented() || !isInTheDom) { - return + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } } - var tip = this.getTipElement() - var tipId = Bootstrap.getUID(this.getName()) - - tip.setAttribute('id', tipId) - this.element.setAttribute('aria-describedby', tipId) + return options + } - this.setContent() + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() - if (this.config['animation']) { - $(tip).addClass(Tooltip._ClassName.FADE) - } + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) - var placement = typeof this.config['placement'] == 'function' ? - this.config['placement'].call(this, tip, this.element) : - this.config['placement'] + return options + } - var autoToken = /\s?auto?\s?/i - var isWithAutoPlacement = autoToken.test(placement) + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) - if (isWithAutoPlacement) { - placement = placement.replace(autoToken, '') || Tooltip.Direction.TOP + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) } - if (tip.parentNode && tip.parentNode.nodeType == Node.ELEMENT_NODE) { - tip.parentNode.removeChild(tip) + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true } - tip.style.top = 0 - tip.style.left = 0 - tip.style.display = 'block' - - $(tip).addClass(Tooltip._NAME + '-' + placement) - - $(tip).data(this.getDataKey(), this) - - if (this.config['container']) { - $(this.config['container'])[0].appendChild(tip) - } else { - this.element.parentNode.insertBefore(tip, this.element.nextSibling) + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return } - var position = this._getPosition() - var actualWidth = tip.offsetWidth - var actualHeight = tip.offsetHeight - - var calculatedPlacement = this._getCalculatedAutoPlacement(isWithAutoPlacement, placement, position, actualWidth, actualHeight) - var calculatedOffset = this._getCalculatedOffset(calculatedPlacement, position, actualWidth, actualHeight) - - this._applyCalculatedPlacement(calculatedOffset, calculatedPlacement) + clearTimeout(self.timeout) - var complete = function () { - var prevHoverState = this.hoverState - $(this.element).trigger(this.getEventObject().SHOWN) - this.hoverState = null + self.hoverState = 'in' - if (prevHoverState == 'out') this._leave(null, this) - }.bind(this) + if (!self.options.delay || !self.options.delay.show) return self.show() - Bootstrap.transition && $(this._tip).hasClass(Tooltip._ClassName.FADE) ? - $(this._tip) - .one(Bootstrap.TRANSITION_END, complete) - .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) : - complete() + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) } -} - -/** - * Hide the tooltip breh - */ -Tooltip.prototype['hide'] = function (callback) { - var tip = this.getTipElement() - var hideEvent = $.Event(this.getEventObject().HIDE) - - var complete = function () { - if (this._hoverState != Tooltip._HoverState.IN) { - tip.parentNode.removeChild(tip) + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true } - this.element.removeAttribute('aria-describedby') - $(this.element).trigger(this.getEventObject().HIDDEN) - - if (callback) { - callback() - } - }.bind(this) - - $(this.element).trigger(hideEvent) - - if (hideEvent.isDefaultPrevented()) return - - $(tip).removeClass(Tooltip._ClassName.IN) - - if (Bootstrap.transition && $(this._tip).hasClass(Tooltip._ClassName.FADE)) { - $(tip) - .one(Bootstrap.TRANSITION_END, complete) - .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) - } else { - complete() + return false } - this._hoverState = '' -} - + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) -/** - * @return {string} - */ -Tooltip.prototype['getHoverState'] = function (callback) { - return this._hoverState -} + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } -/** - * @return {string} - * @protected - */ -Tooltip.prototype.getName = function () { - return Tooltip._NAME -} + if (self.isInStateTrue()) return + clearTimeout(self.timeout) -/** - * @return {string} - * @protected - */ -Tooltip.prototype.getDataKey = function () { - return Tooltip._DATA_KEY -} + self.hoverState = 'out' + if (!self.options.delay || !self.options.delay.hide) return self.hide() -/** - * @return {Object} - * @protected - */ -Tooltip.prototype.getEventObject = function () { - return Tooltip._Event -} + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) -/** - * @return {string} - * @protected - */ -Tooltip.prototype.getTitle = function () { - var title = this.element.getAttribute('data-original-title') + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) - if (!title) { - title = typeof this.config['title'] === 'function' ? - this.config['title'].call(this.element) : - this.config['title'] - } + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this - return /** @type {string} */ (title) -} + var $tip = this.tip() + var tipId = this.getUID(this.type) -/** - * @return {Element} - * @protected - */ -Tooltip.prototype.getTipElement = function () { - return (this._tip = this._tip || $(this.config['template'])[0]) -} + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + if (this.options.animation) $tip.addClass('fade') -/** - * @return {Element} - * @protected - */ -Tooltip.prototype.getArrowElement = function () { - return (this.arrow = this.arrow || $(this.getTipElement()).find(Tooltip._Selector.TOOLTIP_ARROW)[0]) -} + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' -/** - * @return {boolean} - * @protected - */ -Tooltip.prototype.isWithContent = function () { - return !!this.getTitle() -} + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) -/** - * @protected - */ -Tooltip.prototype.setContent = function () { - var tip = this.getTipElement() - var title = this.getTitle() + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight - $(tip).find(Tooltip._Selector.TOOLTIP_INNER)[0][this.config['html'] ? 'innerHTML' : 'innerText'] = title + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) - $(tip) - .removeClass(Tooltip._ClassName.FADE) - .removeClass(Tooltip._ClassName.IN) + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement - for (var direction in Tooltip.Direction) { - $(tip).removeClass(Tooltip._NAME + '-' + direction) - } -} + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) -/** - * @private - */ -Tooltip.prototype._setListeners = function () { - var triggers = this.config['trigger'].split(' ') + this.applyPlacement(calculatedOffset, placement) - triggers.forEach(function (trigger) { - if (trigger == 'click') { - $(this.element).on('click.bs.tooltip', this.config['selector'], this['toggle'].bind(this)) + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null - } else if (trigger != 'manual') { - var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' - var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + if (prevHoverState == 'out') that.leave(that) + } - $(this.element) - .on(eventIn + '.bs.tooltip', this.config['selector'], this._enter.bind(this)) - .on(eventOut + '.bs.tooltip', this.config['selector'], this._leave.bind(this)) + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() } - }.bind(this)) - - if (this.config['selector']) { - this.config = $.extend({}, this.config, { 'trigger': 'manual', 'selector': '' }) - } else { - this._fixTitle() } -} - - -/** - * @param {Object=} opt_config - * @return {Object} - * @private - */ -Tooltip.prototype._getConfig = function (opt_config) { - var config = $.extend({}, this.constructor['Defaults'], $(this.element).data(), opt_config) - if (config['delay'] && typeof config['delay'] == 'number') { - config['delay'] = { - 'show': config['delay'], - 'hide': config['delay'] + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight } - } - return config -} + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + if (delta.left) offset.left += delta.left + else offset.top += delta.top -/** - * @return {Object} - * @private - */ -Tooltip.prototype._getDelegateConfig = function () { - var config = {} - var defaults = this.constructor['Defaults'] + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' - if (this.config) { - for (var key in this.config) { - var value = this.config[key] - if (defaults[key] != value) config[key] = value - } + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) } - return config -} - - - -/** - * @param {boolean} isWithAutoPlacement - * @param {string} placement - * @param {Object} position - * @param {number} actualWidth - * @param {number} actualHeight - * @return {string} - * @private - */ -Tooltip.prototype._getCalculatedAutoPlacement = function (isWithAutoPlacement, placement, position, actualWidth, actualHeight) { - if (isWithAutoPlacement) { - var originalPlacement = placement - var container = this.config['container'] ? $(this.config['container'])[0] : this.element.parentNode - var containerDim = this._getPosition(/** @type {Element} */ (container)) - - placement = placement == Tooltip.Direction.BOTTOM && position.bottom + actualHeight > containerDim.bottom ? Tooltip.Direction.TOP : - placement == Tooltip.Direction.TOP && position.top - actualHeight < containerDim.top ? Tooltip.Direction.BOTTOM : - placement == Tooltip.Direction.RIGHT && position.right + actualWidth > containerDim.width ? Tooltip.Direction.LEFT : - placement == Tooltip.Direction.LEFT && position.left - actualWidth < containerDim.left ? Tooltip.Direction.RIGHT : - placement - - $(this._tip) - .removeClass(Tooltip._NAME + '-' + originalPlacement) - .addClass(Tooltip._NAME + '-' + placement) + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') } - return placement -} - - -/** - * @param {string} placement - * @param {Object} position - * @param {number} actualWidth - * @param {number} actualHeight - * @return {{left: number, top: number}} - * @private - */ -Tooltip.prototype._getCalculatedOffset = function (placement, position, actualWidth, actualHeight) { - return placement == Tooltip.Direction.BOTTOM ? { top: position.top + position.height, left: position.left + position.width / 2 - actualWidth / 2 } : - placement == Tooltip.Direction.TOP ? { top: position.top - actualHeight, left: position.left + position.width / 2 - actualWidth / 2 } : - placement == Tooltip.Direction.LEFT ? { top: position.top + position.height / 2 - actualHeight / 2, left: position.left - actualWidth } : - /* placement == Tooltip.Direction.RIGHT */ { top: position.top + position.height / 2 - actualHeight / 2, left: position.left + position.width } -} - - -/** - * @param {string} placement - * @param {Object} position - * @param {number} actualWidth - * @param {number} actualHeight - * @return {Object} - * @private - */ -Tooltip.prototype._getViewportAdjustedDelta = function (placement, position, actualWidth, actualHeight) { - var delta = { top: 0, left: 0 } - - if (!this._viewport) { - return delta + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') } - var viewportPadding = this.config['viewport'] && this.config['viewport']['padding'] || 0 - var viewportDimensions = this._getPosition(this._viewport) + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } - if (placement === Tooltip.Direction.RIGHT || placement === Tooltip.Direction.LEFT) { - var topEdgeOffset = position.top - viewportPadding - viewportDimensions.scroll - var bottomEdgeOffset = position.top + viewportPadding - viewportDimensions.scroll + actualHeight + this.$element.trigger(e) - if (topEdgeOffset < viewportDimensions.top) { // top overflow - delta.top = viewportDimensions.top - topEdgeOffset + if (e.isDefaultPrevented()) return - } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow - delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset - } + $tip.removeClass('in') - } else { - var leftEdgeOffset = position.left - viewportPadding - var rightEdgeOffset = position.left + viewportPadding + actualWidth + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() - if (leftEdgeOffset < viewportDimensions.left) { // left overflow - delta.left = viewportDimensions.left - leftEdgeOffset + this.hoverState = null - } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow - delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset - } + return this } - return delta -} - - -/** - * @param {Element=} opt_element - * @return {Object} - * @private - */ -Tooltip.prototype._getPosition = function (opt_element) { - var element = opt_element || this.element - var isBody = element.tagName == 'BODY' - var rect = element.getBoundingClientRect() - var offset = isBody ? { top: 0, left: 0 } : $(element).offset() - var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : this.element.scrollTop } - var outerDims = isBody ? { width: window.innerWidth, height: window.innerHeight } : null - - return $.extend({}, rect, scroll, outerDims, offset) -} - - -/** - * @param {{left: number, top: number}} offset - * @param {string} placement - * @private - */ -Tooltip.prototype._applyCalculatedPlacement = function (offset, placement) { - var tip = this.getTipElement() - var width = tip.offsetWidth - var height = tip.offsetHeight - - // manually read margins because getBoundingClientRect includes difference - var marginTop = parseInt(tip.style.marginTop, 10) - var marginLeft = parseInt(tip.style.marginLeft, 10) - - // we must check for NaN for ie 8/9 - if (isNaN(marginTop)) { - marginTop = 0 - } - if (isNaN(marginLeft)) { - marginLeft = 0 + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } } - offset.top = offset.top + marginTop - offset.left = offset.left + marginLeft + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } - // $.fn.offset doesn't round pixel values - // so we use setOffset directly with our own function B-0 - $.offset.setOffset(tip, $.extend({ - using: function (props) { - tip.style.top = Math.round(props.top) + 'px' - tip.style.left = Math.round(props.left) + 'px' - } - }, offset), 0) + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element - $(tip).addClass(Tooltip._ClassName.IN) + var el = $element[0] + var isBody = el.tagName == 'BODY' - // check to see if placing tip in new offset caused the tip to resize itself - var actualWidth = tip.offsetWidth - var actualHeight = tip.offsetHeight + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null - if (placement == Tooltip.Direction.TOP && actualHeight != height) { - offset.top = offset.top + height - actualHeight + return $.extend({}, elRect, scroll, outerDims, elOffset) } - var delta = this._getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } - if (delta.left) { - offset.left += delta.left - } else { - offset.top += delta.top } - var isVertical = placement === Tooltip.Direction.TOP || placement === Tooltip.Direction.BOTTOM - var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight - var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' - - $(tip).offset(offset) - - this._replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical) -} - - -/** - * @param {number} delta - * @param {number} dimension - * @param {boolean} isHorizontal - * @private - */ -Tooltip.prototype._replaceArrow = function (delta, dimension, isHorizontal) { - var arrow = this.getArrowElement() + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } - arrow.style[isHorizontal ? 'left' : 'top'] = 50 * (1 - delta / dimension) + '%' - arrow.style[isHorizontal ? 'top' : 'left'] = '' -} + return delta + } + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) -/** - * @private - */ -Tooltip.prototype._fixTitle = function () { - if (this.element.getAttribute('title') || typeof this.element.getAttribute('data-original-title') != 'string') { - this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '') - this.element.setAttribute('title', '') + return title } -} - - -/** - * @param {Event=} opt_event - * @param {Object=} opt_context - * @private - */ -Tooltip.prototype._enter = function (opt_event, opt_context) { - var dataKey = this.getDataKey() - var context = opt_context || $(opt_event.currentTarget).data(dataKey) - if (context && context._tip && context._tip.offsetWidth) { - context._hoverState = Tooltip._HoverState.IN - return + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix } - if (!context) { - context = new this.constructor(opt_event.currentTarget, this._getDelegateConfig()) - $(opt_event.currentTarget).data(dataKey, context) + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip } - clearTimeout(context._timeout) + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } - context._hoverState = Tooltip._HoverState.IN + Tooltip.prototype.enable = function () { + this.enabled = true + } - if (!context.config['delay'] || !context.config['delay']['show']) { - context['show']() - return + Tooltip.prototype.disable = function () { + this.enabled = false } - context._timeout = setTimeout(function () { - if (context._hoverState == Tooltip._HoverState.IN) { - context['show']() - } - }, context.config['delay']['show']) -} - - -/** - * @param {Event=} opt_event - * @param {Object=} opt_context - * @private - */ -Tooltip.prototype._leave = function (opt_event, opt_context) { - var dataKey = this.getDataKey() - var context = opt_context || $(opt_event.currentTarget).data(dataKey) - - if (!context) { - context = new this.constructor(opt_event.currentTarget, this._getDelegateConfig()) - $(opt_event.currentTarget).data(dataKey, context) + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled } - clearTimeout(context._timeout) + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } - context._hoverState = Tooltip._HoverState.OUT + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } - if (!context.config['delay'] || !context.config['delay']['hide']) { - context['hide']() - return + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + }) } - context._timeout = setTimeout(function () { - if (context._hoverState == Tooltip._HoverState.OUT) { - context['hide']() - } - }, context.config['delay']['hide']) -} + // TOOLTIP PLUGIN DEFINITION + // ========================= + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option -/** - * ------------------------------------------------------------------------ - * jQuery Interface + noConflict implementaiton - * ------------------------------------------------------------------------ - */ + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } -/** - * @const - * @type {Function} - */ -$.fn[Tooltip._NAME] = Tooltip._jQueryInterface + var old = $.fn.tooltip + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip -/** - * @const - * @type {Function} - */ -$.fn[Tooltip._NAME]['Constructor'] = Tooltip + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } -/** - * @const - * @type {Function} - */ -$.fn[Tooltip._NAME]['noConflict'] = function () { - $.fn[Tooltip._NAME] = Tooltip._JQUERY_NO_CONFLICT - return this -} +}(jQuery); |
