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