Tooltip.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. require({cache:{
  2. 'url:dijit/templates/Tooltip.html':"<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" data-dojo-attach-point=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" data-dojo-attach-point=\"connectorNode\"></div\n></div>\n"}});
  3. define("dijit/Tooltip", [
  4. "dojo/_base/array", // array.forEach array.indexOf array.map
  5. "dojo/_base/declare", // declare
  6. "dojo/_base/fx", // fx.fadeIn fx.fadeOut
  7. "dojo/dom", // dom.byId
  8. "dojo/dom-class", // domClass.add
  9. "dojo/dom-geometry", // domGeometry.position
  10. "dojo/dom-style", // domStyle.set, domStyle.get
  11. "dojo/_base/lang", // lang.hitch lang.isArrayLike
  12. "dojo/_base/sniff", // has("ie")
  13. "dojo/_base/window", // win.body
  14. "./_base/manager", // manager.defaultDuration
  15. "./place",
  16. "./_Widget",
  17. "./_TemplatedMixin",
  18. "./BackgroundIframe",
  19. "dojo/text!./templates/Tooltip.html",
  20. "." // sets dijit.showTooltip etc. for back-compat
  21. ], function(array, declare, fx, dom, domClass, domGeometry, domStyle, lang, has, win,
  22. manager, place, _Widget, _TemplatedMixin, BackgroundIframe, template, dijit){
  23. /*=====
  24. var _Widget = dijit._Widget;
  25. var BackgroundIframe = dijit.BackgroundIframe;
  26. var _TemplatedMixin = dijit._TemplatedMixin;
  27. =====*/
  28. // module:
  29. // dijit/Tooltip
  30. // summary:
  31. // Defines dijit.Tooltip widget (to display a tooltip), showTooltip()/hideTooltip(), and _MasterTooltip
  32. var MasterTooltip = declare("dijit._MasterTooltip", [_Widget, _TemplatedMixin], {
  33. // summary:
  34. // Internal widget that holds the actual tooltip markup,
  35. // which occurs once per page.
  36. // Called by Tooltip widgets which are just containers to hold
  37. // the markup
  38. // tags:
  39. // protected
  40. // duration: Integer
  41. // Milliseconds to fade in/fade out
  42. duration: manager.defaultDuration,
  43. templateString: template,
  44. postCreate: function(){
  45. win.body().appendChild(this.domNode);
  46. this.bgIframe = new BackgroundIframe(this.domNode);
  47. // Setup fade-in and fade-out functions.
  48. this.fadeIn = fx.fadeIn({ node: this.domNode, duration: this.duration, onEnd: lang.hitch(this, "_onShow") });
  49. this.fadeOut = fx.fadeOut({ node: this.domNode, duration: this.duration, onEnd: lang.hitch(this, "_onHide") });
  50. },
  51. show: function(innerHTML, aroundNode, position, rtl, textDir){
  52. // summary:
  53. // Display tooltip w/specified contents to right of specified node
  54. // (To left if there's no space on the right, or if rtl == true)
  55. // innerHTML: String
  56. // Contents of the tooltip
  57. // aroundNode: DomNode || dijit.__Rectangle
  58. // Specifies that tooltip should be next to this node / area
  59. // position: String[]?
  60. // List of positions to try to position tooltip (ex: ["right", "above"])
  61. // rtl: Boolean?
  62. // Corresponds to `WidgetBase.dir` attribute, where false means "ltr" and true
  63. // means "rtl"; specifies GUI direction, not text direction.
  64. // textDir: String?
  65. // Corresponds to `WidgetBase.textdir` attribute; specifies direction of text.
  66. if(this.aroundNode && this.aroundNode === aroundNode && this.containerNode.innerHTML == innerHTML){
  67. return;
  68. }
  69. // reset width; it may have been set by orient() on a previous tooltip show()
  70. this.domNode.width = "auto";
  71. if(this.fadeOut.status() == "playing"){
  72. // previous tooltip is being hidden; wait until the hide completes then show new one
  73. this._onDeck=arguments;
  74. return;
  75. }
  76. this.containerNode.innerHTML=innerHTML;
  77. if(textDir){
  78. this.set("textDir", textDir);
  79. }
  80. this.containerNode.align = rtl? "right" : "left"; //fix the text alignment
  81. var pos = place.around(this.domNode, aroundNode,
  82. position && position.length ? position : Tooltip.defaultPosition, !rtl, lang.hitch(this, "orient"));
  83. // Position the tooltip connector for middle alignment.
  84. // This could not have been done in orient() since the tooltip wasn't positioned at that time.
  85. var aroundNodeCoords = pos.aroundNodePos;
  86. if(pos.corner.charAt(0) == 'M' && pos.aroundCorner.charAt(0) == 'M'){
  87. this.connectorNode.style.top = aroundNodeCoords.y + ((aroundNodeCoords.h - this.connectorNode.offsetHeight) >> 1) - pos.y + "px";
  88. this.connectorNode.style.left = "";
  89. }else if(pos.corner.charAt(1) == 'M' && pos.aroundCorner.charAt(1) == 'M'){
  90. this.connectorNode.style.left = aroundNodeCoords.x + ((aroundNodeCoords.w - this.connectorNode.offsetWidth) >> 1) - pos.x + "px";
  91. }
  92. // show it
  93. domStyle.set(this.domNode, "opacity", 0);
  94. this.fadeIn.play();
  95. this.isShowingNow = true;
  96. this.aroundNode = aroundNode;
  97. },
  98. orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){
  99. // summary:
  100. // Private function to set CSS for tooltip node based on which position it's in.
  101. // This is called by the dijit popup code. It will also reduce the tooltip's
  102. // width to whatever width is available
  103. // tags:
  104. // protected
  105. this.connectorNode.style.top = ""; //reset to default
  106. // Adjust for space taking by tooltip connector.
  107. // Take care not to modify the original spaceAvailable arg as that confuses the caller (dijit.place).
  108. var heightAvailable = spaceAvailable.h,
  109. widthAvailable = spaceAvailable.w;
  110. if(aroundCorner.charAt(1) != tooltipCorner.charAt(1)){
  111. // left/right tooltip
  112. widthAvailable -= this.connectorNode.offsetWidth;
  113. }else{
  114. // above/below tooltip
  115. heightAvailable -= this.connectorNode.offsetHeight;
  116. }
  117. node.className = "dijitTooltip " +
  118. {
  119. "MR-ML": "dijitTooltipRight",
  120. "ML-MR": "dijitTooltipLeft",
  121. "TM-BM": "dijitTooltipAbove",
  122. "BM-TM": "dijitTooltipBelow",
  123. "BL-TL": "dijitTooltipBelow dijitTooltipABLeft",
  124. "TL-BL": "dijitTooltipAbove dijitTooltipABLeft",
  125. "BR-TR": "dijitTooltipBelow dijitTooltipABRight",
  126. "TR-BR": "dijitTooltipAbove dijitTooltipABRight",
  127. "BR-BL": "dijitTooltipRight",
  128. "BL-BR": "dijitTooltipLeft"
  129. }[aroundCorner + "-" + tooltipCorner];
  130. // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen
  131. this.domNode.style.width = "auto";
  132. var size = domGeometry.getContentBox(this.domNode);
  133. var width = Math.min((Math.max(widthAvailable,1)), size.w);
  134. var widthWasReduced = width < size.w;
  135. this.domNode.style.width = width+"px";
  136. // Reposition the tooltip connector.
  137. if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){
  138. var bb = domGeometry.position(node);
  139. var tooltipConnectorHeight = this.connectorNode.offsetHeight;
  140. if(bb.h > heightAvailable){
  141. // The tooltip starts at the top of the page and will extend past the aroundNode
  142. var aroundNodePlacement = heightAvailable - ((aroundNodeCoords.h + tooltipConnectorHeight) >> 1);
  143. this.connectorNode.style.top = aroundNodePlacement + "px";
  144. this.connectorNode.style.bottom = "";
  145. }else{
  146. // Align center of connector with center of aroundNode, except don't let bottom
  147. // of connector extend below bottom of tooltip content, or top of connector
  148. // extend past top of tooltip content
  149. this.connectorNode.style.bottom = Math.min(
  150. Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0),
  151. bb.h - tooltipConnectorHeight) + "px";
  152. this.connectorNode.style.top = "";
  153. }
  154. }else{
  155. // reset the tooltip back to the defaults
  156. this.connectorNode.style.top = "";
  157. this.connectorNode.style.bottom = "";
  158. }
  159. return Math.max(0, size.w - widthAvailable);
  160. },
  161. _onShow: function(){
  162. // summary:
  163. // Called at end of fade-in operation
  164. // tags:
  165. // protected
  166. if(has("ie")){
  167. // the arrow won't show up on a node w/an opacity filter
  168. this.domNode.style.filter="";
  169. }
  170. },
  171. hide: function(aroundNode){
  172. // summary:
  173. // Hide the tooltip
  174. if(this._onDeck && this._onDeck[1] == aroundNode){
  175. // this hide request is for a show() that hasn't even started yet;
  176. // just cancel the pending show()
  177. this._onDeck=null;
  178. }else if(this.aroundNode === aroundNode){
  179. // this hide request is for the currently displayed tooltip
  180. this.fadeIn.stop();
  181. this.isShowingNow = false;
  182. this.aroundNode = null;
  183. this.fadeOut.play();
  184. }else{
  185. // just ignore the call, it's for a tooltip that has already been erased
  186. }
  187. },
  188. _onHide: function(){
  189. // summary:
  190. // Called at end of fade-out operation
  191. // tags:
  192. // protected
  193. this.domNode.style.cssText=""; // to position offscreen again
  194. this.containerNode.innerHTML="";
  195. if(this._onDeck){
  196. // a show request has been queued up; do it now
  197. this.show.apply(this, this._onDeck);
  198. this._onDeck=null;
  199. }
  200. },
  201. _setAutoTextDir: function(/*Object*/node){
  202. // summary:
  203. // Resolve "auto" text direction for children nodes
  204. // tags:
  205. // private
  206. this.applyTextDir(node, has("ie") ? node.outerText : node.textContent);
  207. array.forEach(node.children, function(child){this._setAutoTextDir(child); }, this);
  208. },
  209. _setTextDirAttr: function(/*String*/ textDir){
  210. // summary:
  211. // Setter for textDir.
  212. // description:
  213. // Users shouldn't call this function; they should be calling
  214. // set('textDir', value)
  215. // tags:
  216. // private
  217. this._set("textDir", textDir);
  218. if (textDir == "auto"){
  219. this._setAutoTextDir(this.containerNode);
  220. }else{
  221. this.containerNode.dir = this.textDir;
  222. }
  223. }
  224. });
  225. dijit.showTooltip = function(innerHTML, aroundNode, position, rtl, textDir){
  226. // summary:
  227. // Static method to display tooltip w/specified contents in specified position.
  228. // See description of dijit.Tooltip.defaultPosition for details on position parameter.
  229. // If position is not specified then dijit.Tooltip.defaultPosition is used.
  230. // innerHTML: String
  231. // Contents of the tooltip
  232. // aroundNode: dijit.__Rectangle
  233. // Specifies that tooltip should be next to this node / area
  234. // position: String[]?
  235. // List of positions to try to position tooltip (ex: ["right", "above"])
  236. // rtl: Boolean?
  237. // Corresponds to `WidgetBase.dir` attribute, where false means "ltr" and true
  238. // means "rtl"; specifies GUI direction, not text direction.
  239. // textDir: String?
  240. // Corresponds to `WidgetBase.textdir` attribute; specifies direction of text.
  241. // after/before don't work, but they used to, so for back-compat convert them to after-centered, before-centered
  242. if(position){
  243. position = array.map(position, function(val){
  244. return {after: "after-centered", before: "before-centered"}[val] || val;
  245. });
  246. }
  247. if(!Tooltip._masterTT){ dijit._masterTT = Tooltip._masterTT = new MasterTooltip(); }
  248. return Tooltip._masterTT.show(innerHTML, aroundNode, position, rtl, textDir);
  249. };
  250. dijit.hideTooltip = function(aroundNode){
  251. // summary:
  252. // Static method to hide the tooltip displayed via showTooltip()
  253. return Tooltip._masterTT && Tooltip._masterTT.hide(aroundNode);
  254. };
  255. var Tooltip = declare("dijit.Tooltip", _Widget, {
  256. // summary:
  257. // Pops up a tooltip (a help message) when you hover over a node.
  258. // label: String
  259. // Text to display in the tooltip.
  260. // Specified as innerHTML when creating the widget from markup.
  261. label: "",
  262. // showDelay: Integer
  263. // Number of milliseconds to wait after hovering over/focusing on the object, before
  264. // the tooltip is displayed.
  265. showDelay: 400,
  266. // connectId: String|String[]
  267. // Id of domNode(s) to attach the tooltip to.
  268. // When user hovers over specified dom node, the tooltip will appear.
  269. connectId: [],
  270. // position: String[]
  271. // See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
  272. position: [],
  273. _setConnectIdAttr: function(/*String|String[]*/ newId){
  274. // summary:
  275. // Connect to specified node(s)
  276. // Remove connections to old nodes (if there are any)
  277. array.forEach(this._connections || [], function(nested){
  278. array.forEach(nested, lang.hitch(this, "disconnect"));
  279. }, this);
  280. // Make array of id's to connect to, excluding entries for nodes that don't exist yet, see startup()
  281. this._connectIds = array.filter(lang.isArrayLike(newId) ? newId : (newId ? [newId] : []),
  282. function(id){ return dom.byId(id); });
  283. // Make connections
  284. this._connections = array.map(this._connectIds, function(id){
  285. var node = dom.byId(id);
  286. return [
  287. this.connect(node, "onmouseenter", "_onHover"),
  288. this.connect(node, "onmouseleave", "_onUnHover"),
  289. this.connect(node, "onfocus", "_onHover"),
  290. this.connect(node, "onblur", "_onUnHover")
  291. ];
  292. }, this);
  293. this._set("connectId", newId);
  294. },
  295. addTarget: function(/*DOMNODE || String*/ node){
  296. // summary:
  297. // Attach tooltip to specified node if it's not already connected
  298. // TODO: remove in 2.0 and just use set("connectId", ...) interface
  299. var id = node.id || node;
  300. if(array.indexOf(this._connectIds, id) == -1){
  301. this.set("connectId", this._connectIds.concat(id));
  302. }
  303. },
  304. removeTarget: function(/*DomNode || String*/ node){
  305. // summary:
  306. // Detach tooltip from specified node
  307. // TODO: remove in 2.0 and just use set("connectId", ...) interface
  308. var id = node.id || node, // map from DOMNode back to plain id string
  309. idx = array.indexOf(this._connectIds, id);
  310. if(idx >= 0){
  311. // remove id (modifies original this._connectIds but that's OK in this case)
  312. this._connectIds.splice(idx, 1);
  313. this.set("connectId", this._connectIds);
  314. }
  315. },
  316. buildRendering: function(){
  317. this.inherited(arguments);
  318. domClass.add(this.domNode,"dijitTooltipData");
  319. },
  320. startup: function(){
  321. this.inherited(arguments);
  322. // If this tooltip was created in a template, or for some other reason the specified connectId[s]
  323. // didn't exist during the widget's initialization, then connect now.
  324. var ids = this.connectId;
  325. array.forEach(lang.isArrayLike(ids) ? ids : [ids], this.addTarget, this);
  326. },
  327. _onHover: function(/*Event*/ e){
  328. // summary:
  329. // Despite the name of this method, it actually handles both hover and focus
  330. // events on the target node, setting a timer to show the tooltip.
  331. // tags:
  332. // private
  333. if(!this._showTimer){
  334. var target = e.target;
  335. this._showTimer = setTimeout(lang.hitch(this, function(){this.open(target)}), this.showDelay);
  336. }
  337. },
  338. _onUnHover: function(/*Event*/ /*===== e =====*/){
  339. // summary:
  340. // Despite the name of this method, it actually handles both mouseleave and blur
  341. // events on the target node, hiding the tooltip.
  342. // tags:
  343. // private
  344. // keep a tooltip open if the associated element still has focus (even though the
  345. // mouse moved away)
  346. if(this._focus){ return; }
  347. if(this._showTimer){
  348. clearTimeout(this._showTimer);
  349. delete this._showTimer;
  350. }
  351. this.close();
  352. },
  353. open: function(/*DomNode*/ target){
  354. // summary:
  355. // Display the tooltip; usually not called directly.
  356. // tags:
  357. // private
  358. if(this._showTimer){
  359. clearTimeout(this._showTimer);
  360. delete this._showTimer;
  361. }
  362. Tooltip.show(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight(), this.textDir);
  363. this._connectNode = target;
  364. this.onShow(target, this.position);
  365. },
  366. close: function(){
  367. // summary:
  368. // Hide the tooltip or cancel timer for show of tooltip
  369. // tags:
  370. // private
  371. if(this._connectNode){
  372. // if tooltip is currently shown
  373. Tooltip.hide(this._connectNode);
  374. delete this._connectNode;
  375. this.onHide();
  376. }
  377. if(this._showTimer){
  378. // if tooltip is scheduled to be shown (after a brief delay)
  379. clearTimeout(this._showTimer);
  380. delete this._showTimer;
  381. }
  382. },
  383. onShow: function(/*===== target, position =====*/){
  384. // summary:
  385. // Called when the tooltip is shown
  386. // tags:
  387. // callback
  388. },
  389. onHide: function(){
  390. // summary:
  391. // Called when the tooltip is hidden
  392. // tags:
  393. // callback
  394. },
  395. uninitialize: function(){
  396. this.close();
  397. this.inherited(arguments);
  398. }
  399. });
  400. Tooltip._MasterTooltip = MasterTooltip; // for monkey patching
  401. Tooltip.show = dijit.showTooltip; // export function through module return value
  402. Tooltip.hide = dijit.hideTooltip; // export function through module return value
  403. // dijit.Tooltip.defaultPosition: String[]
  404. // This variable controls the position of tooltips, if the position is not specified to
  405. // the Tooltip widget or *TextBox widget itself. It's an array of strings with the values
  406. // possible for `dijit/place::around()`. The recommended values are:
  407. //
  408. // * before-centered: centers tooltip to the left of the anchor node/widget, or to the right
  409. // in the case of RTL scripts like Hebrew and Arabic
  410. // * after-centered: centers tooltip to the right of the anchor node/widget, or to the left
  411. // in the case of RTL scripts like Hebrew and Arabic
  412. // * above-centered: tooltip is centered above anchor node
  413. // * below-centered: tooltip is centered above anchor node
  414. //
  415. // The list is positions is tried, in order, until a position is found where the tooltip fits
  416. // within the viewport.
  417. //
  418. // Be careful setting this parameter. A value of "above-centered" may work fine until the user scrolls
  419. // the screen so that there's no room above the target node. Nodes with drop downs, like
  420. // DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure
  421. // that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there
  422. // is only room below (or above) the target node, but not both.
  423. Tooltip.defaultPosition = ["after-centered", "before-centered"];
  424. return Tooltip;
  425. });