]>
git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/src/tip/ToolTip.js
2 * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
3 * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
4 * control over the tooltip's alignment relative to the target element or mouse, and the timing
5 * of when it is automatically shown and hidden.
7 * This implementation does **not** have a built-in method of automatically populating the tooltip's
8 * text based on the target element; you must either configure a fixed {@link #html} value for each
9 * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
10 * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
11 * convenient way of automatically populating and configuring a tooltip based on specific DOM
12 * attributes of each target element.
17 * Ext.getBody().appendChild({
19 * html: 'Clear Button',
20 * style: 'display:inline-block;background:#A2C841;padding:7px;cursor:pointer;'
23 * var tip = Ext.create('Ext.tip.ToolTip', {
24 * target: 'clearButton',
25 * html: 'Press this button to clear the form'
30 * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
31 * one ToolTip to many elements under a common parent. This is more efficient than creating many
32 * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
33 * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
34 * appropriate sub-elements.
36 * When using delegation, it is likely that you will want to programmatically change the content
37 * of the ToolTip based on each delegate element; you can do this by implementing a custom
38 * listener for the {@link #beforeshow} event. Example:
41 * var store = Ext.create('Ext.data.ArrayStore', {
42 * fields: ['company', 'price', 'change'],
44 * ['3m Co', 71.72, 0.02],
45 * ['Alcoa Inc', 29.01, 0.42],
46 * ['Altria Group Inc', 83.81, 0.28],
47 * ['American Express Company', 52.55, 0.01],
48 * ['American International Group, Inc.', 64.13, 0.31],
49 * ['AT&T Inc.', 31.61, -0.48]
53 * var grid = Ext.create('Ext.grid.Panel', {
54 * title: 'Array Grid',
57 * {text: 'Company', flex: 1, dataIndex: 'company'},
58 * {text: 'Price', width: 75, dataIndex: 'price'},
59 * {text: 'Change', width: 75, dataIndex: 'change'}
63 * renderTo: Ext.getBody()
66 * var view = grid.getView();
67 * var tip = Ext.create('Ext.tip.ToolTip', {
68 * // The overall target element.
70 * // Each grid row causes its own separate show and hide.
71 * delegate: view.itemSelector,
72 * // Moving within the row should not hide the tip.
74 * // Render immediately so that tip.body can be referenced prior to the first show.
75 * renderTo: Ext.getBody(),
77 * // Change content dynamically depending on which element triggered the show.
78 * beforeshow: function updateTipBody(tip) {
79 * tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
86 * The following configuration properties allow control over how the ToolTip is aligned relative to
87 * the target element and/or mouse pointer:
90 * - {@link #anchorToTarget}
91 * - {@link #anchorOffset}
92 * - {@link #trackMouse}
93 * - {@link #mouseOffset}
97 * The following configuration properties allow control over how and when the ToolTip is automatically
100 * - {@link #autoHide}
101 * - {@link #showDelay}
102 * - {@link #hideDelay}
103 * - {@link #dismissDelay}
105 Ext
.define('Ext.tip.ToolTip', {
106 extend
: 'Ext.tip.Tip',
107 alias
: 'widget.tooltip',
108 alternateClassName
: 'Ext.ToolTip',
111 * @property {HTMLElement} triggerElement
112 * When a ToolTip is configured with the `{@link #delegate}`
113 * option to cause selected child elements of the `{@link #target}`
114 * Element to each trigger a separate show event, this property is set to
115 * the DOM element which triggered the show.
118 * @cfg {HTMLElement/Ext.dom.Element/String} target
119 * The target element or string id to monitor for mouseover events to trigger
120 * showing this ToolTip.
123 * @cfg {Boolean} [autoHide=true]
124 * True to automatically hide the tooltip after the
125 * mouse exits the target element or after the `{@link #dismissDelay}`
126 * has expired if set. If `{@link #closable} = true`
127 * a close tool button will be rendered into the tooltip header.
132 * @cfg {Number} showDelay
133 * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
137 * @cfg {Number} hideDelay
138 * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
139 * Set to 0 for the tooltip to hide immediately.
143 * @cfg {Number} dismissDelay
144 * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
149 * @cfg {Number[]} [mouseOffset=[15,18]]
150 * An XY offset from the mouse position where the tooltip should be shown.
153 * @cfg {Boolean} trackMouse
154 * True to have the tooltip follow the mouse as it moves over the target element.
158 * @cfg {String} anchor
159 * If specified, indicates that the tip should be anchored to a
160 * particular side of the target element or mouse pointer ("top", "right", "bottom",
161 * or "left"), with an arrow pointing back at the target or mouse pointer. If
162 * {@link #constrainPosition} is enabled, this will be used as a preferred value
163 * only and may be flipped as needed.
166 * @cfg {Boolean} anchorToTarget
167 * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
168 * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
169 * target element. When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
171 anchorToTarget
: true,
173 * @cfg {Number} anchorOffset
174 * A numeric pixel value used to offset the default position of the anchor arrow. When the anchor
175 * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
176 * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
182 * @cfg {String} delegate
184 * A {@link Ext.DomQuery DomQuery} simple selector which allows selection of individual elements within the
185 * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
186 * target. See {@link Ext.dom.Query} for information about simple selectors.
188 * When specified, the child element of the target which caused a show event is placed into the
189 * `{@link #triggerElement}` property before the ToolTip is shown.
191 * This may be useful when a Component has regular, repeating elements in it, each of which need a
192 * ToolTip which contains information specific to that element.
194 * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
202 quickShowInterval
: 250,
205 * @cfg {String} [hideAction="hide"]
206 * The method to use to hide the tooltip. Another useful method for this is `fadeOut`.
211 * @cfg {Number} [fadeOutDuration=1000]
212 * The number of milliseconds for the `fadeOut` animation. Only valid if `hideAction`
213 * is set to `fadeOut`.
215 fadeOutDuration
: 1000,
219 initComponent: function() {
221 me
.callParent(arguments
);
222 me
.lastActive
= new Date();
223 me
.setTarget(me
.target
);
224 me
.origAnchor
= me
.anchor
;
227 onRender: function(ct
, position
) {
229 me
.callParent(arguments
);
230 me
.anchorCls
= Ext
.baseCSSPrefix
+ 'tip-anchor-' + me
.getAnchorPosition();
233 // tell the spec runner to ignore this element when checking if the dom is clean
234 me
.el
.dom
.setAttribute('data-sticky', true);
237 me
.anchorEl
= me
.el
.createChild({
238 role
: 'presentation',
239 cls
: Ext
.baseCSSPrefix
+ 'tip-anchor ' + me
.anchorCls
244 * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
245 * @param {String/HTMLElement/Ext.dom.Element} target The Element, HTMLElement, or
246 * ID of an element to bind to
248 setTarget: function(target
) {
254 tg
= Ext
.get(me
.target
);
255 if (Ext
.supports
.Touch
) {
256 me
.mun(tg
, 'tap', me
.onTargetOver
, me
);
259 mouseover
: me
.onTargetOver
,
260 mouseout
: me
.onTargetOut
,
261 mousemove
: me
.onMouseMove
,
269 if (Ext
.supports
.Touch
) {
271 tap
: me
.onTargetOver
,
276 mouseover
: me
.onTargetOver
,
277 mouseout
: me
.onTargetOut
,
278 mousemove
: me
.onMouseMove
,
284 me
.anchorTarget
= me
.target
;
291 onMouseMove: function(e
) {
296 // If the event target is no longer in this tip's target (possibly due to rapidly churning content in target), ignore it.
297 if (!me
.target
|| me
.target
.contains(e
.target
)) {
298 t
= me
.delegate
? e
.getTarget(me
.delegate
) : (me
.triggerElement
= true);
300 me
.targetXY
= e
.getXY();
301 if (t
=== me
.triggerElement
) {
302 if (!me
.hidden
&& me
.trackMouse
) {
303 xy
= me
.getTargetXY();
304 if (me
.constrainPosition
) {
305 xy
= me
.el
.adjustForConstraints(xy
, me
.el
.parent());
307 me
.setPagePosition(xy
);
311 me
.lastActive
= new Date(0);
314 } else if ((!me
.closable
&& me
.isVisible()) && me
.autoHide
!== false) {
323 getTargetXY: function() {
326 offsets
, xy
, dw
, dh
, de
, bd
, scrollX
, scrollY
, axy
, sz
, constrainPosition
;
328 me
.anchorTarget
= me
.triggerElement
;
332 offsets
= me
.getOffsets();
333 xy
= (me
.anchorToTarget
&& !me
.trackMouse
) ? me
.getAlignToXY(me
.anchorTarget
, me
.getAnchorAlign()) : me
.targetXY
;
334 dw
= Ext
.Element
.getViewportWidth() - 5;
335 dh
= Ext
.Element
.getViewportHeight() - 5;
336 de
= document
.documentElement
;
338 scrollX
= (de
.scrollLeft
|| bd
.scrollLeft
|| 0) + 5;
339 scrollY
= (de
.scrollTop
|| bd
.scrollTop
|| 0) + 5;
340 axy
= [xy
[0] + offsets
[0], xy
[1] + offsets
[1]];
342 constrainPosition
= me
.constrainPosition
;
344 me
.anchorEl
.removeCls(me
.anchorCls
);
346 if (me
.targetCounter
< 2 && constrainPosition
) {
347 if (axy
[0] < scrollX
) {
348 if (me
.anchorToTarget
) {
349 me
.defaultAlign
= 'l-r';
350 if (me
.mouseOffset
) {
351 me
.mouseOffset
[0] *= -1;
355 return me
.getTargetXY();
357 if (axy
[0] + sz
.width
> dw
) {
358 if (me
.anchorToTarget
) {
359 me
.defaultAlign
= 'r-l';
360 if (me
.mouseOffset
) {
361 me
.mouseOffset
[0] *= -1;
365 return me
.getTargetXY();
367 if (axy
[1] < scrollY
) {
368 if (me
.anchorToTarget
) {
369 me
.defaultAlign
= 't-b';
370 if (me
.mouseOffset
) {
371 me
.mouseOffset
[1] *= -1;
375 return me
.getTargetXY();
377 if (axy
[1] + sz
.height
> dh
) {
378 if (me
.anchorToTarget
) {
379 me
.defaultAlign
= 'b-t';
380 if (me
.mouseOffset
) {
381 me
.mouseOffset
[1] *= -1;
384 me
.anchor
= 'bottom';
385 return me
.getTargetXY();
389 me
.anchorCls
= Ext
.baseCSSPrefix
+ 'tip-anchor-' + me
.getAnchorPosition();
390 me
.anchorEl
.addCls(me
.anchorCls
);
391 me
.targetCounter
= 0;
394 mouseOffset
= me
.getMouseOffset();
395 return (me
.targetXY
) ? [me
.targetXY
[0] + mouseOffset
[0], me
.targetXY
[1] + mouseOffset
[1]] : mouseOffset
;
400 * Overrides Positionable's calculateConstrainedPosition to return a value that is
404 calculateConstrainedPosition: function(constrainTo
) {
410 // If this is a floating child, account for the fact that positioning will be relative to it
411 if (!constrainTo
&& me
.isContainedFloater()) {
412 visible
= me
.isVisible();
416 result
= me
.getTargetXY();
420 floatParentBox
= me
.floatParent
.getTargetEl().getViewRegion();
421 result
[0] -= floatParentBox
.left
;
422 result
[1] -= floatParentBox
.top
;
424 result
= me
.callOverridden(arguments
);
429 getMouseOffset: function() {
431 offset
= me
.anchor
? [0, 0] : [15, 18];
432 if (me
.mouseOffset
) {
433 offset
[0] += me
.mouseOffset
[0];
434 offset
[1] += me
.mouseOffset
[1];
439 fadeOut: function () {
443 duration
: me
.fadeOutDuration
,
444 callback: function () {
446 me
.el
.setOpacity('');
454 getAnchorPosition: function() {
458 me
.tipAnchor
= me
.anchor
.charAt(0);
460 m
= me
.defaultAlign
.match(/^([a-z]+)-([a-z]+)(\?)?$/);
463 Ext
.raise('The AnchorTip.defaultAlign value "' + me
.defaultAlign
+ '" is invalid.');
466 me
.tipAnchor
= m
[1].charAt(0);
469 switch (me
.tipAnchor
) {
483 getAnchorAlign: function() {
484 switch (this.anchor
) {
499 getOffsets: function() {
503 ap
= me
.getAnchorPosition().charAt(0);
504 if (me
.anchorToTarget
&& !me
.trackMouse
) {
522 offsets
= [-15 - me
.anchorOffset
, 30];
525 offsets
= [-19 - me
.anchorOffset
, -13 - me
.el
.dom
.offsetHeight
];
528 offsets
= [-15 - me
.el
.dom
.offsetWidth
, -13 - me
.anchorOffset
];
531 offsets
= [25, -13 - me
.anchorOffset
];
535 mouseOffset
= me
.getMouseOffset();
536 offsets
[0] += mouseOffset
[0];
537 offsets
[1] += mouseOffset
[1];
545 onTargetOver: function(e
) {
547 delegate
= me
.delegate
,
550 if (me
.disabled
|| e
.within(me
.target
.dom
, true)) {
553 t
= delegate
? e
.getTarget(delegate
) : true;
555 me
.triggerElement
= t
;
557 me
.clearTimer('hide');
558 me
.targetXY
= e
.getXY();
566 delayShow: function (trackMouse
) {
567 // When delaying, cache the XY coords of the mouse when this method was invoked, NOT when the deferred
568 // show is called because the mouse could then be in a completely different location. Only cache the
569 // coords when trackMouse is false.
571 // Note that the delayShow call could be coming from a caller which would internally be setting trackMouse
572 // (e.g., Ext.chart.Tip:showTip()). Because of this, the caller will pass along the original value for
573 // trackMouse (i.e., the value passed to the component constructor) to the delayShow method.
574 // See EXTJSIV-11292.
576 xy
= me
.el
&& (trackMouse
=== false || !me
.trackMouse
) && me
.getTargetXY();
578 if (me
.hidden
&& !me
.showTimer
) {
579 if (Ext
.Date
.getElapsed(me
.lastActive
) < me
.quickShowInterval
) {
582 me
.showTimer
= Ext
.defer(me
.showFromDelay
, me
.showDelay
, me
, [xy
]);
585 else if (!me
.hidden
&& me
.autoHide
!== false) {
590 showFromDelay: function (xy
) {
592 // Need to check this here since onDisable only gets called after render, which
593 // the show call below may trigger
598 me
.fromDelayShow
= true;
600 delete me
.fromDelayShow
;
603 onShowVeto: function(){
605 delete this.triggerElement
;
606 this.clearTimer('show');
612 onTargetOut: function(e
) {
614 triggerEl
= me
.triggerElement
,
615 // If we don't have a delegate, then the target is set
616 // to true, so set it to the main target.
617 target
= triggerEl
=== true ? me
.target
: triggerEl
;
619 // If disabled, moving within the current target, ignore the mouseout
620 // e.within is the only correct way to determine this.
621 if (me
.disabled
|| !triggerEl
|| e
.within(target
, true)) {
625 me
.clearTimer('show');
626 me
.triggerElement
= null;
628 if (me
.autoHide
!== false) {
636 delayHide: function() {
639 if (!me
.hidden
&& !me
.hideTimer
) {
640 me
.hideTimer
= Ext
.defer(me
[me
.hideAction
], me
.hideDelay
, me
);
645 * Hides this tooltip if visible.
649 me
.clearTimer('dismiss');
650 me
.lastActive
= new Date();
654 me
.callParent(arguments
);
655 delete me
.triggerElement
;
659 * Shows this tooltip at the current event target XY position.
661 show: function (xy
) {
664 // Show this Component first, so that sizing can be calculated
665 // pre-show it off screen so that the el will have dimensions
667 if (this.hidden
=== false) {
669 me
.anchor
= me
.origAnchor
;
672 if (!me
.calledFromShowAt
) {
673 // If the caller was this.showFromDelay(), the XY coords may have been cached.
674 me
.showAt(xy
|| me
.getTargetXY());
682 showAt: function(xy
) {
684 me
.lastActive
= new Date();
686 me
.calledFromShowAt
= true;
688 // Only call if this is hidden. May have been called from show above.
689 if (!me
.isVisible()) {
690 this.callParent(arguments
);
693 // Show may have been vetoed.
694 if (me
.isVisible()) {
695 me
.setPagePosition(xy
[0], xy
[1]);
696 if (me
.constrainPosition
|| me
.constrain
) {
700 me
.el
.syncUnderlays();
701 if (me
.dismissDelay
&& me
.autoHide
!== false) {
702 me
.dismissTimer
= Ext
.defer(me
.hide
, me
.dismissDelay
, me
);
705 delete me
.calledFromShowAt
;
711 syncAnchor: function() {
716 switch (me
.tipAnchor
.charAt(0)) {
720 offset
= [20 + me
.anchorOffset
, 1];
725 offset
= [ - 1, 12 + me
.anchorOffset
];
730 offset
= [20 + me
.anchorOffset
, -1];
735 offset
= [1, 12 + me
.anchorOffset
];
738 me
.anchorEl
.alignTo(me
.el
, anchorPos
+ '-' + targetPos
, offset
);
739 me
.anchorEl
.setStyle('z-index', parseInt(me
.el
.getZIndex(), 10) || 0 + 1).setVisibilityMode(Ext
.Element
.DISPLAY
);
742 afterSetPosition: function(x
, y
) {
744 me
.callParent(arguments
);
747 if (!me
.anchorEl
.isVisible()) {
760 clearTimer: function (name
) {
762 names
= me
._timerNames
,
763 propName
= names
[name
] || (names
[name
] = name
+ 'Timer'),
764 timer
= me
[propName
];
775 clearTimers: function() {
777 me
.clearTimer('show');
778 me
.clearTimer('dismiss');
779 me
.clearTimer('hide');
785 me
.mon(Ext
.getDoc(), 'mousedown', me
.onDocMouseDown
, me
);
791 me
.mun(Ext
.getDoc(), 'mousedown', me
.onDocMouseDown
, me
);
797 onDocMouseDown: function(e
) {
799 if (!me
.closable
&& !e
.within(me
.el
.dom
)) {
801 Ext
.defer(me
.doEnable
, 100, me
);
808 doEnable: function() {
809 if (!this.destroyed
) {
814 onDisable: function() {
820 beforeDestroy: function() {
823 Ext
.destroy(me
.anchorEl
);
826 delete me
.anchorTarget
;
827 delete me
.triggerElement
;
831 onDestroy: function() {
832 Ext
.getDoc().un('mousedown', this.onDocMouseDown
, this);