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 * When a ToolTip is configured with the `{@link #delegate}`
124 * option to cause selected child elements of the `{@link #target}`
125 * Element to each trigger a seperate show event, this property is set to
126 * the DOM element which triggered the show.
128 * @property triggerElement
131 * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to monitor
132 * for mouseover events to trigger showing this ToolTip.
135 * @cfg {Boolean} autoHide True to automatically hide the tooltip after the
136 * mouse exits the target element or after the `{@link #dismissDelay}`
137 * has expired if set (defaults to true). If `{@link #closable} = true`
138 * a close tool button will be rendered into the tooltip header.
141 * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays
142 * after the mouse enters the target element (defaults to 500)
146 * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the
147 * target element but before the tooltip actually hides (defaults to 200).
148 * Set to 0 for the tooltip to hide immediately.
152 * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip
153 * automatically hides (defaults to 5000). To disable automatic hiding, set
158 * @cfg {Array} mouseOffset An XY offset from the mouse position where the
159 * tooltip should be shown (defaults to [15,18]).
162 * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it
163 * moves over the target element (defaults to false).
167 * @cfg {String} anchor If specified, indicates that the tip should be anchored to a
168 * particular side of the target element or mouse pointer ("top", "right", "bottom",
169 * or "left"), with an arrow pointing back at the target or mouse pointer. If
170 * {@link #constrainPosition} is enabled, this will be used as a preferred value
171 * only and may be flipped as needed.
174 * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target
175 * element, false to anchor it relative to the mouse coordinates (defaults
176 * to true). When `anchorToTarget` is true, use
177 * `{@link #defaultAlign}` to control tooltip alignment to the
178 * target element. When `anchorToTarget` is false, use
179 * `{@link #anchorPosition}` instead to control alignment.
181 anchorToTarget: true,
183 * @cfg {Number} anchorOffset A numeric pixel value used to offset the
184 * default position of the anchor arrow (defaults to 0). When the anchor
185 * position is on the top or bottom of the tooltip, `anchorOffset`
186 * will be used as a horizontal offset. Likewise, when the anchor position
187 * is on the left or right side, `anchorOffset` will be used as
192 * @cfg {String} delegate
194 * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
195 * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
198 * When specified, the child element of the target which caused a show event is placed into the
199 * `{@link #triggerElement}` property before the ToolTip is shown.
201 * This may be useful when a Component has regular, repeating elements in it, each of which need a
202 * ToolTip which contains information specific to that element.
204 * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
209 quickShowInterval: 250,
212 initComponent: function() {
214 me.callParent(arguments);
215 me.lastActive = new Date();
216 me.setTarget(me.target);
217 me.origAnchor = me.anchor;
221 onRender: function(ct, position) {
223 me.callParent(arguments);
224 me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
225 me.anchorEl = me.el.createChild({
226 cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
231 afterRender: function() {
235 me.callParent(arguments);
236 zIndex = parseInt(me.el.getZIndex(), 10) || 0;
237 me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.core.Element.DISPLAY);
241 * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
242 * @param {Mixed} t The Element, HtmlElement, or ID of an element to bind to
244 setTarget: function(target) {
250 tg = Ext.get(me.target);
251 me.mun(tg, 'mouseover', me.onTargetOver, me);
252 me.mun(tg, 'mouseout', me.onTargetOut, me);
253 me.mun(tg, 'mousemove', me.onMouseMove, me);
260 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
261 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
264 mouseover: me.onTargetOver,
265 mouseout: me.onTargetOut,
266 mousemove: me.onMouseMove,
271 me.anchorTarget = me.target;
276 onMouseMove: function(e) {
278 t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
281 me.targetXY = e.getXY();
282 if (t === me.triggerElement) {
283 if (!me.hidden && me.trackMouse) {
284 xy = me.getTargetXY();
285 if (me.constrainPosition) {
286 xy = me.el.adjustForConstraints(xy, me.el.dom.parentNode);
288 me.setPagePosition(xy);
292 me.lastActive = new Date(0);
295 } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
301 getTargetXY: function() {
305 me.anchorTarget = me.triggerElement;
309 var offsets = me.getOffsets(),
310 xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
311 dw = Ext.core.Element.getViewWidth() - 5,
312 dh = Ext.core.Element.getViewHeight() - 5,
313 de = document.documentElement,
315 scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
316 scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
317 axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
319 constrainPosition = me.constrainPosition;
321 me.anchorEl.removeCls(me.anchorCls);
323 if (me.targetCounter < 2 && constrainPosition) {
324 if (axy[0] < scrollX) {
325 if (me.anchorToTarget) {
326 me.defaultAlign = 'l-r';
327 if (me.mouseOffset) {
328 me.mouseOffset[0] *= -1;
332 return me.getTargetXY();
334 if (axy[0] + sz.width > dw) {
335 if (me.anchorToTarget) {
336 me.defaultAlign = 'r-l';
337 if (me.mouseOffset) {
338 me.mouseOffset[0] *= -1;
342 return me.getTargetXY();
344 if (axy[1] < scrollY) {
345 if (me.anchorToTarget) {
346 me.defaultAlign = 't-b';
347 if (me.mouseOffset) {
348 me.mouseOffset[1] *= -1;
352 return me.getTargetXY();
354 if (axy[1] + sz.height > dh) {
355 if (me.anchorToTarget) {
356 me.defaultAlign = 'b-t';
357 if (me.mouseOffset) {
358 me.mouseOffset[1] *= -1;
361 me.anchor = 'bottom';
362 return me.getTargetXY();
366 me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
367 me.anchorEl.addCls(me.anchorCls);
368 me.targetCounter = 0;
371 mouseOffset = me.getMouseOffset();
372 return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
376 getMouseOffset: function() {
378 offset = me.anchor ? [0, 0] : [15, 18];
379 if (me.mouseOffset) {
380 offset[0] += me.mouseOffset[0];
381 offset[1] += me.mouseOffset[1];
387 getAnchorPosition: function() {
391 me.tipAnchor = me.anchor.charAt(0);
393 m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
396 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
399 me.tipAnchor = m[1].charAt(0);
402 switch (me.tipAnchor) {
414 getAnchorAlign: function() {
415 switch (this.anchor) {
428 getOffsets: function() {
432 ap = me.getAnchorPosition().charAt(0);
433 if (me.anchorToTarget && !me.trackMouse) {
442 offsets = [ - 13, 0];
451 offsets = [ - 15 - me.anchorOffset, 30];
454 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
457 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
460 offsets = [25, -13 - me.anchorOffset];
464 mouseOffset = me.getMouseOffset();
465 offsets[0] += mouseOffset[0];
466 offsets[1] += mouseOffset[1];
472 onTargetOver: function(e) {
476 if (me.disabled || e.within(me.target.dom, true)) {
479 t = e.getTarget(me.delegate);
481 me.triggerElement = t;
482 me.clearTimer('hide');
483 me.targetXY = e.getXY();
489 delayShow: function() {
491 if (me.hidden && !me.showTimer) {
492 if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
495 me.showTimer = Ext.defer(me.show, me.showDelay, me);
498 else if (!me.hidden && me.autoHide !== false) {
504 onTargetOut: function(e) {
506 if (me.disabled || e.within(me.target.dom, true)) {
509 me.clearTimer('show');
510 if (me.autoHide !== false) {
516 delayHide: function() {
518 if (!me.hidden && !me.hideTimer) {
519 me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
524 * Hides this tooltip if visible.
528 me.clearTimer('dismiss');
529 me.lastActive = new Date();
533 me.callParent(arguments);
534 delete me.triggerElement;
538 * Shows this tooltip at the current event target XY position.
543 // Show this Component first, so that sizing can be calculated
544 // pre-show it off screen so that the el will have dimensions
546 if (this.hidden === false) {
547 me.setPagePosition(-10000, -10000);
550 me.anchor = me.origAnchor;
552 me.showAt(me.getTargetXY());
564 showAt: function(xy) {
566 me.lastActive = new Date();
569 // Only call if this is hidden. May have been called from show above.
570 if (!me.isVisible()) {
571 this.callParent(arguments);
574 // Show may have been vetoed.
575 if (me.isVisible()) {
576 me.setPagePosition(xy[0], xy[1]);
577 if (me.constrainPosition || me.constrain) {
583 if (me.dismissDelay && me.autoHide !== false) {
584 me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
588 if (!me.anchorEl.isVisible()) {
597 syncAnchor: function() {
602 switch (me.tipAnchor.charAt(0)) {
606 offset = [20 + me.anchorOffset, 1];
611 offset = [ - 1, 12 + me.anchorOffset];
616 offset = [20 + me.anchorOffset, -1];
621 offset = [1, 12 + me.anchorOffset];
624 me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
628 setPagePosition: function(x, y) {
630 me.callParent(arguments);
637 clearTimer: function(name) {
638 name = name + 'Timer';
639 clearTimeout(this[name]);
644 clearTimers: function() {
646 me.clearTimer('show');
647 me.clearTimer('dismiss');
648 me.clearTimer('hide');
655 me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
662 me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
666 onDocMouseDown: function(e) {
668 if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
670 Ext.defer(me.doEnable, 100, me);
675 doEnable: function() {
676 if (!this.isDestroyed) {
682 onDisable: function() {
688 beforeDestroy: function() {
691 Ext.destroy(me.anchorEl);
694 delete me.anchorTarget;
695 delete me.triggerElement;
700 onDestroy: function() {
701 Ext.getDoc().un('mousedown', this.onDocMouseDown, this);