bootstrap-tooltip.js 9.51 KB
Newer Older
1
/* ===========================================================
Mark Otto's avatar
Mark Otto committed
2
 * bootstrap-tooltip.js v2.3.0
Jon Stevens's avatar
Jon Stevens committed
3
 * http://twitter.github.com/bootstrap/javascript.html#tooltips
4
 * Inspired by the original jQuery.tipsy by Jason Frame
5
 * ===========================================================
Mark Otto's avatar
Mark Otto committed
6
 * Copyright 2012 Twitter, Inc.
Jacob Thornton's avatar
Jacob Thornton committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================== */

21

22
23
24
!function ($) {

  "use strict"; // jshint ;_;
25

26

27
28
 /* TOOLTIP PUBLIC CLASS DEFINITION
  * =============================== */
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
40
      var eventIn
        , eventOut
41
42
43
        , triggers
        , trigger
        , i
44
45
46
47
48
49

      this.type = type
      this.$element = $(element)
      this.options = this.getOptions(options)
      this.enabled = true

50
51
52
53
54
55
56
57
58
59
60
61
      triggers = this.options.trigger.split(' ')

      for (i = triggers.length; i--;) {
        trigger = triggers[i]
        if (trigger == 'click') {
          this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
        } else if (trigger != 'manual') {
          eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
          eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
          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))
        }
62
63
64
65
66
67
68
      }

      this.options.selector ?
        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
        this.fixTitle()
    }

69
  , getOptions: function (options) {
70
71
72
73
74
75
76
77
78
79
80
81
      options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())

      if (options.delay && typeof options.delay == 'number') {
        options.delay = {
          show: options.delay
        , hide: options.delay
        }
      }

      return options
    }

82
  , enter: function (e) {
83
84
      var self = $(e.currentTarget)[this.type](this._options).data(this.type)

85
86
87
88
89
90
91
      if (!self.options.delay || !self.options.delay.show) return self.show()

      clearTimeout(this.timeout)
      self.hoverState = 'in'
      this.timeout = setTimeout(function() {
        if (self.hoverState == 'in') self.show()
      }, self.options.delay.show)
92
93
    }

94
  , leave: function (e) {
95
96
      var self = $(e.currentTarget)[this.type](this._options).data(this.type)

97
      if (this.timeout) clearTimeout(this.timeout)
98
99
100
101
102
103
      if (!self.options.delay || !self.options.delay.hide) return self.hide()

      self.hoverState = 'out'
      this.timeout = setTimeout(function() {
        if (self.hoverState == 'out') self.hide()
      }, self.options.delay.hide)
104
105
106
107
108
    }

  , show: function () {
      var $tip
        , pos
109
110
111
112
        , actualWidth
        , actualHeight
        , placement
        , tp
113
        , e = $.Event('show')
114

115
      if (this.hasContent() && this.enabled) {
116
        this.$element.trigger(e)
117
        if (e.isDefaultPrevented()) return
118
        $tip = this.tip()
Jacob Thornton's avatar
Jacob Thornton committed
119
        this.setContent()
120

Jacob Thornton's avatar
Jacob Thornton committed
121
        if (this.options.animation) {
122
123
124
          $tip.addClass('fade')
        }

125
        placement = typeof this.options.placement == 'function' ?
126
          this.options.placement.call(this, $tip[0], this.$element[0]) :
127
128
          this.options.placement

129
        $tip
frntz's avatar
frntz committed
130
          .detach()
131
          .css({ top: 0, left: 0, display: 'block' })
Yohn's avatar
Yohn committed
132

Yohn's avatar
Yohn committed
133
        this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
134

135
        pos = this.getPosition()
136
137
138

        actualWidth = $tip[0].offsetWidth
        actualHeight = $tip[0].offsetHeight
139

140
        switch (placement) {
141
          case 'bottom':
142
            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
143
            break
144
          case 'top':
145
            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
146
147
            break
          case 'left':
148
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
149
150
            break
          case 'right':
151
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
152
153
154
            break
        }

155
        this.applyPlacement(tp, placement);
156
157

        this.$element.trigger('shown')
158
159
160
      }
    }

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  , applyPlacement: function(offset, placement){
    var $tip
      , width
      , height
      , actualWidth
      , actualHeight
      , delta
      , replace = false;

    $tip = this.tip();

    width = $tip[0].offsetWidth;
    height = $tip[0].offsetHeight;

    $tip
          .offset(offset)
          .addClass(placement)
          .addClass('in');

    actualWidth = $tip[0].offsetWidth;
    actualHeight = $tip[0].offsetHeight;

    if (placement == "top" && actualHeight != actualWidth){
      offset.top = offset.top + height - actualHeight;
      replace = true;
    }

    if (placement == "bottom" || placement == "top"){
      delta = 0;

      if (offset.left < 0){
        delta = -offset.left * 2;
        offset.left = 0;
        $tip.offset(offset);
        actualWidth = $tip[0].offsetWidth;
        actualHeight = $tip[0].offsetHeight;
      }

      this.replaceArrow(delta - width + actualWidth, actualWidth, "left");
    }else{
      this.replaceArrow(actualHeight - height, actualHeight, "top");
    }

    if (replace) $tip.offset(offset);
  }

  , replaceArrow: function(delta, dimension, position){
    var $arrow = this.arrow();

    if (delta !== 0){
      $arrow.css(position, 50 * (1 - delta / dimension) + "%");
    }else{
      $arrow.css(position, "");
    }
  }

