tooltip.js 13.1 KB
Newer Older
1
/* ========================================================================
2
 * Bootstrap: tooltip.js v3.0.3
3
 * http://getbootstrap.com/javascript/#tooltip
4
 * Inspired by the original jQuery.tipsy by Jason Frame
5
 * ========================================================================
6
 * Copyright 2011-2014 Twitter, Inc.
7
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
8
 * ======================================================================== */
Jacob Thornton's avatar
Jacob Thornton committed
9

10

Zlatan Vasović's avatar
Zlatan Vasović committed
11
12
+function ($) {
  'use strict';
13

fat's avatar
fat committed
14
15
  // TOOLTIP PUBLIC CLASS DEFINITION
  // ===============================
16

17
  var Tooltip = function (element, options) {
fat's avatar
fat committed
18
19
20
21
22
23
24
    this.type       =
    this.options    =
    this.enabled    =
    this.timeout    =
    this.hoverState =
    this.$element   = null

25
    this.init('tooltip', element, options)
26
27
  }

fat's avatar
fat committed
28
  Tooltip.DEFAULTS = {
Zlatan Vasović's avatar
Zlatan Vasović committed
29
30
31
32
33
34
35
36
    animation: true,
    placement: 'top',
    selector: false,
    template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
    trigger: 'hover focus',
    title: '',
    delay: 0,
    html: false,
37
38
39
    container: false,
    viewport: 'body',
    viewportPadding: 0
fat's avatar
fat committed
40
  }
41

fat's avatar
fat committed
42
43
  Tooltip.prototype.init = function (type, element, options) {
    this.enabled  = true
fat's avatar
fat committed
44
    this.type     = type
fat's avatar
fat committed
45
    this.$element = $(element)
fat's avatar
fat committed
46
    this.options  = this.getOptions(options)
47
    this.$viewport = $(this.options.viewport)
48

fat's avatar
fat committed
49
    var triggers = this.options.trigger.split(' ')
50

fat's avatar
fat committed
51
52
53
54
55
56
    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') {
57
58
        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
fat's avatar
fat committed
59

Jacob Thornton's avatar
Jacob Thornton committed
60
        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
fat's avatar
fat committed
61
        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
62
      }
fat's avatar
fat committed
63
64
65
66
67
68
69
70
71
72
73
74
75
    }

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

  Tooltip.prototype.getDefaults = function () {
    return Tooltip.DEFAULTS
  }

  Tooltip.prototype.getOptions = function (options) {
    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
76

fat's avatar
fat committed
77
78
    if (options.delay && typeof options.delay == 'number') {
      options.delay = {
Zlatan Vasović's avatar
Zlatan Vasović committed
79
80
        show: options.delay,
        hide: options.delay
fat's avatar
fat committed
81
      }
82
83
    }

fat's avatar
fat committed
84
85
    return options
  }
86

Jacob Thornton's avatar
Jacob Thornton committed
87
  Tooltip.prototype.getDelegateOptions = function () {
fat's avatar
fat committed
88
    var options  = {}
Jacob Thornton's avatar
Jacob Thornton committed
89
    var defaults = this.getDefaults()
90

fat's avatar
fat committed
91
92
    this._options && $.each(this._options, function (key, value) {
      if (defaults[key] != value) options[key] = value
fat's avatar
fat committed
93
    })
94

Jacob Thornton's avatar
Jacob Thornton committed
95
96
97
98
    return options
  }

  Tooltip.prototype.enter = function (obj) {
fat's avatar
fat committed
99
    var self = obj instanceof this.constructor ?
Jacob Thornton's avatar
Jacob Thornton committed
100
      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
101

Jacob Thornton's avatar
Jacob Thornton committed
102
    clearTimeout(self.timeout)
103

104
105
    self.hoverState = 'in'

Jacob Thornton's avatar
Jacob Thornton committed
106
107
    if (!self.options.delay || !self.options.delay.show) return self.show()

108
    self.timeout = setTimeout(function () {
fat's avatar
fat committed
109
110
111
      if (self.hoverState == 'in') self.show()
    }, self.options.delay.show)
  }
