tooltip.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /*!
  2. * jQuery UI Tooltip 1.11.2
  3. * http://jqueryui.com
  4. *
  5. * Copyright 2014 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.2",
  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._updateContent( target, event );
  171. },
  172. _updateContent: function( target, event ) {
  173. var content,
  174. contentOption = this.options.content,
  175. that = this,
  176. eventType = event ? event.type : null;
  177. if ( typeof contentOption === "string" ) {
  178. return this._open( event, target, contentOption );
  179. }
  180. content = contentOption.call( target[0], function( response ) {
  181. // ignore async response if tooltip was closed already
  182. if ( !target.data( "ui-tooltip-open" ) ) {
  183. return;
  184. }
  185. // IE may instantly serve a cached response for ajax requests
  186. // delay this call to _open so the other call to _open runs first
  187. that._delay(function() {
  188. // jQuery creates a special event for focusin when it doesn't
  189. // exist natively. To improve performance, the native event
  190. // object is reused and the type is changed. Therefore, we can't
  191. // rely on the type being correct after the event finished
  192. // bubbling, so we set it back to the previous value. (#8740)
  193. if ( event ) {
  194. event.type = eventType;
  195. }
  196. this._open( event, target, response );
  197. });
  198. });
  199. if ( content ) {
  200. this._open( event, target, content );
  201. }
  202. },
  203. _open: function( event, target, content ) {
  204. var tooltipData, tooltip, events, delayedShow, a11yContent,
  205. positionOption = $.extend( {}, this.options.position );
  206. if ( !content ) {
  207. return;
  208. }
  209. // Content can be updated multiple times. If the tooltip already
  210. // exists, then just update the content and bail.
  211. tooltipData = this._find( target );
  212. if ( tooltipData ) {
  213. tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content );
  214. return;
  215. }
  216. // if we have a title, clear it to prevent the native tooltip
  217. // we have to check first to avoid defining a title if none exists
  218. // (we don't want to cause an element to start matching [title])
  219. //
  220. // We use removeAttr only for key events, to allow IE to export the correct
  221. // accessible attributes. For mouse events, set to empty string to avoid
  222. // native tooltip showing up (happens only when removing inside mouseover).
  223. if ( target.is( "[title]" ) ) {
  224. if ( event && event.type === "mouseover" ) {
  225. target.attr( "title", "" );
  226. } else {
  227. target.removeAttr( "title" );
  228. }
  229. }
  230. tooltipData = this._tooltip( target );
  231. tooltip = tooltipData.tooltip;
  232. this._addDescribedBy( target, tooltip.attr( "id" ) );
  233. tooltip.find( ".ui-tooltip-content" ).html( content );
  234. // Support: Voiceover on OS X, JAWS on IE <= 9
  235. // JAWS announces deletions even when aria-relevant="additions"
  236. // Voiceover will sometimes re-read the entire log region's contents from the beginning
  237. this.liveRegion.children().hide();
  238. if ( content.clone ) {
  239. a11yContent = content.clone();
  240. a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" );
  241. } else {
  242. a11yContent = content;
  243. }
  244. $( "<div>" ).html( a11yContent ).appendTo( this.liveRegion );
  245. function position( event ) {
  246. positionOption.of = event;
  247. if ( tooltip.is( ":hidden" ) ) {
  248. return;
  249. }
  250. tooltip.position( positionOption );
  251. }
  252. if ( this.options.track && event && /^mouse/.test( event.type ) ) {
  253. this._on( this.document, {
  254. mousemove: position
  255. });
  256. // trigger once to override element-relative positioning
  257. position( event );
  258. } else {
  259. tooltip.position( $.extend({
  260. of: target
  261. }, this.options.position ) );
  262. }
  263. tooltip.hide();
  264. this._show( tooltip, this.options.show );
  265. // Handle tracking tooltips that are shown with a delay (#8644). As soon
  266. // as the tooltip is visible, position the tooltip using the most recent
  267. // event.
  268. if ( this.options.show && this.options.show.delay ) {
  269. delayedShow = this.delayedShow = setInterval(function() {
  270. if ( tooltip.is( ":visible" ) ) {
  271. position( positionOption.of );
  272. clearInterval( delayedShow );
  273. }
  274. }, $.fx.interval );
  275. }
  276. this._trigger( "open", event, { tooltip: tooltip } );
  277. events = {
  278. keyup: function( event ) {
  279. if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
  280. var fakeEvent = $.Event(event);
  281. fakeEvent.currentTarget = target[0];
  282. this.close( fakeEvent, true );
  283. }
  284. }
  285. };
  286. // Only bind remove handler for delegated targets. Non-delegated
  287. // tooltips will handle this in destroy.
  288. if ( target[ 0 ] !== this.element[ 0 ] ) {
  289. events.remove = function() {
  290. this._removeTooltip( tooltip );
  291. };
  292. }
  293. if ( !event || event.type === "mouseover" ) {
  294. events.mouseleave = "close";
  295. }
  296. if ( !event || event.type === "focusin" ) {
  297. events.focusout = "close";
  298. }
  299. this._on( true, target, events );
  300. },
  301. close: function( event ) {
  302. var tooltip,
  303. that = this,
  304. target = $( event ? event.currentTarget : this.element ),
  305. tooltipData = this._find( target );
  306. // The tooltip may already be closed
  307. if ( !tooltipData ) {
  308. return;
  309. }
  310. tooltip = tooltipData.tooltip;
  311. // disabling closes the tooltip, so we need to track when we're closing
  312. // to avoid an infinite loop in case the tooltip becomes disabled on close
  313. if ( tooltipData.closing ) {
  314. return;
  315. }
  316. // Clear the interval for delayed tracking tooltips
  317. clearInterval( this.delayedShow );
  318. // only set title if we had one before (see comment in _open())
  319. // If the title attribute has changed since open(), don't restore
  320. if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) {
  321. target.attr( "title", target.data( "ui-tooltip-title" ) );
  322. }
  323. this._removeDescribedBy( target );
  324. tooltipData.hiding = true;
  325. tooltip.stop( true );
  326. this._hide( tooltip, this.options.hide, function() {
  327. that._removeTooltip( $( this ) );
  328. });
  329. target.removeData( "ui-tooltip-open" );
  330. this._off( target, "mouseleave focusout keyup" );
  331. // Remove 'remove' binding only on delegated targets
  332. if ( target[ 0 ] !== this.element[ 0 ] ) {
  333. this._off( target, "remove" );
  334. }
  335. this._off( this.document, "mousemove" );
  336. if ( event && event.type === "mouseleave" ) {
  337. $.each( this.parents, function( id, parent ) {
  338. $( parent.element ).attr( "title", parent.title );
  339. delete that.parents[ id ];
  340. });
  341. }
  342. tooltipData.closing = true;
  343. this._trigger( "close", event, { tooltip: tooltip } );
  344. if ( !tooltipData.hiding ) {
  345. tooltipData.closing = false;
  346. }
  347. },
  348. _tooltip: function( element ) {
  349. var tooltip = $( "<div>" )
  350. .attr( "role", "tooltip" )
  351. .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
  352. ( this.options.tooltipClass || "" ) ),
  353. id = tooltip.uniqueId().attr( "id" );
  354. $( "<div>" )
  355. .addClass( "ui-tooltip-content" )
  356. .appendTo( tooltip );
  357. tooltip.appendTo( this.document[0].body );
  358. return this.tooltips[ id ] = {
  359. element: element,
  360. tooltip: tooltip
  361. };
  362. },
  363. _find: function( target ) {
  364. var id = target.data( "ui-tooltip-id" );
  365. return id ? this.tooltips[ id ] : null;
  366. },
  367. _removeTooltip: function( tooltip ) {
  368. tooltip.remove();
  369. delete this.tooltips[ tooltip.attr( "id" ) ];
  370. },
  371. _destroy: function() {
  372. var that = this;
  373. // close open tooltips
  374. $.each( this.tooltips, function( id, tooltipData ) {
  375. // Delegate to close method to handle common cleanup
  376. var event = $.Event( "blur" ),
  377. element = tooltipData.element;
  378. event.target = event.currentTarget = element[ 0 ];
  379. that.close( event, true );
  380. // Remove immediately; destroying an open tooltip doesn't use the
  381. // hide animation
  382. $( "#" + id ).remove();
  383. // Restore the title
  384. if ( element.data( "ui-tooltip-title" ) ) {
  385. // If the title attribute has changed since open(), don't restore
  386. if ( !element.attr( "title" ) ) {
  387. element.attr( "title", element.data( "ui-tooltip-title" ) );
  388. }
  389. element.removeData( "ui-tooltip-title" );
  390. }
  391. });
  392. this.liveRegion.remove();
  393. }
  394. });
  395. }));