1 /* ===========================================================
 
   2  * bootstrap-tooltip.js v2.3.2
 
   3  * http://getbootstrap.com/2.3.2/javascript.html#tooltips
 
   4  * Inspired by the original jQuery.tipsy by Jason Frame
 
   5  * ===========================================================
 
   6  * Copyright 2013 Twitter, Inc.
 
   8  * Licensed under the Apache License, Version 2.0 (the "License");
 
   9  * you may not use this file except in compliance with the License.
 
  10  * You may obtain a copy of the License at
 
  12  * http://www.apache.org/licenses/LICENSE-2.0
 
  14  * Unless required by applicable law or agreed to in writing, software
 
  15  * distributed under the License is distributed on an "AS IS" BASIS,
 
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  17  * See the License for the specific language governing permissions and
 
  18  * limitations under the License.
 
  19  * ========================================================== */
 
  24   "use strict"; // jshint ;_;
 
  27  /* TOOLTIP PUBLIC CLASS DEFINITION
 
  28   * =============================== */
 
  30   var Tooltip = function (element, options) {
 
  31     this.init('tooltip', element, options)
 
  38   , init: function (type, element, options) {
 
  46       this.$element = $(element)
 
  47       this.options = this.getOptions(options)
 
  50       triggers = this.options.trigger.split(' ')
 
  52       for (i = triggers.length; i--;) {
 
  54         if (trigger == 'click') {
 
  55           this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
 
  56         } else if (trigger != 'manual') {
 
  57           eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
 
  58           eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
 
  59           this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
 
  60           this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
 
  64       this.options.selector ?
 
  65         (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
 
  69   , getOptions: function (options) {
 
  70       options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
 
  72       if (options.delay && typeof options.delay == 'number') {
 
  82   , enter: function (e) {
 
  83       var defaults = $.fn[this.type].defaults
 
  87       this._options && $.each(this._options, function (key, value) {
 
  88         if (defaults[key] != value) options[key] = value
 
  91       self = $(e.currentTarget)[this.type](options).data(this.type)
 
  93       if (!self.options.delay || !self.options.delay.show) return self.show()
 
  95       clearTimeout(this.timeout)
 
  96       self.hoverState = 'in'
 
  97       this.timeout = setTimeout(function() {
 
  98         if (self.hoverState == 'in') self.show()
 
  99       }, self.options.delay.show)
 
 102   , leave: function (e) {
 
 103       var self = $(e.currentTarget)[this.type](this._options).data(this.type)
 
 105       if (this.timeout) clearTimeout(this.timeout)
 
 106       if (!self.options.delay || !self.options.delay.hide) return self.hide()
 
 108       self.hoverState = 'out'
 
 109       this.timeout = setTimeout(function() {
 
 110         if (self.hoverState == 'out') self.hide()
 
 111       }, self.options.delay.hide)
 
 114   , show: function () {
 
 121         , e = $.Event('show')
 
 123       if (this.hasContent() && this.enabled) {
 
 124         this.$element.trigger(e)
 
 125         if (e.isDefaultPrevented()) return
 
 129         if (this.options.animation) {
 
 130           $tip.addClass('fade')
 
 133         placement = typeof this.options.placement == 'function' ?
 
 134           this.options.placement.call(this, $tip[0], this.$element[0]) :
 
 135           this.options.placement
 
 139           .css({ top: 0, left: 0, display: 'block' })
 
 141         this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
 
 143         pos = this.getPosition()
 
 145         actualWidth = $tip[0].offsetWidth
 
 146         actualHeight = $tip[0].offsetHeight
 
 150             tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
 
 153             tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
 
 156             tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
 
 159             tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
 
 163         this.applyPlacement(tp, placement)
 
 164         this.$element.trigger('shown')
 
 168   , applyPlacement: function(offset, placement){
 
 169       var $tip = this.tip()
 
 170         , width = $tip[0].offsetWidth
 
 171         , height = $tip[0].offsetHeight
 
 182       actualWidth = $tip[0].offsetWidth
 
 183       actualHeight = $tip[0].offsetHeight
 
 185       if (placement == 'top' && actualHeight != height) {
 
 186         offset.top = offset.top + height - actualHeight
 
 190       if (placement == 'bottom' || placement == 'top') {
 
 193         if (offset.left < 0){
 
 194           delta = offset.left * -2
 
 197           actualWidth = $tip[0].offsetWidth
 
 198           actualHeight = $tip[0].offsetHeight
 
 201         this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
 
 203         this.replaceArrow(actualHeight - height, actualHeight, 'top')
 
 206       if (replace) $tip.offset(offset)
 
 209   , replaceArrow: function(delta, dimension, position){
 
 212         .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
 
 215   , setContent: function () {
 
 216       var $tip = this.tip()
 
 217         , title = this.getTitle()
 
 219       $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
 
 220       $tip.removeClass('fade in top bottom left right')
 
 223   , hide: function () {
 
 226         , e = $.Event('hide')
 
 228       this.$element.trigger(e)
 
 229       if (e.isDefaultPrevented()) return
 
 231       $tip.removeClass('in')
 
 233       function removeWithAnimation() {
 
 234         var timeout = setTimeout(function () {
 
 235           $tip.off($.support.transition.end).detach()
 
 238         $tip.one($.support.transition.end, function () {
 
 239           clearTimeout(timeout)
 
 244       $.support.transition && this.$tip.hasClass('fade') ?
 
 245         removeWithAnimation() :
 
 248       this.$element.trigger('hidden')
 
 253   , fixTitle: function () {
 
 254       var $e = this.$element
 
 255       if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
 
 256         $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
 
 260   , hasContent: function () {
 
 261       return this.getTitle()
 
 264   , getPosition: function () {
 
 265       var el = this.$element[0]
 
 266       return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
 
 267         width: el.offsetWidth
 
 268       , height: el.offsetHeight
 
 269       }, this.$element.offset())
 
 272   , getTitle: function () {
 
 277       title = $e.attr('data-original-title')
 
 278         || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
 
 284       return this.$tip = this.$tip || $(this.options.template)
 
 288       return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
 
 291   , validate: function () {
 
 292       if (!this.$element[0].parentNode) {
 
 299   , enable: function () {
 
 303   , disable: function () {
 
 307   , toggleEnabled: function () {
 
 308       this.enabled = !this.enabled
 
 311   , toggle: function (e) {
 
 312       var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
 
 313       self.tip().hasClass('in') ? self.hide() : self.show()
 
 316   , destroy: function () {
 
 317       this.hide().$element.off('.' + this.type).removeData(this.type)
 
 323  /* TOOLTIP PLUGIN DEFINITION
 
 324   * ========================= */
 
 326   var old = $.fn.tooltip
 
 328   $.fn.tooltip = function ( option ) {
 
 329     return this.each(function () {
 
 331         , data = $this.data('tooltip')
 
 332         , options = typeof option == 'object' && option
 
 333       if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
 
 334       if (typeof option == 'string') data[option]()
 
 338   $.fn.tooltip.Constructor = Tooltip
 
 340   $.fn.tooltip.defaults = {
 
 344   , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
 
 345   , trigger: 'hover focus'
 
 353  /* TOOLTIP NO CONFLICT
 
 354   * =================== */
 
 356   $.fn.tooltip.noConflict = function () {