bootstrap-tooltip.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /* ===========================================================
  2. * bootstrap-tooltip.js v2.3.2
  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. !function ($) {
  21. "use strict"; // jshint ;_;
  22. /* TOOLTIP PUBLIC CLASS DEFINITION
  23. * =============================== */
  24. var Tooltip = function (element, options) {
  25. this.init('tooltip', element, options)
  26. }
  27. Tooltip.prototype = {
  28. constructor: Tooltip
  29. , init: function (type, element, options) {
  30. var eventIn
  31. , eventOut
  32. , triggers
  33. , trigger
  34. , i
  35. this.type = type
  36. this.$element = $(element)
  37. this.options = this.getOptions(options)
  38. this.enabled = true
  39. triggers = this.options.trigger.split(' ')
  40. for (i = triggers.length; i--;) {
  41. trigger = triggers[i]
  42. if (trigger == 'click') {
  43. this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
  44. } else if (trigger != 'manual') {
  45. eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
  46. eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
  47. this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
  48. this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
  49. }
  50. }
  51. this.options.selector ?
  52. (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
  53. this.fixTitle()
  54. }
  55. , getOptions: function (options) {
  56. options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
  57. if (options.delay && typeof options.delay == 'number') {
  58. options.delay = {
  59. show: options.delay
  60. , hide: options.delay
  61. }
  62. }
  63. return options
  64. }
  65. , enter: function (e) {
  66. var defaults = $.fn[this.type].defaults
  67. , options = {}
  68. , self
  69. this._options && $.each(this._options, function (key, value) {
  70. if (defaults[key] != value) options[key] = value
  71. }, this)
  72. self = $(e.currentTarget)[this.type](options).data(this.type)
  73. if (!self.options.delay || !self.options.delay.show) return self.show()
  74. clearTimeout(this.timeout)
  75. self.hoverState = 'in'
  76. this.timeout = setTimeout(function() {
  77. if (self.hoverState == 'in') self.show()
  78. }, self.options.delay.show)
  79. }
  80. , leave: function (e) {
  81. var self = $(e.currentTarget)[this.type](this._options).data(this.type)
  82. if (this.timeout) clearTimeout(this.timeout)
  83. if (!self.options.delay || !self.options.delay.hide) return self.hide()
  84. self.hoverState = 'out'
  85. this.timeout = setTimeout(function() {
  86. if (self.hoverState == 'out') self.hide()
  87. }, self.options.delay.hide)
  88. }
  89. , show: function () {
  90. var $tip
  91. , pos
  92. , actualWidth
  93. , actualHeight
  94. , placement
  95. , tp
  96. , e = $.Event('show')
  97. if (this.hasContent() && this.enabled) {
  98. this.$element.trigger(e)
  99. if (e.isDefaultPrevented()) return
  100. $tip = this.tip()
  101. this.setContent()
  102. if (this.options.animation) {
  103. $tip.addClass('fade')
  104. }
  105. placement = typeof this.options.placement == 'function' ?
  106. this.options.placement.call(this, $tip[0], this.$element[0]) :
  107. this.options.placement
  108. $tip
  109. .detach()
  110. .css({ top: 0, left: 0, display: 'block' })
  111. this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
  112. pos = this.getPosition()
  113. actualWidth = $tip[0].offsetWidth
  114. actualHeight = $tip[0].offsetHeight
  115. switch (placement) {
  116. case 'bottom':
  117. tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
  118. break
  119. case 'top':
  120. tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
  121. break
  122. case 'left':
  123. tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
  124. break
  125. case 'right':
  126. tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
  127. break
  128. }
  129. this.applyPlacement(tp, placement)
  130. this.$element.trigger('shown')
  131. }
  132. }
  133. , applyPlacement: function(offset, placement){
  134. var $tip = this.tip()
  135. , width = $tip[0].offsetWidth
  136. , height = $tip[0].offsetHeight
  137. , actualWidth
  138. , actualHeight
  139. , delta
  140. , replace
  141. $tip
  142. .offset(offset)
  143. .addClass(placement)
  144. .addClass('in')
  145. actualWidth = $tip[0].offsetWidth
  146. actualHeight = $tip[0].offsetHeight
  147. if (placement == 'top' && actualHeight != height) {
  148. offset.top = offset.top + height - actualHeight
  149. replace = true
  150. }
  151. if (placement == 'bottom' || placement == 'top') {
  152. delta = 0
  153. if (offset.left < 0){
  154. delta = offset.left * -2
  155. offset.left = 0
  156. $tip.offset(offset)
  157. actualWidth = $tip[0].offsetWidth
  158. actualHeight = $tip[0].offsetHeight
  159. }
  160. this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
  161. } else {
  162. this.replaceArrow(actualHeight - height, actualHeight, 'top')
  163. }
  164. if (replace) $tip.offset(offset)
  165. }
  166. , replaceArrow: function(delta, dimension, position){
  167. this
  168. .arrow()
  169. .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
  170. }
  171. , setContent: function () {
  172. var $tip = this.tip()
  173. , title = this.getTitle()
  174. $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
  175. $tip.removeClass('fade in top bottom left right')
  176. }
  177. , hide: function () {
  178. var that = this
  179. , $tip = this.tip()
  180. , e = $.Event('hide')
  181. this.$element.trigger(e)
  182. if (e.isDefaultPrevented()) return
  183. $tip.removeClass('in')
  184. function removeWithAnimation() {
  185. var timeout = setTimeout(function () {
  186. $tip.off($.support.transition.end).detach()
  187. }, 500)
  188. $tip.one($.support.transition.end, function () {
  189. clearTimeout(timeout)
  190. $tip.detach()
  191. })
  192. }
  193. $.support.transition && this.$tip.hasClass('fade') ?
  194. removeWithAnimation() :
  195. $tip.detach()
  196. this.$element.trigger('hidden')
  197. return this
  198. }
  199. , fixTitle: function () {
  200. var $e = this.$element
  201. if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
  202. $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
  203. }
  204. }
  205. , hasContent: function () {
  206. return this.getTitle()
  207. }
  208. , getPosition: function () {
  209. var el = this.$element[0]
  210. return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
  211. width: el.offsetWidth
  212. , height: el.offsetHeight
  213. }, this.$element.offset())
  214. }
  215. , getTitle: function () {
  216. var title
  217. , $e = this.$element
  218. , o = this.options
  219. title = $e.attr('data-original-title')
  220. || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
  221. return title
  222. }
  223. , tip: function () {
  224. return this.$tip = this.$tip || $(this.options.template)
  225. }
  226. , arrow: function(){
  227. return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
  228. }
  229. , validate: function () {
  230. if (!this.$element[0].parentNode) {
  231. this.hide()
  232. this.$element = null
  233. this.options = null
  234. }
  235. }
  236. , enable: function () {
  237. this.enabled = true
  238. }
  239. , disable: function () {
  240. this.enabled = false
  241. }
  242. , toggleEnabled: function () {
  243. this.enabled = !this.enabled
  244. }
  245. , toggle: function (e) {
  246. var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
  247. self.tip().hasClass('in') ? self.hide() : self.show()
  248. }
  249. , destroy: function () {
  250. this.hide().$element.off('.' + this.type).removeData(this.type)
  251. }
  252. }
  253. /* TOOLTIP PLUGIN DEFINITION
  254. * ========================= */
  255. var old = $.fn.tooltip
  256. $.fn.tooltip = function ( option ) {
  257. return this.each(function () {
  258. var $this = $(this)
  259. , data = $this.data('tooltip')
  260. , options = typeof option == 'object' && option
  261. if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
  262. if (typeof option == 'string') data[option]()
  263. })
  264. }
  265. $.fn.tooltip.Constructor = Tooltip
  266. $.fn.tooltip.defaults = {
  267. animation: true
  268. , placement: 'top'
  269. , selector: false
  270. , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
  271. , trigger: 'hover focus'
  272. , title: ''
  273. , delay: 0
  274. , html: false
  275. , container: false
  276. }
  277. /* TOOLTIP NO CONFLICT
  278. * =================== */
  279. $.fn.tooltip.noConflict = function () {
  280. $.fn.tooltip = old
  281. return this
  282. }
  283. }(window.jQuery);