js/bootstrap-tooltip.js
author indvd00m (gotoindvdum[at]gmail[dot]com)
Fri, 04 Jul 2014 16:42:41 +0400
changeset 0 ba8ab09f730e
permissions -rw-r--r--
First home page
     1 /* ===========================================================
     2  * bootstrap-tooltip.js v2.3.1
     3  * http://twitter.github.com/bootstrap/javascript.html#tooltips
     4  * Inspired by the original jQuery.tipsy by Jason Frame
     5  * ===========================================================
     6  * Copyright 2012 Twitter, Inc.
     7  *
     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
    11  *
    12  * http://www.apache.org/licenses/LICENSE-2.0
    13  *
    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  * ========================================================== */
    20 
    21 
    22 !function ($) {
    23 
    24   "use strict"; // jshint ;_;
    25 
    26 
    27  /* TOOLTIP PUBLIC CLASS DEFINITION
    28   * =============================== */
    29 
    30   var Tooltip = function (element, options) {
    31     this.init('tooltip', element, options)
    32   }
    33 
    34   Tooltip.prototype = {
    35 
    36     constructor: Tooltip
    37 
    38   , init: function (type, element, options) {
    39       var eventIn
    40         , eventOut
    41         , triggers
    42         , trigger
    43         , i
    44 
    45       this.type = type
    46       this.$element = $(element)
    47       this.options = this.getOptions(options)
    48       this.enabled = true
    49 
    50       triggers = this.options.trigger.split(' ')
    51 
    52       for (i = triggers.length; i--;) {
    53         trigger = triggers[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))
    61         }
    62       }
    63 
    64       this.options.selector ?
    65         (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
    66         this.fixTitle()
    67     }
    68 
    69   , getOptions: function (options) {
    70       options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
    71 
    72       if (options.delay && typeof options.delay == 'number') {
    73         options.delay = {
    74           show: options.delay
    75         , hide: options.delay
    76         }
    77       }
    78 
    79       return options
    80     }
    81 
    82   , enter: function (e) {
    83       var defaults = $.fn[this.type].defaults
    84         , options = {}
    85         , self
    86 
    87       this._options && $.each(this._options, function (key, value) {
    88         if (defaults[key] != value) options[key] = value
    89       }, this)
    90 
    91       self = $(e.currentTarget)[this.type](options).data(this.type)
    92 
    93       if (!self.options.delay || !self.options.delay.show) return self.show()
    94 
    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)
   100     }
   101 
   102   , leave: function (e) {
   103       var self = $(e.currentTarget)[this.type](this._options).data(this.type)
   104 
   105       if (this.timeout) clearTimeout(this.timeout)
   106       if (!self.options.delay || !self.options.delay.hide) return self.hide()
   107 
   108       self.hoverState = 'out'
   109       this.timeout = setTimeout(function() {
   110         if (self.hoverState == 'out') self.hide()
   111       }, self.options.delay.hide)
   112     }
   113 
   114   , show: function () {
   115       var $tip
   116         , pos
   117         , actualWidth
   118         , actualHeight
   119         , placement
   120         , tp
   121         , e = $.Event('show')
   122 
   123       if (this.hasContent() && this.enabled) {
   124         this.$element.trigger(e)
   125         if (e.isDefaultPrevented()) return
   126         $tip = this.tip()
   127         this.setContent()
   128 
   129         if (this.options.animation) {
   130           $tip.addClass('fade')
   131         }
   132 
   133         placement = typeof this.options.placement == 'function' ?
   134           this.options.placement.call(this, $tip[0], this.$element[0]) :
   135           this.options.placement
   136 
   137         $tip
   138           .detach()
   139           .css({ top: 0, left: 0, display: 'block' })
   140 
   141         this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
   142 
   143         pos = this.getPosition()
   144 
   145         actualWidth = $tip[0].offsetWidth
   146         actualHeight = $tip[0].offsetHeight
   147 
   148         switch (placement) {
   149           case 'bottom':
   150             tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
   151             break
   152           case 'top':
   153             tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
   154             break
   155           case 'left':
   156             tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
   157             break
   158           case 'right':
   159             tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
   160             break
   161         }
   162 
   163         this.applyPlacement(tp, placement)
   164         this.$element.trigger('shown')
   165       }
   166     }
   167 
   168   , applyPlacement: function(offset, placement){
   169       var $tip = this.tip()
   170         , width = $tip[0].offsetWidth
   171         , height = $tip[0].offsetHeight
   172         , actualWidth
   173         , actualHeight
   174         , delta
   175         , replace
   176 
   177       $tip
   178         .offset(offset)
   179         .addClass(placement)
   180         .addClass('in')
   181 
   182       actualWidth = $tip[0].offsetWidth
   183       actualHeight = $tip[0].offsetHeight
   184 
   185       if (placement == 'top' && actualHeight != height) {
   186         offset.top = offset.top + height - actualHeight
   187         replace = true
   188       }
   189 
   190       if (placement == 'bottom' || placement == 'top') {
   191         delta = 0
   192 
   193         if (offset.left < 0){
   194           delta = offset.left * -2
   195           offset.left = 0
   196           $tip.offset(offset)
   197           actualWidth = $tip[0].offsetWidth
   198           actualHeight = $tip[0].offsetHeight
   199         }
   200 
   201         this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
   202       } else {
   203         this.replaceArrow(actualHeight - height, actualHeight, 'top')
   204       }
   205 
   206       if (replace) $tip.offset(offset)
   207     }
   208 
   209   , replaceArrow: function(delta, dimension, position){
   210       this
   211         .arrow()
   212         .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
   213     }
   214 
   215   , setContent: function () {
   216       var $tip = this.tip()
   217         , title = this.getTitle()
   218 
   219       $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
   220       $tip.removeClass('fade in top bottom left right')
   221     }
   222 
   223   , hide: function () {
   224       var that = this
   225         , $tip = this.tip()
   226         , e = $.Event('hide')
   227 
   228       this.$element.trigger(e)
   229       if (e.isDefaultPrevented()) return
   230 
   231       $tip.removeClass('in')
   232 
   233       function removeWithAnimation() {
   234         var timeout = setTimeout(function () {
   235           $tip.off($.support.transition.end).detach()
   236         }, 500)
   237 
   238         $tip.one($.support.transition.end, function () {
   239           clearTimeout(timeout)
   240           $tip.detach()
   241         })
   242       }
   243 
   244       $.support.transition && this.$tip.hasClass('fade') ?
   245         removeWithAnimation() :
   246         $tip.detach()
   247 
   248       this.$element.trigger('hidden')
   249 
   250       return this
   251     }
   252 
   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', '')
   257       }
   258     }
   259 
   260   , hasContent: function () {
   261       return this.getTitle()
   262     }
   263 
   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())
   270     }
   271 
   272   , getTitle: function () {
   273       var title
   274         , $e = this.$element
   275         , o = this.options
   276 
   277       title = $e.attr('data-original-title')
   278         || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
   279 
   280       return title
   281     }
   282 
   283   , tip: function () {
   284       return this.$tip = this.$tip || $(this.options.template)
   285     }
   286 
   287   , arrow: function(){
   288       return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
   289     }
   290 
   291   , validate: function () {
   292       if (!this.$element[0].parentNode) {
   293         this.hide()
   294         this.$element = null
   295         this.options = null
   296       }
   297     }
   298 
   299   , enable: function () {
   300       this.enabled = true
   301     }
   302 
   303   , disable: function () {
   304       this.enabled = false
   305     }
   306 
   307   , toggleEnabled: function () {
   308       this.enabled = !this.enabled
   309     }
   310 
   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()
   314     }
   315 
   316   , destroy: function () {
   317       this.hide().$element.off('.' + this.type).removeData(this.type)
   318     }
   319 
   320   }
   321 
   322 
   323  /* TOOLTIP PLUGIN DEFINITION
   324   * ========================= */
   325 
   326   var old = $.fn.tooltip
   327 
   328   $.fn.tooltip = function ( option ) {
   329     return this.each(function () {
   330       var $this = $(this)
   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]()
   335     })
   336   }
   337 
   338   $.fn.tooltip.Constructor = Tooltip
   339 
   340   $.fn.tooltip.defaults = {
   341     animation: true
   342   , placement: 'top'
   343   , selector: false
   344   , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
   345   , trigger: 'hover focus'
   346   , title: ''
   347   , delay: 0
   348   , html: false
   349   , container: false
   350   }
   351 
   352 
   353  /* TOOLTIP NO CONFLICT
   354   * =================== */
   355 
   356   $.fn.tooltip.noConflict = function () {
   357     $.fn.tooltip = old
   358     return this
   359   }
   360 
   361 }(window.jQuery);