112

fat's avatar
fat committed
113
114
  Tooltip.prototype.leave = function (obj) {
    var self = obj instanceof this.constructor ?
Jacob Thornton's avatar
Jacob Thornton committed
115
      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
fat's avatar
fat committed
116

Jacob Thornton's avatar
Jacob Thornton committed
117
    clearTimeout(self.timeout)
fat's avatar
fat committed
118

119
120
    self.hoverState = 'out'

fat's avatar
fat committed
121
    if (!self.options.delay || !self.options.delay.hide) return self.hide()
122

123
    self.timeout = setTimeout(function () {
fat's avatar
fat committed
124
125
126
      if (self.hoverState == 'out') self.hide()
    }, self.options.delay.hide)
  }
127

fat's avatar
fat committed
128
  Tooltip.prototype.show = function () {
Chris Rebert's avatar
Chris Rebert committed
129
    var e = $.Event('show.bs.' + this.type)
130

fat's avatar
fat committed
131
132
    if (this.hasContent() && this.enabled) {
      this.$element.trigger(e)
133

fat's avatar
fat committed
134
      if (e.isDefaultPrevented()) return
135
      var that = this;
136

fat's avatar
fat committed
137
      var $tip = this.tip()
Yohn's avatar
Yohn committed
138

fat's avatar
fat committed
139
      this.setContent()
140

fat's avatar
fat committed
141
      if (this.options.animation) $tip.addClass('fade')
142

fat's avatar
fat committed
143
144
145
      var placement = typeof this.options.placement == 'function' ?
        this.options.placement.call(this, $tip[0], this.$element[0]) :
        this.options.placement
146

147
148
149
150
      var autoToken = /\s?auto?\s?/i
      var autoPlace = autoToken.test(placement)
      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

fat's avatar
fat committed
151
152
153
      $tip
        .detach()
        .css({ top: 0, left: 0, display: 'block' })
154
        .addClass(placement)
fat's avatar
fat committed
155
156
157
158
159
160
161

      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)

      var pos          = this.getPosition()
      var actualWidth  = $tip[0].offsetWidth
      var actualHeight = $tip[0].offsetHeight

162
      if (autoPlace) {
163
        var orgPlacement = placement
164
        var $parent = this.$element.parent()
165
        var parentDim = this.getElementDimensions($parent)
166

167
168
169
170
        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - parentDim.scroll > parentDim.height  ? 'top' :
                    placement == 'top'    && pos.top   - parentDim.scroll - actualHeight < 0 ? 'bottom' :
                    placement == 'right'  && pos.right + actualWidth > parentDim.width ? 'left' :
                    placement == 'left'   && pos.left  - actualWidth < parentDim.left ? 'right' :
171
172
173
174
175
176
177
                    placement

        $tip
          .removeClass(orgPlacement)
          .addClass(placement)
      }

178
      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
fat's avatar
fat committed
179

fat's avatar
fat committed
180
      this.applyPlacement(calculatedOffset, placement)
181
      this.hoverState = null
182
183
184
185
186
187

      var complete = function() {
        that.$element.trigger('shown.bs.' + that.type)
      }

      $.support.transition && this.$tip.hasClass('fade') ?
188
189
190
191
        $tip
          .one($.support.transition.end, complete)
          .emulateTransitionEnd(150) :
        complete()
192
    }
fat's avatar
fat committed
193
  }
194