Jacob Thornton's avatar
Jacob Thornton committed
217
218
  , setContent: function () {
      var $tip = this.tip()
219
220
        , title = this.getTitle()

221
      $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
222
      $tip.removeClass('fade in top bottom left right')
Jacob Thornton's avatar
Jacob Thornton committed
223
224
    }

225
  , hide: function () {
226
227
      var that = this
        , $tip = this.tip()
228
        , e = $.Event('hide')
229

230
      this.$element.trigger(e)
231
      if (e.isDefaultPrevented()) return
232

233
      $tip.removeClass('in')
234

235
236
      function removeWithAnimation() {
        var timeout = setTimeout(function () {
frntz's avatar
frntz committed
237
          $tip.off($.support.transition.end).detach()
238
239
240
241
        }, 500)

        $tip.one($.support.transition.end, function () {
          clearTimeout(timeout)
frntz's avatar
frntz committed
242
          $tip.detach()
243
        })
244
245
      }

246
      $.support.transition && this.$tip.hasClass('fade') ?
247
        removeWithAnimation() :
frntz's avatar
frntz committed
248
        $tip.detach()
Jacob Thornton's avatar
Jacob Thornton committed
249

250
251
      this.$element.trigger('hidden')

252
      return this
253
254
    }

255
  , fixTitle: function () {
256
257
258
259
260
261
      var $e = this.$element
      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
        $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
      }
    }

262
263
264
265
  , hasContent: function () {
      return this.getTitle()
    }

266
267
  , getPosition: function () {
      var el = this.$element[0]
fat's avatar
fat committed
268
      return $.extend({}, el.getBoundingClientRect ? el.getBoundingClientRect() : {
269
270
271
        width: el.offsetWidth
      , height: el.offsetHeight
      }, this.$element.offset())
272
273
    }

274
  , getTitle: function () {
275
276
277
278
      var title
        , $e = this.$element
        , o = this.options

279
280
      title = $e.attr('data-original-title')
        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
281

282
      return title
283
284
    }

285
  , tip: function () {
286
      return this.$tip = this.$tip || $(this.options.template)
287
288
    }

289
290
291
292
  , arrow: function(){
    return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow");
  }

293
  , validate: function () {
294
295
296
297
298
299
300
      if (!this.$element[0].parentNode) {
        this.hide()
        this.$element = null
        this.options = null
      }
    }

301
  , enable: function () {
302
303
304
      this.enabled = true
    }

305
  , disable: function () {
306
307
308
      this.enabled = false
    }

309
  , toggleEnabled: function () {
310
311
312
      this.enabled = !this.enabled
    }

313
  , toggle: function (e) {
Yohn's avatar
Yohn committed
314
315
      var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
      self.tip().hasClass('in') ? self.hide() : self.show()
316
317
    }

318
  , destroy: function () {
Jon Stevens's avatar
Jon Stevens committed
319
      this.hide().$element.off('.' + this.type).removeData(this.type)
320
321
    }

322
323
324
  }


325
326
 /* TOOLTIP PLUGIN DEFINITION
  * ========================= */
327

328
329
  var old = $.fn.tooltip

330
  $.fn.tooltip = function ( option ) {
331
332
    return this.each(function () {
      var $this = $(this)
333
        , data = $this.data('tooltip')
334
        , options = typeof option == 'object' && option
335
      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
336
337
      if (typeof option == 'string') data[option]()
    })
338
339
  }

340
  $.fn.tooltip.Constructor = Tooltip
Jacob Thornton's avatar
Jacob Thornton committed
341

342
  $.fn.tooltip.defaults = {
Jacob Thornton's avatar
Jacob Thornton committed
343
    animation: true
344
  , placement: 'top'
345
346
  , selector: false
  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
347
  , trigger: 'hover focus'
348
  , title: ''
349
  , delay: 0
350
  , html: false
Yohn's avatar
Yohn committed
351
  , container: false
352
353
  }

354
355
356
357
358
359
360
361
362

 /* TOOLTIP NO CONFLICT
  * =================== */

  $.fn.tooltip.noConflict = function () {
    $.fn.tooltip = old
    return this
  }

Yohn's avatar
Yohn committed
363
}(window.jQuery);