File indexing completed on 2024-05-19 06:00:31

0001 /* ========================================================================
0002  * Bootstrap: tooltip.js v3.0.3
0003  * http://getbootstrap.com/javascript/#tooltip
0004  * Inspired by the original jQuery.tipsy by Jason Frame
0005  * ========================================================================
0006  * Copyright 2013 Twitter, Inc.
0007  *
0008  * Licensed under the Apache License, Version 2.0 (the "License");
0009  * you may not use this file except in compliance with the License.
0010  * You may obtain a copy of the License at
0011  *
0012  * http://www.apache.org/licenses/LICENSE-2.0
0013  *
0014  * Unless required by applicable law or agreed to in writing, software
0015  * distributed under the License is distributed on an "AS IS" BASIS,
0016  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017  * See the License for the specific language governing permissions and
0018  * limitations under the License.
0019  * ======================================================================== */
0020 
0021 
0022 +function ($) { "use strict";
0023 
0024   // TOOLTIP PUBLIC CLASS DEFINITION
0025   // ===============================
0026 
0027   var Tooltip = function (element, options) {
0028     this.type       =
0029     this.options    =
0030     this.enabled    =
0031     this.timeout    =
0032     this.hoverState =
0033     this.$element   = null
0034 
0035     this.init('tooltip', element, options)
0036   }
0037 
0038   Tooltip.DEFAULTS = {
0039     animation: true
0040   , placement: 'top'
0041   , selector: false
0042   , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
0043   , trigger: 'hover focus'
0044   , title: ''
0045   , delay: 0
0046   , html: false
0047   , container: false
0048   }
0049 
0050   Tooltip.prototype.init = function (type, element, options) {
0051     this.enabled  = true
0052     this.type     = type
0053     this.$element = $(element)
0054     this.options  = this.getOptions(options)
0055 
0056     var triggers = this.options.trigger.split(' ')
0057 
0058     for (var i = triggers.length; i--;) {
0059       var trigger = triggers[i]
0060 
0061       if (trigger == 'click') {
0062         this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
0063       } else if (trigger != 'manual') {
0064         var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'
0065         var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
0066 
0067         this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
0068         this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
0069       }
0070     }
0071 
0072     this.options.selector ?
0073       (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
0074       this.fixTitle()
0075   }
0076 
0077   Tooltip.prototype.getDefaults = function () {
0078     return Tooltip.DEFAULTS
0079   }
0080 
0081   Tooltip.prototype.getOptions = function (options) {
0082     options = $.extend({}, this.getDefaults(), this.$element.data(), options)
0083 
0084     if (options.delay && typeof options.delay == 'number') {
0085       options.delay = {
0086         show: options.delay
0087       , hide: options.delay
0088       }
0089     }
0090 
0091     return options
0092   }
0093 
0094   Tooltip.prototype.getDelegateOptions = function () {
0095     var options  = {}
0096     var defaults = this.getDefaults()
0097 
0098     this._options && $.each(this._options, function (key, value) {
0099       if (defaults[key] != value) options[key] = value
0100     })
0101 
0102     return options
0103   }
0104 
0105   Tooltip.prototype.enter = function (obj) {
0106     var self = obj instanceof this.constructor ?
0107       obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
0108 
0109     clearTimeout(self.timeout)
0110 
0111     self.hoverState = 'in'
0112 
0113     if (!self.options.delay || !self.options.delay.show) return self.show()
0114 
0115     self.timeout = setTimeout(function () {
0116       if (self.hoverState == 'in') self.show()
0117     }, self.options.delay.show)
0118   }
0119 
0120   Tooltip.prototype.leave = function (obj) {
0121     var self = obj instanceof this.constructor ?
0122       obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
0123 
0124     clearTimeout(self.timeout)
0125 
0126     self.hoverState = 'out'
0127 
0128     if (!self.options.delay || !self.options.delay.hide) return self.hide()
0129 
0130     self.timeout = setTimeout(function () {
0131       if (self.hoverState == 'out') self.hide()
0132     }, self.options.delay.hide)
0133   }
0134 
0135   Tooltip.prototype.show = function () {
0136     var e = $.Event('show.bs.'+ this.type)
0137 
0138     if (this.hasContent() && this.enabled) {
0139       this.$element.trigger(e)
0140 
0141       if (e.isDefaultPrevented()) return
0142 
0143       var $tip = this.tip()
0144 
0145       this.setContent()
0146 
0147       if (this.options.animation) $tip.addClass('fade')
0148 
0149       var placement = typeof this.options.placement == 'function' ?
0150         this.options.placement.call(this, $tip[0], this.$element[0]) :
0151         this.options.placement
0152 
0153       var autoToken = /\s?auto?\s?/i
0154       var autoPlace = autoToken.test(placement)
0155       if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
0156 
0157       $tip
0158         .detach()
0159         .css({ top: 0, left: 0, display: 'block' })
0160         .addClass(placement)
0161 
0162       this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
0163 
0164       var pos          = this.getPosition()
0165       var actualWidth  = $tip[0].offsetWidth
0166       var actualHeight = $tip[0].offsetHeight
0167 
0168       if (autoPlace) {
0169         var $parent = this.$element.parent()
0170 
0171         var orgPlacement = placement
0172         var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
0173         var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
0174         var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
0175         var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left
0176 
0177         placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
0178                     placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
0179                     placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
0180                     placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
0181                     placement
0182 
0183         $tip
0184           .removeClass(orgPlacement)
0185           .addClass(placement)
0186       }
0187 
0188       var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
0189 
0190       this.applyPlacement(calculatedOffset, placement)
0191       this.$element.trigger('shown.bs.' + this.type)
0192     }
0193   }
0194 
0195   Tooltip.prototype.applyPlacement = function(offset, placement) {
0196     var replace
0197     var $tip   = this.tip()
0198     var width  = $tip[0].offsetWidth
0199     var height = $tip[0].offsetHeight
0200 
0201     // manually read margins because getBoundingClientRect includes difference
0202     var marginTop = parseInt($tip.css('margin-top'), 10)
0203     var marginLeft = parseInt($tip.css('margin-left'), 10)
0204 
0205     // we must check for NaN for ie 8/9
0206     if (isNaN(marginTop))  marginTop  = 0
0207     if (isNaN(marginLeft)) marginLeft = 0
0208 
0209     offset.top  = offset.top  + marginTop
0210     offset.left = offset.left + marginLeft
0211 
0212     $tip
0213       .offset(offset)
0214       .addClass('in')
0215 
0216     // check to see if placing tip in new offset caused the tip to resize itself
0217     var actualWidth  = $tip[0].offsetWidth
0218     var actualHeight = $tip[0].offsetHeight
0219 
0220     if (placement == 'top' && actualHeight != height) {
0221       replace = true
0222       offset.top = offset.top + height - actualHeight
0223     }
0224 
0225     if (/bottom|top/.test(placement)) {
0226       var delta = 0
0227 
0228       if (offset.left < 0) {
0229         delta       = offset.left * -2
0230         offset.left = 0
0231 
0232         $tip.offset(offset)
0233 
0234         actualWidth  = $tip[0].offsetWidth
0235         actualHeight = $tip[0].offsetHeight
0236       }
0237 
0238       this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
0239     } else {
0240       this.replaceArrow(actualHeight - height, actualHeight, 'top')
0241     }
0242 
0243     if (replace) $tip.offset(offset)
0244   }
0245 
0246   Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
0247     this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
0248   }
0249 
0250   Tooltip.prototype.setContent = function () {
0251     var $tip  = this.tip()
0252     var title = this.getTitle()
0253 
0254     $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
0255     $tip.removeClass('fade in top bottom left right')
0256   }
0257 
0258   Tooltip.prototype.hide = function () {
0259     var that = this
0260     var $tip = this.tip()
0261     var e    = $.Event('hide.bs.' + this.type)
0262 
0263     function complete() {
0264       if (that.hoverState != 'in') $tip.detach()
0265     }
0266 
0267     this.$element.trigger(e)
0268 
0269     if (e.isDefaultPrevented()) return
0270 
0271     $tip.removeClass('in')
0272 
0273     $.support.transition && this.$tip.hasClass('fade') ?
0274       $tip
0275         .one($.support.transition.end, complete)
0276         .emulateTransitionEnd(150) :
0277       complete()
0278 
0279     this.$element.trigger('hidden.bs.' + this.type)
0280 
0281     return this
0282   }
0283 
0284   Tooltip.prototype.fixTitle = function () {
0285     var $e = this.$element
0286     if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
0287       $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
0288     }
0289   }
0290 
0291   Tooltip.prototype.hasContent = function () {
0292     return this.getTitle()
0293   }
0294 
0295   Tooltip.prototype.getPosition = function () {
0296     var el = this.$element[0]
0297     return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
0298       width: el.offsetWidth
0299     , height: el.offsetHeight
0300     }, this.$element.offset())
0301   }
0302 
0303   Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
0304     return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
0305            placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
0306            placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
0307         /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
0308   }
0309 
0310   Tooltip.prototype.getTitle = function () {
0311     var title
0312     var $e = this.$element
0313     var o  = this.options
0314 
0315     title = $e.attr('data-original-title')
0316       || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
0317 
0318     return title
0319   }
0320 
0321   Tooltip.prototype.tip = function () {
0322     return this.$tip = this.$tip || $(this.options.template)
0323   }
0324 
0325   Tooltip.prototype.arrow = function () {
0326     return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
0327   }
0328 
0329   Tooltip.prototype.validate = function () {
0330     if (!this.$element[0].parentNode) {
0331       this.hide()
0332       this.$element = null
0333       this.options  = null
0334     }
0335   }
0336 
0337   Tooltip.prototype.enable = function () {
0338     this.enabled = true
0339   }
0340 
0341   Tooltip.prototype.disable = function () {
0342     this.enabled = false
0343   }
0344 
0345   Tooltip.prototype.toggleEnabled = function () {
0346     this.enabled = !this.enabled
0347   }
0348 
0349   Tooltip.prototype.toggle = function (e) {
0350     var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
0351     self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
0352   }
0353 
0354   Tooltip.prototype.destroy = function () {
0355     this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
0356   }
0357 
0358 
0359   // TOOLTIP PLUGIN DEFINITION
0360   // =========================
0361 
0362   var old = $.fn.tooltip
0363 
0364   $.fn.tooltip = function (option) {
0365     return this.each(function () {
0366       var $this   = $(this)
0367       var data    = $this.data('bs.tooltip')
0368       var options = typeof option == 'object' && option
0369 
0370       if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
0371       if (typeof option == 'string') data[option]()
0372     })
0373   }
0374 
0375   $.fn.tooltip.Constructor = Tooltip
0376 
0377 
0378   // TOOLTIP NO CONFLICT
0379   // ===================
0380 
0381   $.fn.tooltip.noConflict = function () {
0382     $.fn.tooltip = old
0383     return this
0384   }
0385 
0386 }(jQuery);