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