Tooltip.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit.Tooltip"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.Tooltip"] = true;
  8. dojo.provide("dijit.Tooltip");
  9. dojo.require("dijit._Widget");
  10. dojo.require("dijit._Templated");
  11. dojo.declare(
  12. "dijit._MasterTooltip",
  13. [dijit._Widget, dijit._Templated],
  14. {
  15. // summary:
  16. // Internal widget that holds the actual tooltip markup,
  17. // which occurs once per page.
  18. // Called by Tooltip widgets which are just containers to hold
  19. // the markup
  20. // tags:
  21. // protected
  22. // duration: Integer
  23. // Milliseconds to fade in/fade out
  24. duration: dijit.defaultDuration,
  25. templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" dojoAttachPoint=\"connectorNode\"></div\n></div>\n"),
  26. postCreate: function(){
  27. dojo.body().appendChild(this.domNode);
  28. this.bgIframe = new dijit.BackgroundIframe(this.domNode);
  29. // Setup fade-in and fade-out functions.
  30. this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") });
  31. this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") });
  32. },
  33. show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
  34. // summary:
  35. // Display tooltip w/specified contents to right of specified node
  36. // (To left if there's no space on the right, or if rtl == true)
  37. if(this.aroundNode && this.aroundNode === aroundNode){
  38. return;
  39. }
  40. // reset width; it may have been set by orient() on a previous tooltip show()
  41. this.domNode.width = "auto";
  42. if(this.fadeOut.status() == "playing"){
  43. // previous tooltip is being hidden; wait until the hide completes then show new one
  44. this._onDeck=arguments;
  45. return;
  46. }
  47. this.containerNode.innerHTML=innerHTML;
  48. var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient"));
  49. // show it
  50. dojo.style(this.domNode, "opacity", 0);
  51. this.fadeIn.play();
  52. this.isShowingNow = true;
  53. this.aroundNode = aroundNode;
  54. },
  55. orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){
  56. // summary:
  57. // Private function to set CSS for tooltip node based on which position it's in.
  58. // This is called by the dijit popup code. It will also reduce the tooltip's
  59. // width to whatever width is available
  60. // tags:
  61. // protected
  62. this.connectorNode.style.top = ""; //reset to default
  63. //Adjust the spaceAvailable width, without changing the spaceAvailable object
  64. var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth;
  65. node.className = "dijitTooltip " +
  66. {
  67. "BL-TL": "dijitTooltipBelow dijitTooltipABLeft",
  68. "TL-BL": "dijitTooltipAbove dijitTooltipABLeft",
  69. "BR-TR": "dijitTooltipBelow dijitTooltipABRight",
  70. "TR-BR": "dijitTooltipAbove dijitTooltipABRight",
  71. "BR-BL": "dijitTooltipRight",
  72. "BL-BR": "dijitTooltipLeft"
  73. }[aroundCorner + "-" + tooltipCorner];
  74. // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen
  75. this.domNode.style.width = "auto";
  76. var size = dojo.contentBox(this.domNode);
  77. var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w);
  78. var widthWasReduced = width < size.w;
  79. this.domNode.style.width = width+"px";
  80. //Adjust width for tooltips that have a really long word or a nowrap setting
  81. if(widthWasReduced){
  82. this.containerNode.style.overflow = "auto"; //temp change to overflow to detect if our tooltip needs to be wider to support the content
  83. var scrollWidth = this.containerNode.scrollWidth;
  84. this.containerNode.style.overflow = "visible"; //change it back
  85. if(scrollWidth > width){
  86. scrollWidth = scrollWidth + dojo.style(this.domNode,"paddingLeft") + dojo.style(this.domNode,"paddingRight");
  87. this.domNode.style.width = scrollWidth + "px";
  88. }
  89. }
  90. // Reposition the tooltip connector.
  91. if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){
  92. var mb = dojo.marginBox(node);
  93. var tooltipConnectorHeight = this.connectorNode.offsetHeight;
  94. if(mb.h > spaceAvailable.h){
  95. // The tooltip starts at the top of the page and will extend past the aroundNode
  96. var aroundNodePlacement = spaceAvailable.h - (aroundNodeCoords.h / 2) - (tooltipConnectorHeight / 2);
  97. this.connectorNode.style.top = aroundNodePlacement + "px";
  98. this.connectorNode.style.bottom = "";
  99. }else{
  100. // Align center of connector with center of aroundNode, except don't let bottom
  101. // of connector extend below bottom of tooltip content, or top of connector
  102. // extend past top of tooltip content
  103. this.connectorNode.style.bottom = Math.min(
  104. Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0),
  105. mb.h - tooltipConnectorHeight) + "px";
  106. this.connectorNode.style.top = "";
  107. }
  108. }else{
  109. // reset the tooltip back to the defaults
  110. this.connectorNode.style.top = "";
  111. this.connectorNode.style.bottom = "";
  112. }
  113. return Math.max(0, size.w - tooltipSpaceAvaliableWidth);
  114. },
  115. _onShow: function(){
  116. // summary:
  117. // Called at end of fade-in operation
  118. // tags:
  119. // protected
  120. if(dojo.isIE){
  121. // the arrow won't show up on a node w/an opacity filter
  122. this.domNode.style.filter="";
  123. }
  124. },
  125. hide: function(aroundNode){
  126. // summary:
  127. // Hide the tooltip
  128. if(this._onDeck && this._onDeck[1] == aroundNode){
  129. // this hide request is for a show() that hasn't even started yet;
  130. // just cancel the pending show()
  131. this._onDeck=null;
  132. }else if(this.aroundNode === aroundNode){
  133. // this hide request is for the currently displayed tooltip
  134. this.fadeIn.stop();
  135. this.isShowingNow = false;
  136. this.aroundNode = null;
  137. this.fadeOut.play();
  138. }else{
  139. // just ignore the call, it's for a tooltip that has already been erased
  140. }
  141. },
  142. _onHide: function(){
  143. // summary:
  144. // Called at end of fade-out operation
  145. // tags:
  146. // protected
  147. this.domNode.style.cssText=""; // to position offscreen again
  148. this.containerNode.innerHTML="";
  149. if(this._onDeck){
  150. // a show request has been queued up; do it now
  151. this.show.apply(this, this._onDeck);
  152. this._onDeck=null;
  153. }
  154. }
  155. }
  156. );
  157. dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
  158. // summary:
  159. // Display tooltip w/specified contents in specified position.
  160. // See description of dijit.Tooltip.defaultPosition for details on position parameter.
  161. // If position is not specified then dijit.Tooltip.defaultPosition is used.
  162. if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
  163. return dijit._masterTT.show(innerHTML, aroundNode, position, rtl);
  164. };
  165. dijit.hideTooltip = function(aroundNode){
  166. // summary:
  167. // Hide the tooltip
  168. if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
  169. return dijit._masterTT.hide(aroundNode);
  170. };
  171. dojo.declare(
  172. "dijit.Tooltip",
  173. dijit._Widget,
  174. {
  175. // summary:
  176. // Pops up a tooltip (a help message) when you hover over a node.
  177. // label: String
  178. // Text to display in the tooltip.
  179. // Specified as innerHTML when creating the widget from markup.
  180. label: "",
  181. // showDelay: Integer
  182. // Number of milliseconds to wait after hovering over/focusing on the object, before
  183. // the tooltip is displayed.
  184. showDelay: 400,
  185. // connectId: String|String[]
  186. // Id of domNode(s) to attach the tooltip to.
  187. // When user hovers over specified dom node, the tooltip will appear.
  188. connectId: [],
  189. // position: String[]
  190. // See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
  191. position: [],
  192. _setConnectIdAttr: function(/*String*/ newId){
  193. // summary:
  194. // Connect to node(s) (specified by id)
  195. // Remove connections to old nodes (if there are any)
  196. dojo.forEach(this._connections || [], function(nested){
  197. dojo.forEach(nested, dojo.hitch(this, "disconnect"));
  198. }, this);
  199. // Make connections to nodes in newIds.
  200. var ary = dojo.isArrayLike(newId) ? newId : (newId ? [newId] : []);
  201. this._connections = dojo.map(ary, function(id){
  202. var node = dojo.byId(id);
  203. return node ? [
  204. this.connect(node, "onmouseenter", "_onTargetMouseEnter"),
  205. this.connect(node, "onmouseleave", "_onTargetMouseLeave"),
  206. this.connect(node, "onfocus", "_onTargetFocus"),
  207. this.connect(node, "onblur", "_onTargetBlur")
  208. ] : [];
  209. }, this);
  210. this._set("connectId", newId);
  211. this._connectIds = ary; // save as array
  212. },
  213. addTarget: function(/*DOMNODE || String*/ node){
  214. // summary:
  215. // Attach tooltip to specified node if it's not already connected
  216. // TODO: remove in 2.0 and just use set("connectId", ...) interface
  217. var id = node.id || node;
  218. if(dojo.indexOf(this._connectIds, id) == -1){
  219. this.set("connectId", this._connectIds.concat(id));
  220. }
  221. },
  222. removeTarget: function(/*DOMNODE || String*/ node){
  223. // summary:
  224. // Detach tooltip from specified node
  225. // TODO: remove in 2.0 and just use set("connectId", ...) interface
  226. var id = node.id || node, // map from DOMNode back to plain id string
  227. idx = dojo.indexOf(this._connectIds, id);
  228. if(idx >= 0){
  229. // remove id (modifies original this._connectIds but that's OK in this case)
  230. this._connectIds.splice(idx, 1);
  231. this.set("connectId", this._connectIds);
  232. }
  233. },
  234. buildRendering: function(){
  235. this.inherited(arguments);
  236. dojo.addClass(this.domNode,"dijitTooltipData");
  237. },
  238. startup: function(){
  239. this.inherited(arguments);
  240. // If this tooltip was created in a template, or for some other reason the specified connectId[s]
  241. // didn't exist during the widget's initialization, then connect now.
  242. var ids = this.connectId;
  243. dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this);
  244. },
  245. _onTargetMouseEnter: function(/*Event*/ e){
  246. // summary:
  247. // Handler for mouseenter event on the target node
  248. // tags:
  249. // private
  250. this._onHover(e);
  251. },
  252. _onTargetMouseLeave: function(/*Event*/ e){
  253. // summary:
  254. // Handler for mouseleave event on the target node
  255. // tags:
  256. // private
  257. this._onUnHover(e);
  258. },
  259. _onTargetFocus: function(/*Event*/ e){
  260. // summary:
  261. // Handler for focus event on the target node
  262. // tags:
  263. // private
  264. this._focus = true;
  265. this._onHover(e);
  266. },
  267. _onTargetBlur: function(/*Event*/ e){
  268. // summary:
  269. // Handler for blur event on the target node
  270. // tags:
  271. // private
  272. this._focus = false;
  273. this._onUnHover(e);
  274. },
  275. _onHover: function(/*Event*/ e){
  276. // summary:
  277. // Despite the name of this method, it actually handles both hover and focus
  278. // events on the target node, setting a timer to show the tooltip.
  279. // tags:
  280. // private
  281. if(!this._showTimer){
  282. var target = e.target;
  283. this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay);
  284. }
  285. },
  286. _onUnHover: function(/*Event*/ e){
  287. // summary:
  288. // Despite the name of this method, it actually handles both mouseleave and blur
  289. // events on the target node, hiding the tooltip.
  290. // tags:
  291. // private
  292. // keep a tooltip open if the associated element still has focus (even though the
  293. // mouse moved away)
  294. if(this._focus){ return; }
  295. if(this._showTimer){
  296. clearTimeout(this._showTimer);
  297. delete this._showTimer;
  298. }
  299. this.close();
  300. },
  301. open: function(/*DomNode*/ target){
  302. // summary:
  303. // Display the tooltip; usually not called directly.
  304. // tags:
  305. // private
  306. if(this._showTimer){
  307. clearTimeout(this._showTimer);
  308. delete this._showTimer;
  309. }
  310. dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight());
  311. this._connectNode = target;
  312. this.onShow(target, this.position);
  313. },
  314. close: function(){
  315. // summary:
  316. // Hide the tooltip or cancel timer for show of tooltip
  317. // tags:
  318. // private
  319. if(this._connectNode){
  320. // if tooltip is currently shown
  321. dijit.hideTooltip(this._connectNode);
  322. delete this._connectNode;
  323. this.onHide();
  324. }
  325. if(this._showTimer){
  326. // if tooltip is scheduled to be shown (after a brief delay)
  327. clearTimeout(this._showTimer);
  328. delete this._showTimer;
  329. }
  330. },
  331. onShow: function(target, position){
  332. // summary:
  333. // Called when the tooltip is shown
  334. // tags:
  335. // callback
  336. },
  337. onHide: function(){
  338. // summary:
  339. // Called when the tooltip is hidden
  340. // tags:
  341. // callback
  342. },
  343. uninitialize: function(){
  344. this.close();
  345. this.inherited(arguments);
  346. }
  347. }
  348. );
  349. // dijit.Tooltip.defaultPosition: String[]
  350. // This variable controls the position of tooltips, if the position is not specified to
  351. // the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values:
  352. //
  353. // * before: places tooltip to the left of the target node/widget, or to the right in
  354. // the case of RTL scripts like Hebrew and Arabic
  355. // * after: places tooltip to the right of the target node/widget, or to the left in
  356. // the case of RTL scripts like Hebrew and Arabic
  357. // * above: tooltip goes above target node
  358. // * below: tooltip goes below target node
  359. //
  360. // The list is positions is tried, in order, until a position is found where the tooltip fits
  361. // within the viewport.
  362. //
  363. // Be careful setting this parameter. A value of "above" may work fine until the user scrolls
  364. // the screen so that there's no room above the target node. Nodes with drop downs, like
  365. // DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure
  366. // that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there
  367. // is only room below (or above) the target node, but not both.
  368. dijit.Tooltip.defaultPosition = ["after", "before"];
  369. }