tooltip.js 13 KB

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