jquery.ui.tooltip.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*!
  2. * jQuery UI Tooltip v1.9 stable
  3. * http://jqueryui.com
  4. *
  5. * Copyright 2012 jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. *
  9. * http://api.jqueryui.com/tooltip/
  10. *
  11. * Depends:
  12. * jquery.ui.core.js
  13. * jquery.ui.widget.js
  14. * jquery.ui.position.js
  15. */
  16. (function( $ ) {
  17. var increments = 0;
  18. function addDescribedBy( elem, id ) {
  19. var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
  20. describedby.push( id );
  21. elem
  22. .data( "ui-tooltip-id", id )
  23. .attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
  24. }
  25. function removeDescribedBy( elem ) {
  26. var id = elem.data( "ui-tooltip-id" ),
  27. describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
  28. index = $.inArray( id, describedby );
  29. if ( index !== -1 ) {
  30. describedby.splice( index, 1 );
  31. }
  32. elem.removeData( "ui-tooltip-id" );
  33. describedby = $.trim( describedby.join( " " ) );
  34. if ( describedby ) {
  35. elem.attr( "aria-describedby", describedby );
  36. } else {
  37. elem.removeAttr( "aria-describedby" );
  38. }
  39. }
  40. $.widget( "ui.tooltip", {
  41. version: "@VERSION",
  42. options: {
  43. content: function() {
  44. return $( this ).attr( "title" );
  45. },
  46. hide: true,
  47. // Disabled elements have inconsistent behavior across browsers (#8661)
  48. items: "[title]:not([disabled])",
  49. position: {
  50. my: "left top+15",
  51. at: "left bottom",
  52. collision: "flipfit flip"
  53. },
  54. show: true,
  55. tooltipClass: null,
  56. track: false,
  57. // callbacks
  58. close: null,
  59. open: null
  60. },
  61. _create: function() {
  62. this._on({
  63. mouseover: "open",
  64. focusin: "open"
  65. });
  66. // IDs of generated tooltips, needed for destroy
  67. this.tooltips = {};
  68. // IDs of parent tooltips where we removed the title attribute
  69. this.parents = {};
  70. if ( this.options.disabled ) {
  71. this._disable();
  72. }
  73. },
  74. _setOption: function( key, value ) {
  75. var that = this;
  76. if ( key === "disabled" ) {
  77. this[ value ? "_disable" : "_enable" ]();
  78. this.options[ key ] = value;
  79. // disable element style changes
  80. return;
  81. }
  82. this._super( key, value );
  83. if ( key === "content" ) {
  84. $.each( this.tooltips, function( id, element ) {
  85. that._updateContent( element );
  86. });
  87. }
  88. },
  89. _disable: function() {
  90. var that = this;
  91. // close open tooltips
  92. $.each( this.tooltips, function( id, element ) {
  93. var event = $.Event( "blur" );
  94. event.target = event.currentTarget = element[0];
  95. that.close( event, true );
  96. });
  97. // remove title attributes to prevent native tooltips
  98. this.element.find( this.options.items ).andSelf().each(function() {
  99. var element = $( this );
  100. if ( element.is( "[title]" ) ) {
  101. element
  102. .data( "ui-tooltip-title", element.attr( "title" ) )
  103. .attr( "title", "" );
  104. }
  105. });
  106. },
  107. _enable: function() {
  108. // restore title attributes
  109. this.element.find( this.options.items ).andSelf().each(function() {
  110. var element = $( this );
  111. if ( element.data( "ui-tooltip-title" ) ) {
  112. element.attr( "title", element.data( "ui-tooltip-title" ) );
  113. }
  114. });
  115. },
  116. open: function( event ) {
  117. var that = this,
  118. target = $( event ? event.target : this.element )
  119. // we need closest here due to mouseover bubbling,
  120. // but always pointing at the same event target
  121. .closest( this.options.items );
  122. // No element to show a tooltip for or the tooltip is already open
  123. if ( !target.length || target.data( "ui-tooltip-id" ) ) {
  124. return;
  125. }
  126. if ( target.attr( "title" ) ) {
  127. target.data( "ui-tooltip-title", target.attr( "title" ) );
  128. }
  129. target.data( "ui-tooltip-open", true );
  130. // kill parent tooltips, custom or native, for hover
  131. if ( event && event.type === "mouseover" ) {
  132. target.parents().each(function() {
  133. var parent = $( this ),
  134. blurEvent;
  135. if ( parent.data( "ui-tooltip-open" ) ) {
  136. blurEvent = $.Event( "blur" );
  137. blurEvent.target = blurEvent.currentTarget = this;
  138. that.close( blurEvent, true );
  139. }
  140. if ( parent.attr( "title" ) ) {
  141. parent.uniqueId();
  142. that.parents[ this.id ] = {
  143. element: this,
  144. title: parent.attr( "title" )
  145. };
  146. parent.attr( "title", "" );
  147. }
  148. });
  149. }
  150. this._updateContent( target, event );
  151. },
  152. _updateContent: function( target, event ) {
  153. var content,
  154. contentOption = this.options.content,
  155. that = this,
  156. eventType = event ? event.type : null;
  157. if ( typeof contentOption === "string" ) {
  158. return this._open( event, target, contentOption );
  159. }
  160. content = contentOption.call( target[0], function( response ) {
  161. // ignore async response if tooltip was closed already
  162. if ( !target.data( "ui-tooltip-open" ) ) {
  163. return;
  164. }
  165. // IE may instantly serve a cached response for ajax requests
  166. // delay this call to _open so the other call to _open runs first
  167. that._delay(function() {
  168. // jQuery creates a special event for focusin when it doesn't
  169. // exist natively. To improve performance, the native event
  170. // object is reused and the type is changed. Therefore, we can't
  171. // rely on the type being correct after the event finished
  172. // bubbling, so we set it back to the previous value. (#8740)
  173. if ( event ) {
  174. event.type = eventType;
  175. }
  176. this._open( event, target, response );
  177. });
  178. });
  179. if ( content ) {
  180. this._open( event, target, content );
  181. }
  182. },
  183. _open: function( event, target, content ) {
  184. var tooltip, events, delayedShow,
  185. positionOption = $.extend( {}, this.options.position );
  186. if ( !content ) {
  187. return;
  188. }
  189. // Content can be updated multiple times. If the tooltip already
  190. // exists, then just update the content and bail.
  191. tooltip = this._find( target );
  192. if ( tooltip.length ) {
  193. tooltip.find( ".ui-tooltip-content" ).html( content );
  194. return;
  195. }
  196. // if we have a title, clear it to prevent the native tooltip
  197. // we have to check first to avoid defining a title if none exists
  198. // (we don't want to cause an element to start matching [title])
  199. //
  200. // We use removeAttr only for key events, to allow IE to export the correct
  201. // accessible attributes. For mouse events, set to empty string to avoid
  202. // native tooltip showing up (happens only when removing inside mouseover).
  203. if ( target.is( "[title]" ) ) {
  204. if ( event && event.type === "mouseover" ) {
  205. target.attr( "title", "" );
  206. } else {
  207. target.removeAttr( "title" );
  208. }
  209. }
  210. tooltip = this._tooltip( target );
  211. addDescribedBy( target, tooltip.attr( "id" ) );
  212. tooltip.find( ".ui-tooltip-content" ).html( content );
  213. function position( event ) {
  214. positionOption.of = event;
  215. if ( tooltip.is( ":hidden" ) ) {
  216. return;
  217. }
  218. tooltip.position( positionOption );
  219. }
  220. if ( this.options.track && event && /^mouse/.test( event.type ) ) {
  221. this._on( this.document, {
  222. mousemove: position
  223. });
  224. // trigger once to override element-relative positioning
  225. position( event );
  226. } else {
  227. tooltip.position( $.extend({
  228. of: target
  229. }, this.options.position ) );
  230. }
  231. tooltip.hide();
  232. this._show( tooltip, this.options.show );
  233. // Handle tracking tooltips that are shown with a delay (#8644). As soon
  234. // as the tooltip is visible, position the tooltip using the most recent
  235. // event.
  236. if ( this.options.show && this.options.show.delay ) {
  237. delayedShow = setInterval(function() {
  238. if ( tooltip.is( ":visible" ) ) {
  239. position( positionOption.of );
  240. clearInterval( delayedShow );
  241. }
  242. }, $.fx.interval );
  243. }
  244. this._trigger( "open", event, { tooltip: tooltip } );
  245. events = {
  246. keyup: function( event ) {
  247. if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
  248. var fakeEvent = $.Event(event);
  249. fakeEvent.currentTarget = target[0];
  250. this.close( fakeEvent, true );
  251. }
  252. },
  253. remove: function() {
  254. this._removeTooltip( tooltip );
  255. }
  256. };
  257. if ( !event || event.type === "mouseover" ) {
  258. events.mouseleave = "close";
  259. }
  260. if ( !event || event.type === "focusin" ) {
  261. events.focusout = "close";
  262. }
  263. this._on( true, target, events );
  264. },
  265. close: function( event ) {
  266. var that = this,
  267. target = $( event ? event.currentTarget : this.element ),
  268. tooltip = this._find( target );
  269. // disabling closes the tooltip, so we need to track when we're closing
  270. // to avoid an infinite loop in case the tooltip becomes disabled on close
  271. if ( this.closing ) {
  272. return;
  273. }
  274. // only set title if we had one before (see comment in _open())
  275. if ( target.data( "ui-tooltip-title" ) ) {
  276. target.attr( "title", target.data( "ui-tooltip-title" ) );
  277. }
  278. removeDescribedBy( target );
  279. tooltip.stop( true );
  280. this._hide( tooltip, this.options.hide, function() {
  281. that._removeTooltip( $( this ) );
  282. });
  283. target.removeData( "ui-tooltip-open" );
  284. this._off( target, "mouseleave focusout keyup" );
  285. // Remove 'remove' binding only on delegated targets
  286. if ( target[0] !== this.element[0] ) {
  287. this._off( target, "remove" );
  288. }
  289. this._off( this.document, "mousemove" );
  290. if ( event && event.type === "mouseleave" ) {
  291. $.each( this.parents, function( id, parent ) {
  292. $( parent.element ).attr( "title", parent.title );
  293. delete that.parents[ id ];
  294. });
  295. }
  296. this.closing = true;
  297. this._trigger( "close", event, { tooltip: tooltip } );
  298. this.closing = false;
  299. },
  300. _tooltip: function( element ) {
  301. var id = "ui-tooltip-" + increments++,
  302. tooltip = $( "<div>" )
  303. .attr({
  304. id: id,
  305. role: "tooltip"
  306. })
  307. .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
  308. ( this.options.tooltipClass || "" ) );
  309. $( "<div>" )
  310. .addClass( "ui-tooltip-content" )
  311. .appendTo( tooltip );
  312. tooltip.appendTo( this.document[0].body );
  313. if ( $.fn.bgiframe ) {
  314. tooltip.bgiframe();
  315. }
  316. this.tooltips[ id ] = element;
  317. return tooltip;
  318. },
  319. _find: function( target ) {
  320. var id = target.data( "ui-tooltip-id" );
  321. return id ? $( "#" + id ) : $();
  322. },
  323. _removeTooltip: function( tooltip ) {
  324. tooltip.remove();
  325. delete this.tooltips[ tooltip.attr( "id" ) ];
  326. },
  327. _destroy: function() {
  328. var that = this;
  329. // close open tooltips
  330. $.each( this.tooltips, function( id, element ) {
  331. // Delegate to close method to handle common cleanup
  332. var event = $.Event( "blur" );
  333. event.target = event.currentTarget = element[0];
  334. that.close( event, true );
  335. // Remove immediately; destroying an open tooltip doesn't use the
  336. // hide animation
  337. $( "#" + id ).remove();
  338. // Restore the title
  339. if ( element.data( "ui-tooltip-title" ) ) {
  340. element.attr( "title", element.data( "ui-tooltip-title" ) );
  341. element.removeData( "ui-tooltip-title" );
  342. }
  343. });
  344. }
  345. });
  346. }( jQuery ) );