3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
17 * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
18 * control over the tooltip's alignment relative to the target element or mouse, and the timing
19 * of when it is automatically shown and hidden.
21 * This implementation does **not** have a built-in method of automatically populating the tooltip's
22 * text based on the target element; you must either configure a fixed {@link #html} value for each
23 * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
24 * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
25 * convenient way of automatically populating and configuring a tooltip based on specific DOM
26 * attributes of each target element.
30 * var tip = Ext.create('Ext.tip.ToolTip', {
31 * target: 'clearButton',
32 * html: 'Press this button to clear the form'
35 * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
39 * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
40 * one ToolTip to many elements under a common parent. This is more efficient than creating many
41 * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
42 * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
43 * appropriate sub-elements.
45 * When using delegation, it is likely that you will want to programmatically change the content
46 * of the ToolTip based on each delegate element; you can do this by implementing a custom
47 * listener for the {@link #beforeshow} event. Example:
49 * var store = Ext.create('Ext.data.ArrayStore', {
50 * fields: ['company', 'price', 'change'],
52 * ['3m Co', 71.72, 0.02],
53 * ['Alcoa Inc', 29.01, 0.42],
54 * ['Altria Group Inc', 83.81, 0.28],
55 * ['American Express Company', 52.55, 0.01],
56 * ['American International Group, Inc.', 64.13, 0.31],
57 * ['AT&T Inc.', 31.61, -0.48]
61 * var grid = Ext.create('Ext.grid.Panel', {
62 * title: 'Array Grid',
65 * {text: 'Company', flex: 1, dataIndex: 'company'},
66 * {text: 'Price', width: 75, dataIndex: 'price'},
67 * {text: 'Change', width: 75, dataIndex: 'change'}
71 * renderTo: Ext.getBody()
74 * grid.getView().on('render', function(view) {
75 * view.tip = Ext.create('Ext.tip.ToolTip', {
76 * // The overall target element.
78 * // Each grid row causes its own seperate show and hide.
79 * delegate: view.itemSelector,
80 * // Moving within the row should not hide the tip.
82 * // Render immediately so that tip.body can be referenced prior to the first show.
83 * renderTo: Ext.getBody(),
85 * // Change content dynamically depending on which element triggered the show.
86 * beforeshow: function updateTipBody(tip) {
87 * tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
93 * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
97 * The following configuration properties allow control over how the ToolTip is aligned relative to
98 * the target element and/or mouse pointer:
101 * - {@link #anchorToTarget}
102 * - {@link #anchorOffset}
103 * - {@link #trackMouse}
104 * - {@link #mouseOffset}
108 * The following configuration properties allow control over how and when the ToolTip is automatically
111 * - {@link #autoHide}
112 * - {@link #showDelay}
113 * - {@link #hideDelay}
114 * - {@link #dismissDelay}
116 * @docauthor Jason Johnston <jason@sencha.com>
118 Ext.define('Ext.tip.ToolTip', {
119 extend: 'Ext.tip.Tip',
120 alias: 'widget.tooltip',
121 alternateClassName: 'Ext.ToolTip',
123 * @property {HTMLElement} triggerElement
124 * When a ToolTip is configured with the `{@link #delegate}`
125 * option to cause selected child elements of the `{@link #target}`
126 * Element to each trigger a seperate show event, this property is set to
127 * the DOM element which triggered the show.
130 * @cfg {HTMLElement/Ext.Element/String} target
131 * The target element or string id to monitor for mouseover events to trigger
132 * showing this ToolTip.
135 * @cfg {Boolean} [autoHide=true]
136 * True to automatically hide the tooltip after the
137 * mouse exits the target element or after the `{@link #dismissDelay}`
138 * has expired if set. If `{@link #closable} = true`
139 * a close tool button will be rendered into the tooltip header.
142 * @cfg {Number} showDelay
143 * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
147 * @cfg {Number} hideDelay
148 * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
149 * Set to 0 for the tooltip to hide immediately.
153 * @cfg {Number} dismissDelay
154 * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
159 * @cfg {Number[]} [mouseOffset=[15,18]]
160 * An XY offset from the mouse position where the tooltip should be shown.
163 * @cfg {Boolean} trackMouse
164 * True to have the tooltip follow the mouse as it moves over the target element.
168 * @cfg {String} anchor
169 * If specified, indicates that the tip should be anchored to a
170 * particular side of the target element or mouse pointer ("top", "right", "bottom",
171 * or "left"), with an arrow pointing back at the target or mouse pointer. If
172 * {@link #constrainPosition} is enabled, this will be used as a preferred value
173 * only and may be flipped as needed.
176 * @cfg {Boolean} anchorToTarget
177 * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
178 * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
179 * target element. When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
181 anchorToTarget: true,
183 * @cfg {Number} anchorOffset
184 * A numeric pixel value used to offset the default position of the anchor arrow. When the anchor
185 * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
186 * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
191 * @cfg {String} delegate
193 * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
194 * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
197 * When specified, the child element of the target which caused a show event is placed into the
198 * `{@link #triggerElement}` property before the ToolTip is shown.
200 * This may be useful when a Component has regular, repeating elements in it, each of which need a
201 * ToolTip which contains information specific to that element.
203 * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
208 quickShowInterval: 250,
211 initComponent: function() {
213 me.callParent(arguments);
214 me.lastActive = new Date();
215 me.setTarget(me.target);
216 me.origAnchor = me.anchor;
220 onRender: function(ct, position) {
222 me.callParent(arguments);
223 me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
224 me.anchorEl = me.el.createChild({
225 cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
230 afterRender: function() {
234 me.callParent(arguments);
235 zIndex = parseInt(me.el.getZIndex(), 10) || 0;
236 me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
240 * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
241 * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
243 setTarget: function(target) {
249 tg = Ext.get(me.target);
250 me.mun(tg, 'mouseover', me.onTargetOver, me);
251 me.mun(tg, 'mouseout', me.onTargetOut, me);
252 me.mun(tg, 'mousemove', me.onMouseMove, me);
259 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
260 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
263 mouseover: me.onTargetOver,
264 mouseout: me.onTargetOut,
265 mousemove: me.onMouseMove,
270 me.anchorTarget = me.target;
275 onMouseMove: function(e) {
277 t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
280 me.targetXY = e.getXY();
281 if (t === me.triggerElement) {
282 if (!me.hidden && me.trackMouse) {
283 xy = me.getTargetXY();
284 if (me.constrainPosition) {
285 xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
287 me.setPagePosition(xy);
291 me.lastActive = new Date(0);
294 } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
300 getTargetXY: function() {
304 me.anchorTarget = me.triggerElement;
308 var offsets = me.getOffsets(),
309 xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
310 dw = Ext.Element.getViewWidth() - 5,
311 dh = Ext.Element.getViewHeight() - 5,
312 de = document.documentElement,
314 scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
315 scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
316 axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
318 constrainPosition = me.constrainPosition;
320 me.anchorEl.removeCls(me.anchorCls);
322 if (me.targetCounter < 2 && constrainPosition) {
323 if (axy[0] < scrollX) {
324 if (me.anchorToTarget) {
325 me.defaultAlign = 'l-r';
326 if (me.mouseOffset) {
327 me.mouseOffset[0] *= -1;
331 return me.getTargetXY();
333 if (axy[0] + sz.width > dw) {
334 if (me.anchorToTarget) {
335 me.defaultAlign = 'r-l';
336 if (me.mouseOffset) {
337 me.mouseOffset[0] *= -1;
341 return me.getTargetXY();
343 if (axy[1] < scrollY) {
344 if (me.anchorToTarget) {
345 me.defaultAlign = 't-b';
346 if (me.mouseOffset) {
347 me.mouseOffset[1] *= -1;
351 return me.getTargetXY();
353 if (axy[1] + sz.height > dh) {
354 if (me.anchorToTarget) {
355 me.defaultAlign = 'b-t';
356 if (me.mouseOffset) {
357 me.mouseOffset[1] *= -1;
360 me.anchor = 'bottom';
361 return me.getTargetXY();
365 me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
366 me.anchorEl.addCls(me.anchorCls);
367 me.targetCounter = 0;
370 mouseOffset = me.getMouseOffset();
371 return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
375 getMouseOffset: function() {
377 offset = me.anchor ? [0, 0] : [15, 18];
378 if (me.mouseOffset) {
379 offset[0] += me.mouseOffset[0];
380 offset[1] += me.mouseOffset[1];
386 getAnchorPosition: function() {
390 me.tipAnchor = me.anchor.charAt(0);
392 m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
395 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
398 me.tipAnchor = m[1].charAt(0);
401 switch (me.tipAnchor) {
413 getAnchorAlign: function() {
414 switch (this.anchor) {
427 getOffsets: function() {
431 ap = me.getAnchorPosition().charAt(0);
432 if (me.anchorToTarget && !me.trackMouse) {
441 offsets = [ - 13, 0];
450 offsets = [ - 15 - me.anchorOffset, 30];
453 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
456 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
459 offsets = [25, -13 - me.anchorOffset];
463 mouseOffset = me.getMouseOffset();
464 offsets[0] += mouseOffset[0];
465 offsets[1] += mouseOffset[1];
471 onTargetOver: function(e) {
475 if (me.disabled || e.within(me.target.dom, true)) {
478 t = e.getTarget(me.delegate);
480 me.triggerElement = t;
481 me.clearTimer('hide');
482 me.targetXY = e.getXY();
488 delayShow: function() {
490 if (me.hidden && !me.showTimer) {
491 if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
494 me.showTimer = Ext.defer(me.show, me.showDelay, me);
497 else if (!me.hidden && me.autoHide !== false) {
503 onTargetOut: function(e) {
505 if (me.disabled || e.within(me.target.dom, true)) {
508 me.clearTimer('show');
509 if (me.autoHide !== false) {
515 delayHide: function() {
517 if (!me.hidden && !me.hideTimer) {
518 me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
523 * Hides this tooltip if visible.
527 me.clearTimer('dismiss');
528 me.lastActive = new Date();
532 me.callParent(arguments);
533 delete me.triggerElement;
537 * Shows this tooltip at the current event target XY position.
542 // Show this Component first, so that sizing can be calculated
543 // pre-show it off screen so that the el will have dimensions
545 if (this.hidden === false) {
546 me.setPagePosition(-10000, -10000);
549 me.anchor = me.origAnchor;
551 me.showAt(me.getTargetXY());
563 showAt: function(xy) {
565 me.lastActive = new Date();
568 // Only call if this is hidden. May have been called from show above.
569 if (!me.isVisible()) {
570 this.callParent(arguments);
573 // Show may have been vetoed.
574 if (me.isVisible()) {
575 me.setPagePosition(xy[0], xy[1]);
576 if (me.constrainPosition || me.constrain) {
582 if (me.dismissDelay && me.autoHide !== false) {
583 me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
587 if (!me.anchorEl.isVisible()) {
596 syncAnchor: function() {
601 switch (me.tipAnchor.charAt(0)) {
605 offset = [20 + me.anchorOffset, 1];
610 offset = [ - 1, 12 + me.anchorOffset];
615 offset = [20 + me.anchorOffset, -1];
620 offset = [1, 12 + me.anchorOffset];
623 me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
627 setPagePosition: function(x, y) {
629 me.callParent(arguments);
636 clearTimer: function(name) {
637 name = name + 'Timer';
638 clearTimeout(this[name]);
643 clearTimers: function() {
645 me.clearTimer('show');
646 me.clearTimer('dismiss');
647 me.clearTimer('hide');
654 me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
661 me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
665 onDocMouseDown: function(e) {
667 if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
669 Ext.defer(me.doEnable, 100, me);
674 doEnable: function() {
675 if (!this.isDestroyed) {
681 onDisable: function() {
687 beforeDestroy: function() {
690 Ext.destroy(me.anchorEl);
693 delete me.anchorTarget;
694 delete me.triggerElement;
699 onDestroy: function() {
700 Ext.getDoc().un('mousedown', this.onDocMouseDown, this);