0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022 !function ($) {
0023
0024 "use strict";
0025
0026
0027
0028
0029
0030 var Tooltip = function (element, options) {
0031 this.init('tooltip', element, options)
0032 }
0033
0034 Tooltip.prototype = {
0035
0036 constructor: Tooltip
0037
0038 , init: function (type, element, options) {
0039 var eventIn
0040 , eventOut
0041 , triggers
0042 , trigger
0043 , i
0044
0045 this.type = type
0046 this.$element = $(element)
0047 this.options = this.getOptions(options)
0048 this.enabled = true
0049
0050 triggers = this.options.trigger.split(' ')
0051
0052 for (i = triggers.length; i--;) {
0053 trigger = triggers[i]
0054 if (trigger == 'click') {
0055 this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
0056 } else if (trigger != 'manual') {
0057 eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
0058 eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
0059 this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
0060 this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
0061 }
0062 }
0063
0064 this.options.selector ?
0065 (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
0066 this.fixTitle()
0067 }
0068
0069 , getOptions: function (options) {
0070 options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
0071
0072 if (options.delay && typeof options.delay == 'number') {
0073 options.delay = {
0074 show: options.delay
0075 , hide: options.delay
0076 }
0077 }
0078
0079 return options
0080 }
0081
0082 , enter: function (e) {
0083 var defaults = $.fn[this.type].defaults
0084 , options = {}
0085 , self
0086
0087 this._options && $.each(this._options, function (key, value) {
0088 if (defaults[key] != value) options[key] = value
0089 }, this)
0090
0091 self = $(e.currentTarget)[this.type](options).data(this.type)
0092
0093 if (!self.options.delay || !self.options.delay.show) return self.show()
0094
0095 clearTimeout(this.timeout)
0096 self.hoverState = 'in'
0097 this.timeout = setTimeout(function() {
0098 if (self.hoverState == 'in') self.show()
0099 }, self.options.delay.show)
0100 }
0101
0102 , leave: function (e) {
0103 var self = $(e.currentTarget)[this.type](this._options).data(this.type)
0104
0105 if (this.timeout) clearTimeout(this.timeout)
0106 if (!self.options.delay || !self.options.delay.hide) return self.hide()
0107
0108 self.hoverState = 'out'
0109 this.timeout = setTimeout(function() {
0110 if (self.hoverState == 'out') self.hide()
0111 }, self.options.delay.hide)
0112 }
0113
0114 , show: function () {
0115 var $tip
0116 , pos
0117 , actualWidth
0118 , actualHeight
0119 , placement
0120 , tp
0121 , e = $.Event('show')
0122
0123 if (this.hasContent() && this.enabled) {
0124 this.$element.trigger(e)
0125 if (e.isDefaultPrevented()) return
0126 $tip = this.tip()
0127 this.setContent()
0128
0129 if (this.options.animation) {
0130 $tip.addClass('fade')
0131 }
0132
0133 placement = typeof this.options.placement == 'function' ?
0134 this.options.placement.call(this, $tip[0], this.$element[0]) :
0135 this.options.placement
0136
0137 $tip
0138 .detach()
0139 .css({ top: 0, left: 0, display: 'block' })
0140
0141 this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
0142
0143 pos = this.getPosition()
0144
0145 actualWidth = $tip[0].offsetWidth
0146 actualHeight = $tip[0].offsetHeight
0147
0148 switch (placement) {
0149 case 'bottom':
0150 tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
0151 break
0152 case 'top':
0153 tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
0154 break
0155 case 'left':
0156 tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
0157 break
0158 case 'right':
0159 tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
0160 break
0161 }
0162
0163 this.applyPlacement(tp, placement)
0164 this.$element.trigger('shown')
0165 }
0166 }
0167
0168 , applyPlacement: function(offset, placement){
0169 var $tip = this.tip()
0170 , width = $tip[0].offsetWidth
0171 , height = $tip[0].offsetHeight
0172 , actualWidth
0173 , actualHeight
0174 , delta
0175 , replace
0176
0177 $tip
0178 .offset(offset)
0179 .addClass(placement)
0180 .addClass('in')
0181
0182 actualWidth = $tip[0].offsetWidth
0183 actualHeight = $tip[0].offsetHeight
0184
0185 if (placement == 'top' && actualHeight != height) {
0186 offset.top = offset.top + height - actualHeight
0187 replace = true
0188 }
0189
0190 if (placement == 'bottom' || placement == 'top') {
0191 delta = 0
0192
0193 if (offset.left < 0){
0194 delta = offset.left * -2
0195 offset.left = 0
0196 $tip.offset(offset)
0197 actualWidth = $tip[0].offsetWidth
0198 actualHeight = $tip[0].offsetHeight
0199 }
0200
0201 this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
0202 } else {
0203 this.replaceArrow(actualHeight - height, actualHeight, 'top')
0204 }
0205
0206 if (replace) $tip.offset(offset)
0207 }
0208
0209 , replaceArrow: function(delta, dimension, position){
0210 this
0211 .arrow()
0212 .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
0213 }
0214
0215 , setContent: function () {
0216 var $tip = this.tip()
0217 , title = this.getTitle()
0218
0219 $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
0220 $tip.removeClass('fade in top bottom left right')
0221 }
0222
0223 , hide: function () {
0224 var that = this
0225 , $tip = this.tip()
0226 , e = $.Event('hide')
0227
0228 this.$element.trigger(e)
0229 if (e.isDefaultPrevented()) return
0230
0231 $tip.removeClass('in')
0232
0233 function removeWithAnimation() {
0234 var timeout = setTimeout(function () {
0235 $tip.off($.support.transition.end).detach()
0236 }, 500)
0237
0238 $tip.one($.support.transition.end, function () {
0239 clearTimeout(timeout)
0240 $tip.detach()
0241 })
0242 }
0243
0244 $.support.transition && this.$tip.hasClass('fade') ?
0245 removeWithAnimation() :
0246 $tip.detach()
0247
0248 this.$element.trigger('hidden')
0249
0250 return this
0251 }
0252
0253 , fixTitle: function () {
0254 var $e = this.$element
0255 if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
0256 $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
0257 }
0258 }
0259
0260 , hasContent: function () {
0261 return this.getTitle()
0262 }
0263
0264 , getPosition: function () {
0265 var el = this.$element[0]
0266 return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
0267 width: el.offsetWidth
0268 , height: el.offsetHeight
0269 }, this.$element.offset())
0270 }
0271
0272 , getTitle: function () {
0273 var title
0274 , $e = this.$element
0275 , o = this.options
0276
0277 title = $e.attr('data-original-title')
0278 || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
0279
0280 return title
0281 }
0282
0283 , tip: function () {
0284 return this.$tip = this.$tip || $(this.options.template)
0285 }
0286
0287 , arrow: function(){
0288 return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
0289 }
0290
0291 , validate: function () {
0292 if (!this.$element[0].parentNode) {
0293 this.hide()
0294 this.$element = null
0295 this.options = null
0296 }
0297 }
0298
0299 , enable: function () {
0300 this.enabled = true
0301 }
0302
0303 , disable: function () {
0304 this.enabled = false
0305 }
0306
0307 , toggleEnabled: function () {
0308 this.enabled = !this.enabled
0309 }
0310
0311 , toggle: function (e) {
0312 var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
0313 self.tip().hasClass('in') ? self.hide() : self.show()
0314 }
0315
0316 , destroy: function () {
0317 this.hide().$element.off('.' + this.type).removeData(this.type)
0318 }
0319
0320 }
0321
0322
0323
0324
0325
0326 var old = $.fn.tooltip
0327
0328 $.fn.tooltip = function ( option ) {
0329 return this.each(function () {
0330 var $this = $(this)
0331 , data = $this.data('tooltip')
0332 , options = typeof option == 'object' && option
0333 if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
0334 if (typeof option == 'string') data[option]()
0335 })
0336 }
0337
0338 $.fn.tooltip.Constructor = Tooltip
0339
0340 $.fn.tooltip.defaults = {
0341 animation: true
0342 , placement: 'top'
0343 , selector: false
0344 , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
0345 , trigger: 'hover focus'
0346 , title: ''
0347 , delay: 0
0348 , html: false
0349 , container: false
0350 }
0351
0352
0353
0354
0355
0356 $.fn.tooltip.noConflict = function () {
0357 $.fn.tooltip = old
0358 return this
0359 }
0360
0361 }(window.jQuery);