195
  Tooltip.prototype.applyPlacement = function (offset, placement) {
fat's avatar
fat committed
196
    var replace
fat's avatar
fat committed
197
198
199
    var $tip   = this.tip()
    var width  = $tip[0].offsetWidth
    var height = $tip[0].offsetHeight
200

201
    // manually read margins because getBoundingClientRect includes difference
fat's avatar
fat committed
202
203
204
205
206
207
208
209
210
    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  = offset.top  + marginTop
    offset.left = offset.left + marginLeft
211

212
213
    // $.fn.offset doesn't round pixel values
    // so we use setOffset directly with our own function B-0
Kevin Sawicki's avatar
Kevin Sawicki committed
214
    $.offset.setOffset($tip[0], $.extend({
215
216
217
218
219
220
221
222
223
      using: function (props) {
        $tip.css({
          top: Math.round(props.top),
          left: Math.round(props.left)
        })
      }
    }, offset), 0)

    $tip.addClass('in')
224

fat's avatar
fat committed
225
    // check to see if placing tip in new offset caused the tip to resize itself
fat's avatar
fat committed
226
227
    var actualWidth  = $tip[0].offsetWidth
    var actualHeight = $tip[0].offsetHeight
fat's avatar
fat committed
228

fat's avatar
fat committed
229
    if (placement == 'top' && actualHeight != height) {
fat's avatar
fat committed
230
      offset.top = offset.top + height - actualHeight
fat's avatar
fat committed
231
232
    }

233
    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
234

235
236
237
238
    if (delta.left)
      offset.left = offset.left + delta.left
    else
      offset.top  = offset.top + delta.top
239

240
241
242
    var arrowDelta          = delta.left * 2 - (delta.left ? width + actualWidth : height + actualHeight)
    var arrowPosition       = delta.left ? 'left'        : 'top'
    var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight'
fat's avatar
fat committed
243

244
245
    $tip.offset(offset)
    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition)
fat's avatar
fat committed
246
  }
247

248
  Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
XhmikosR's avatar
XhmikosR committed
249
    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
fat's avatar
fat committed
250
  }
251

fat's avatar
fat committed
252
253
254
  Tooltip.prototype.setContent = function () {
    var $tip  = this.tip()
    var title = this.getTitle()
Jacob Thornton's avatar
Jacob Thornton committed
255

fat's avatar
fat committed
256
257
258
    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
    $tip.removeClass('fade in top bottom left right')
  }
259

fat's avatar
fat committed
260
261
262
  Tooltip.prototype.hide = function () {
    var that = this
    var $tip = this.tip()
263
    var e    = $.Event('hide.bs.' + this.type)
264

265
266
    function complete() {
      if (that.hoverState != 'in') $tip.detach()
267
      that.$element.trigger('hidden.bs.' + that.type)
268
    }
Jacob Thornton's avatar
Jacob Thornton committed
269

fat's avatar
fat committed
270
    this.$element.trigger(e)
271

fat's avatar
fat committed
272
    if (e.isDefaultPrevented()) return
273

fat's avatar
fat committed
274
275
276
    $tip.removeClass('in')

    $.support.transition && this.$tip.hasClass('fade') ?
277
      $tip
Jacob Thornton's avatar
Jacob Thornton committed
278
        .one($.support.transition.end, complete)
279
        .emulateTransitionEnd(150) :
Jacob Thornton's avatar
Jacob Thornton committed
280
      complete()
281

282
    this.hoverState = null
283

fat's avatar
fat committed
284
285
    return this
  }
286

fat's avatar
fat committed
287
288
289
290
  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', '')
291
    }
fat's avatar
fat committed
292
  }
293

fat's avatar
fat committed
294
295
296
  Tooltip.prototype.hasContent = function () {
    return this.getTitle()
  }
297

fat's avatar
fat committed
298
299
300
  Tooltip.prototype.getPosition = function () {
    var el = this.$element[0]
    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
Zlatan Vasović's avatar
Zlatan Vasović committed
301
302
      width: el.offsetWidth,
      height: el.offsetHeight
fat's avatar
fat committed
303
304
    }, this.$element.offset())
  }
305

306
  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
