source: Dev/branches/rest-dojo-ui/client/dijit/Tooltip.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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