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