fat's avatar
fat committed
307
308
309
310
    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   }
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343

  }

  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
    var delta = {}
    var viewportPadding = this.options.viewportPadding
    var viewportDimensions = this.getElementDimensions(this.$viewport)

    if (/right|left/.test(placement)) {
      if (pos.top + actualHeight - viewportDimensions.scroll + viewportPadding > viewportDimensions.height) { // bottom overflow
        delta.top = viewportDimensions.height + viewportDimensions.scroll - actualHeight - viewportPadding - pos.top
      } else if (pos.top - viewportDimensions.scroll - viewportPadding < 0) { // top overflow
        delta.top = viewportDimensions.scroll + viewportPadding - pos.top
      }
    } else {
      if (pos.left + actualWidth + viewportPadding > viewportDimensions.width) { // right overflow
        delta.left = viewportDimensions.width - actualWidth - viewportPadding - pos.left
      } else if (pos.left - viewportPadding < viewportDimensions.left) { // left overflow
        delta.left = viewportDimensions.left + viewportPadding - pos.left
      }
    }

    return delta
  }

  Tooltip.prototype.getElementDimensions = function ($element) {
    var isBody = $element[0].tagName == 'BODY'
    return {
      scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(),
      width:  isBody ? window.innerWidth  : $element.outerWidth(),
      height: isBody ? window.innerHeight : $element.outerHeight(),
      left:   isBody ? 0 : $element.offset().left
    }
fat's avatar
fat committed
344
345
  }

fat's avatar
fat committed
346
347
348
349
  Tooltip.prototype.getTitle = function () {
    var title
    var $e = this.$element
    var o  = this.options
350

fat's avatar
fat committed
351
352
    title = $e.attr('data-original-title')
      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
353

fat's avatar
fat committed
354
355
    return title
  }
356

fat's avatar
fat committed
357
358
359
  Tooltip.prototype.tip = function () {
    return this.$tip = this.$tip || $(this.options.template)
  }
360

Chris Rebert's avatar
Chris Rebert committed
361
  Tooltip.prototype.arrow = function () {
fat's avatar
fat committed
362
    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
fat's avatar
fat committed
363
  }
364

fat's avatar
fat committed
365
366
367
368
369
  Tooltip.prototype.validate = function () {
    if (!this.$element[0].parentNode) {
      this.hide()
      this.$element = null
      this.options  = null
370
    }
fat's avatar
fat committed
371
  }
372

fat's avatar
fat committed
373
374
375
  Tooltip.prototype.enable = function () {
    this.enabled = true
  }
376

fat's avatar
fat committed
377
378
379
  Tooltip.prototype.disable = function () {
    this.enabled = false
  }
380

fat's avatar
fat committed
381
382
383
  Tooltip.prototype.toggleEnabled = function () {
    this.enabled = !this.enabled
  }
384

fat's avatar
fat committed
385
  Tooltip.prototype.toggle = function (e) {
Jacob Thornton's avatar
Jacob Thornton committed
386
    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
fat's avatar
fat committed
387
    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
fat's avatar
fat committed
388
  }
389

fat's avatar
fat committed
390
  Tooltip.prototype.destroy = function () {
fat's avatar
nope    
fat committed
391
    clearTimeout(this.timeout)
392
    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
393
394
395
  }


fat's avatar
fat committed
396
397
  // TOOLTIP PLUGIN DEFINITION
  // =========================
398

399
400
  var old = $.fn.tooltip

fat's avatar
fat committed
401
  $.fn.tooltip = function (option) {
402
    return this.each(function () {
fat's avatar
fat committed
403
      var $this   = $(this)
404
      var data    = $this.data('bs.tooltip')
fat's avatar
fat committed
405
406
      var options = typeof option == 'object' && option

fat's avatar
fat committed
407
      if (!data && option == 'destroy') return
408
      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
409
410
      if (typeof option == 'string') data[option]()
    })
411
412
  }

413
  $.fn.tooltip.Constructor = Tooltip
Jacob Thornton's avatar
Jacob Thornton committed
414

415

fat's avatar
fat committed
416
417
  // TOOLTIP NO CONFLICT
  // ===================
418
419
420
421
422
423

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

424
}(jQuery);