1000 lines
28 KiB
JavaScript
1000 lines
28 KiB
JavaScript
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/**
|
|
* @fileoverview Tooltip widget implementation.
|
|
*
|
|
* @author eae@google.com (Emil A Eklund)
|
|
* @see ../demos/tooltip.html
|
|
*/
|
|
|
|
goog.provide('goog.ui.Tooltip');
|
|
goog.provide('goog.ui.Tooltip.CursorTooltipPosition');
|
|
goog.provide('goog.ui.Tooltip.ElementTooltipPosition');
|
|
goog.provide('goog.ui.Tooltip.State');
|
|
|
|
goog.require('goog.Timer');
|
|
goog.require('goog.array');
|
|
goog.require('goog.dom');
|
|
goog.require('goog.events');
|
|
goog.require('goog.events.EventType');
|
|
goog.require('goog.math.Box');
|
|
goog.require('goog.math.Coordinate');
|
|
goog.require('goog.positioning');
|
|
goog.require('goog.positioning.AnchoredPosition');
|
|
goog.require('goog.positioning.Corner');
|
|
goog.require('goog.positioning.Overflow');
|
|
goog.require('goog.positioning.OverflowStatus');
|
|
goog.require('goog.positioning.ViewportPosition');
|
|
goog.require('goog.structs.Set');
|
|
goog.require('goog.style');
|
|
goog.require('goog.ui.Popup');
|
|
goog.require('goog.ui.PopupBase');
|
|
|
|
|
|
|
|
/**
|
|
* Tooltip widget. Can be attached to one or more elements and is shown, with a
|
|
* slight delay, when the the cursor is over the element or the element gains
|
|
* focus.
|
|
*
|
|
* @param {Element|string=} opt_el Element to display tooltip for, either
|
|
* element reference or string id.
|
|
* @param {?string=} opt_str Text message to display in tooltip.
|
|
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
|
|
* @constructor
|
|
* @extends {goog.ui.Popup}
|
|
*/
|
|
goog.ui.Tooltip = function(opt_el, opt_str, opt_domHelper) {
|
|
/**
|
|
* Dom Helper
|
|
* @type {goog.dom.DomHelper}
|
|
* @private
|
|
*/
|
|
this.dom_ = opt_domHelper || (opt_el ?
|
|
goog.dom.getDomHelper(goog.dom.getElement(opt_el)) :
|
|
goog.dom.getDomHelper());
|
|
|
|
goog.ui.Popup.call(this, this.dom_.createDom(
|
|
'div', {'style': 'position:absolute;display:none;'}));
|
|
|
|
/**
|
|
* Cursor position relative to the page.
|
|
* @type {!goog.math.Coordinate}
|
|
* @protected
|
|
*/
|
|
this.cursorPosition = new goog.math.Coordinate(1, 1);
|
|
|
|
/**
|
|
* Elements this widget is attached to.
|
|
* @type {goog.structs.Set}
|
|
* @private
|
|
*/
|
|
this.elements_ = new goog.structs.Set();
|
|
|
|
// Attach to element, if specified
|
|
if (opt_el) {
|
|
this.attach(opt_el);
|
|
}
|
|
|
|
// Set message, if specified.
|
|
if (opt_str != null) {
|
|
this.setText(opt_str);
|
|
}
|
|
};
|
|
goog.inherits(goog.ui.Tooltip, goog.ui.Popup);
|
|
|
|
|
|
/**
|
|
* List of active (open) tooltip widgets. Used to prevent multiple tooltips
|
|
* from appearing at once.
|
|
*
|
|
* @type {!Array.<goog.ui.Tooltip>}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.activeInstances_ = [];
|
|
|
|
|
|
/**
|
|
* Active element reference. Used by the delayed show functionality to keep
|
|
* track of the element the mouse is over or the element with focus.
|
|
* @type {Element}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.activeEl_ = null;
|
|
|
|
|
|
/**
|
|
* CSS class name for tooltip.
|
|
*
|
|
* @type {string}
|
|
*/
|
|
goog.ui.Tooltip.prototype.className = goog.getCssName('goog-tooltip');
|
|
|
|
|
|
/**
|
|
* Delay in milliseconds since the last mouseover or mousemove before the
|
|
* tooltip is displayed for an element.
|
|
*
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.showDelayMs_ = 500;
|
|
|
|
|
|
/**
|
|
* Timer for when to show.
|
|
*
|
|
* @type {number|undefined}
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.showTimer;
|
|
|
|
|
|
/**
|
|
* Delay in milliseconds before tooltips are hidden.
|
|
*
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.hideDelayMs_ = 0;
|
|
|
|
|
|
/**
|
|
* Timer for when to hide.
|
|
*
|
|
* @type {number|undefined}
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.hideTimer;
|
|
|
|
|
|
/**
|
|
* Element that triggered the tooltip. Note that if a second element triggers
|
|
* this tooltip, anchor becomes that second element, even if its show is
|
|
* cancelled and the original tooltip survives.
|
|
*
|
|
* @type {Element|undefined}
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.anchor;
|
|
|
|
|
|
/**
|
|
* Possible states for the tooltip to be in.
|
|
* @enum {number}
|
|
*/
|
|
goog.ui.Tooltip.State = {
|
|
INACTIVE: 0,
|
|
WAITING_TO_SHOW: 1,
|
|
SHOWING: 2,
|
|
WAITING_TO_HIDE: 3,
|
|
UPDATING: 4 // waiting to show new hovercard while old one still showing.
|
|
};
|
|
|
|
|
|
/**
|
|
* Popup activation types. Used to select a positioning strategy.
|
|
* @enum {number}
|
|
*/
|
|
goog.ui.Tooltip.Activation = {
|
|
CURSOR: 0,
|
|
FOCUS: 1
|
|
};
|
|
|
|
|
|
/**
|
|
* Whether the anchor has seen the cursor move or has received focus since the
|
|
* tooltip was last shown. Used to ignore mouse over events triggered by view
|
|
* changes and UI updates.
|
|
* @type {boolean|undefined}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.seenInteraction_;
|
|
|
|
|
|
/**
|
|
* Whether the cursor must have moved before the tooltip will be shown.
|
|
* @type {boolean|undefined}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.requireInteraction_;
|
|
|
|
|
|
/**
|
|
* If this tooltip's element contains another tooltip that becomes active, this
|
|
* property identifies that tooltip so that we can check if this tooltip should
|
|
* not be hidden because the nested tooltip is active.
|
|
* @type {goog.ui.Tooltip}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.childTooltip_;
|
|
|
|
|
|
/**
|
|
* If this tooltip is inside another tooltip's element, then it may have
|
|
* prevented that tooltip from hiding. When this tooltip hides, we'll need
|
|
* to check if the parent should be hidden as well.
|
|
* @type {goog.ui.Tooltip}
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.parentTooltip_;
|
|
|
|
|
|
/**
|
|
* Returns the dom helper that is being used on this component.
|
|
* @return {goog.dom.DomHelper} The dom helper used on this component.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getDomHelper = function() {
|
|
return this.dom_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {goog.ui.Tooltip} Active tooltip in a child element, or null if none.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.getChildTooltip = function() {
|
|
return this.childTooltip_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Attach to element. Tooltip will be displayed when the cursor is over the
|
|
* element or when the element has been active for a few milliseconds.
|
|
*
|
|
* @param {Element|string} el Element to display tooltip for, either element
|
|
* reference or string id.
|
|
*/
|
|
goog.ui.Tooltip.prototype.attach = function(el) {
|
|
el = goog.dom.getElement(el);
|
|
|
|
this.elements_.add(el);
|
|
goog.events.listen(el, goog.events.EventType.MOUSEOVER,
|
|
this.handleMouseOver, false, this);
|
|
goog.events.listen(el, goog.events.EventType.MOUSEOUT,
|
|
this.handleMouseOutAndBlur, false, this);
|
|
goog.events.listen(el, goog.events.EventType.MOUSEMOVE,
|
|
this.handleMouseMove, false, this);
|
|
goog.events.listen(el, goog.events.EventType.FOCUS,
|
|
this.handleFocus, false, this);
|
|
goog.events.listen(el, goog.events.EventType.BLUR,
|
|
this.handleMouseOutAndBlur, false, this);
|
|
};
|
|
|
|
|
|
/**
|
|
* Detach from element(s).
|
|
*
|
|
* @param {Element|string=} opt_el Element to detach from, either element
|
|
* reference or string id. If no element is
|
|
* specified all are detached.
|
|
*/
|
|
goog.ui.Tooltip.prototype.detach = function(opt_el) {
|
|
if (opt_el) {
|
|
var el = goog.dom.getElement(opt_el);
|
|
this.detachElement_(el);
|
|
this.elements_.remove(el);
|
|
} else {
|
|
var a = this.elements_.getValues();
|
|
for (var el, i = 0; el = a[i]; i++) {
|
|
this.detachElement_(el);
|
|
}
|
|
this.elements_.clear();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Detach from element.
|
|
*
|
|
* @param {Element} el Element to detach from.
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.detachElement_ = function(el) {
|
|
goog.events.unlisten(el, goog.events.EventType.MOUSEOVER,
|
|
this.handleMouseOver, false, this);
|
|
goog.events.unlisten(el, goog.events.EventType.MOUSEOUT,
|
|
this.handleMouseOutAndBlur, false, this);
|
|
goog.events.unlisten(el, goog.events.EventType.MOUSEMOVE,
|
|
this.handleMouseMove, false, this);
|
|
goog.events.unlisten(el, goog.events.EventType.FOCUS,
|
|
this.handleFocus, false, this);
|
|
goog.events.unlisten(el, goog.events.EventType.BLUR,
|
|
this.handleMouseOutAndBlur, false, this);
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets delay in milliseconds before tooltip is displayed for an element.
|
|
*
|
|
* @param {number} delay The delay in milliseconds.
|
|
*/
|
|
goog.ui.Tooltip.prototype.setShowDelayMs = function(delay) {
|
|
this.showDelayMs_ = delay;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The delay in milliseconds before tooltip is displayed for an
|
|
* element.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getShowDelayMs = function() {
|
|
return this.showDelayMs_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets delay in milliseconds before tooltip is hidden once the cursor leavs
|
|
* the element.
|
|
*
|
|
* @param {number} delay The delay in milliseconds.
|
|
*/
|
|
goog.ui.Tooltip.prototype.setHideDelayMs = function(delay) {
|
|
this.hideDelayMs_ = delay;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The delay in milliseconds before tooltip is hidden once the
|
|
* cursor leaves the element.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getHideDelayMs = function() {
|
|
return this.hideDelayMs_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets tooltip message as plain text.
|
|
*
|
|
* @param {string} str Text message to display in tooltip.
|
|
*/
|
|
goog.ui.Tooltip.prototype.setText = function(str) {
|
|
goog.dom.setTextContent(this.getElement(), str);
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets tooltip message as HTML markup.
|
|
*
|
|
* @param {string} str HTML message to display in tooltip.
|
|
*/
|
|
goog.ui.Tooltip.prototype.setHtml = function(str) {
|
|
this.getElement().innerHTML = str;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets tooltip element.
|
|
*
|
|
* @param {Element} el HTML element to use as the tooltip.
|
|
* @override
|
|
*/
|
|
goog.ui.Tooltip.prototype.setElement = function(el) {
|
|
var oldElement = this.getElement();
|
|
if (oldElement) {
|
|
goog.dom.removeNode(oldElement);
|
|
}
|
|
goog.ui.Tooltip.superClass_.setElement.call(this, el);
|
|
if (el) {
|
|
var body = this.dom_.getDocument().body;
|
|
body.insertBefore(el, body.lastChild);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {string} The tooltip message as plain text.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getText = function() {
|
|
return goog.dom.getTextContent(this.getElement());
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {string} The tooltip message as HTML.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getHtml = function() {
|
|
return this.getElement().innerHTML;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {goog.ui.Tooltip.State} Current state of tooltip.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getState = function() {
|
|
return this.showTimer ?
|
|
(this.isVisible() ? goog.ui.Tooltip.State.UPDATING :
|
|
goog.ui.Tooltip.State.WAITING_TO_SHOW) :
|
|
this.hideTimer ? goog.ui.Tooltip.State.WAITING_TO_HIDE :
|
|
this.isVisible() ? goog.ui.Tooltip.State.SHOWING :
|
|
goog.ui.Tooltip.State.INACTIVE;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets whether tooltip requires the mouse to have moved or the anchor receive
|
|
* focus before the tooltip will be shown.
|
|
* @param {boolean} requireInteraction Whether tooltip should require some user
|
|
* interaction before showing tooltip.
|
|
*/
|
|
goog.ui.Tooltip.prototype.setRequireInteraction = function(requireInteraction) {
|
|
this.requireInteraction_ = requireInteraction;
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns true if the coord is in the tooltip.
|
|
* @param {goog.math.Coordinate} coord Coordinate being tested.
|
|
* @return {boolean} Whether the coord is in the tooltip.
|
|
*/
|
|
goog.ui.Tooltip.prototype.isCoordinateInTooltip = function(coord) {
|
|
// Check if coord is inside the the tooltip
|
|
if (!this.isVisible()) {
|
|
return false;
|
|
}
|
|
|
|
var offset = goog.style.getPageOffset(this.getElement());
|
|
var size = goog.style.getSize(this.getElement());
|
|
return offset.x <= coord.x && coord.x <= offset.x + size.width &&
|
|
offset.y <= coord.y && coord.y <= offset.y + size.height;
|
|
};
|
|
|
|
|
|
/**
|
|
* Called before the popup is shown.
|
|
*
|
|
* @return {boolean} Whether tooltip should be shown.
|
|
* @protected
|
|
* @override
|
|
*/
|
|
goog.ui.Tooltip.prototype.onBeforeShow = function() {
|
|
if (!goog.ui.PopupBase.prototype.onBeforeShow.call(this)) {
|
|
return false;
|
|
}
|
|
|
|
// Hide all open tooltips except if this tooltip is triggered by an element
|
|
// inside another tooltip.
|
|
if (this.anchor) {
|
|
for (var tt, i = 0; tt = goog.ui.Tooltip.activeInstances_[i]; i++) {
|
|
if (!goog.dom.contains(tt.getElement(), this.anchor)) {
|
|
tt.setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
goog.array.insert(goog.ui.Tooltip.activeInstances_, this);
|
|
|
|
var element = this.getElement();
|
|
element.className = this.className;
|
|
this.clearHideTimer();
|
|
|
|
// Register event handlers for tooltip. Used to prevent the tooltip from
|
|
// closing if the cursor is over the tooltip rather then the element that
|
|
// triggered it.
|
|
goog.events.listen(element, goog.events.EventType.MOUSEOVER,
|
|
this.handleTooltipMouseOver, false, this);
|
|
goog.events.listen(element, goog.events.EventType.MOUSEOUT,
|
|
this.handleTooltipMouseOut, false, this);
|
|
|
|
this.clearShowTimer();
|
|
return true;
|
|
};
|
|
|
|
|
|
/**
|
|
* Called after the popup is hidden.
|
|
*
|
|
* @protected
|
|
* @suppress {underscore}
|
|
* @override
|
|
*/
|
|
goog.ui.Tooltip.prototype.onHide_ = function() {
|
|
goog.array.remove(goog.ui.Tooltip.activeInstances_, this);
|
|
|
|
// Hide all open tooltips triggered by an element inside this tooltip.
|
|
var element = this.getElement();
|
|
for (var tt, i = 0; tt = goog.ui.Tooltip.activeInstances_[i]; i++) {
|
|
if (tt.anchor && goog.dom.contains(element, tt.anchor)) {
|
|
tt.setVisible(false);
|
|
}
|
|
}
|
|
|
|
// If this tooltip is inside another tooltip, start hide timer for that
|
|
// tooltip in case this tooltip was the only reason it was still showing.
|
|
if (this.parentTooltip_) {
|
|
this.parentTooltip_.startHideTimer();
|
|
}
|
|
|
|
goog.events.unlisten(element, goog.events.EventType.MOUSEOVER,
|
|
this.handleTooltipMouseOver, false, this);
|
|
goog.events.unlisten(element, goog.events.EventType.MOUSEOUT,
|
|
this.handleTooltipMouseOut, false, this);
|
|
|
|
this.anchor = undefined;
|
|
// If we are still waiting to show a different hovercard, don't abort it
|
|
// because you think you haven't seen a mouse move:
|
|
if (this.getState() == goog.ui.Tooltip.State.INACTIVE) {
|
|
this.seenInteraction_ = false;
|
|
}
|
|
|
|
goog.ui.PopupBase.prototype.onHide_.call(this);
|
|
};
|
|
|
|
|
|
/**
|
|
* Called by timer from mouse over handler. Shows tooltip if cursor is still
|
|
* over the same element.
|
|
*
|
|
* @param {Element} el Element to show tooltip for.
|
|
* @param {goog.positioning.AbstractPosition=} opt_pos Position to display popup
|
|
* at.
|
|
*/
|
|
goog.ui.Tooltip.prototype.maybeShow = function(el, opt_pos) {
|
|
// Assert that the mouse is still over the same element, and that we have not
|
|
// detached from the anchor in the meantime.
|
|
if (this.anchor == el && this.elements_.contains(this.anchor)) {
|
|
if (this.seenInteraction_ || !this.requireInteraction_) {
|
|
// If it is currently showing, then hide it, and abort if it doesn't hide.
|
|
this.setVisible(false);
|
|
if (!this.isVisible()) {
|
|
this.positionAndShow_(el, opt_pos);
|
|
}
|
|
} else {
|
|
this.anchor = undefined;
|
|
}
|
|
}
|
|
this.showTimer = undefined;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {goog.structs.Set} Elements this widget is attached to.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.getElements = function() {
|
|
return this.elements_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {Element} Active element reference.
|
|
*/
|
|
goog.ui.Tooltip.prototype.getActiveElement = function() {
|
|
return this.activeEl_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {Element} activeEl Active element reference.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.setActiveElement = function(activeEl) {
|
|
this.activeEl_ = activeEl;
|
|
};
|
|
|
|
|
|
/**
|
|
* Shows tooltip for a specific element.
|
|
*
|
|
* @param {Element} el Element to show tooltip for.
|
|
* @param {goog.positioning.AbstractPosition=} opt_pos Position to display popup
|
|
* at.
|
|
*/
|
|
goog.ui.Tooltip.prototype.showForElement = function(el, opt_pos) {
|
|
this.attach(el);
|
|
this.activeEl_ = el;
|
|
|
|
this.positionAndShow_(el, opt_pos);
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets tooltip position and shows it.
|
|
*
|
|
* @param {Element} el Element to show tooltip for.
|
|
* @param {goog.positioning.AbstractPosition=} opt_pos Position to display popup
|
|
* at.
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.positionAndShow_ = function(el, opt_pos) {
|
|
this.anchor = el;
|
|
this.setPosition(opt_pos ||
|
|
this.getPositioningStrategy(goog.ui.Tooltip.Activation.CURSOR));
|
|
this.setVisible(true);
|
|
};
|
|
|
|
|
|
/**
|
|
* Called by timer from mouse out handler. Hides tooltip if cursor is still
|
|
* outside element and tooltip, or if a child of tooltip has the focus.
|
|
* @param {Element} el Tooltip's anchor when hide timer was started.
|
|
*/
|
|
goog.ui.Tooltip.prototype.maybeHide = function(el) {
|
|
this.hideTimer = undefined;
|
|
if (el == this.anchor) {
|
|
if ((this.activeEl_ == null || (this.activeEl_ != this.getElement() &&
|
|
!this.elements_.contains(this.activeEl_))) &&
|
|
!this.hasActiveChild()) {
|
|
this.setVisible(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {boolean} Whether tooltip element contains an active child tooltip,
|
|
* and should thus not be hidden. When the child tooltip is hidden, it
|
|
* will check if the parent should be hidden, too.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.hasActiveChild = function() {
|
|
return !!(this.childTooltip_ && this.childTooltip_.activeEl_);
|
|
};
|
|
|
|
|
|
/**
|
|
* Saves the current mouse cursor position to {@code this.cursorPosition}.
|
|
* @param {goog.events.BrowserEvent} event MOUSEOVER or MOUSEMOVE event.
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.saveCursorPosition_ = function(event) {
|
|
var scroll = this.dom_.getDocumentScroll();
|
|
this.cursorPosition.x = event.clientX + scroll.x;
|
|
this.cursorPosition.y = event.clientY + scroll.y;
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for mouse over events.
|
|
*
|
|
* @param {goog.events.BrowserEvent} event Event object.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.handleMouseOver = function(event) {
|
|
var el = this.getAnchorFromElement(/** @type {Element} */ (event.target));
|
|
this.activeEl_ = /** @type {Element} */ (el);
|
|
this.clearHideTimer();
|
|
if (el != this.anchor) {
|
|
this.anchor = el;
|
|
this.startShowTimer(/** @type {Element} */ (el));
|
|
this.checkForParentTooltip_();
|
|
this.saveCursorPosition_(event);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Find anchor containing the given element, if any.
|
|
*
|
|
* @param {Element} el Element that triggered event.
|
|
* @return {Element} Element in elements_ array that contains given element,
|
|
* or null if not found.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.getAnchorFromElement = function(el) {
|
|
// FireFox has a bug where mouse events relating to <input> elements are
|
|
// sometimes duplicated (often in FF2, rarely in FF3): once for the
|
|
// <input> element and once for a magic hidden <div> element. Javascript
|
|
// code does not have sufficient permissions to read properties on that
|
|
// magic element and thus will throw an error in this call to
|
|
// getAnchorFromElement_(). In that case we swallow the error.
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330961
|
|
try {
|
|
while (el && !this.elements_.contains(el)) {
|
|
el = /** @type {Element} */ (el.parentNode);
|
|
}
|
|
return el;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for mouse move events.
|
|
*
|
|
* @param {goog.events.BrowserEvent} event MOUSEMOVE event.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.handleMouseMove = function(event) {
|
|
this.saveCursorPosition_(event);
|
|
this.seenInteraction_ = true;
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for focus events.
|
|
*
|
|
* @param {goog.events.BrowserEvent} event Event object.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.handleFocus = function(event) {
|
|
var el = this.getAnchorFromElement(/** @type {Element} */ (event.target));
|
|
this.activeEl_ = el;
|
|
this.seenInteraction_ = true;
|
|
|
|
if (this.anchor != el) {
|
|
this.anchor = el;
|
|
var pos = this.getPositioningStrategy(goog.ui.Tooltip.Activation.FOCUS);
|
|
this.clearHideTimer();
|
|
this.startShowTimer(/** @type {Element} */ (el), pos);
|
|
|
|
this.checkForParentTooltip_();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Return a Position instance for repositioning the tooltip. Override in
|
|
* subclasses to customize the way repositioning is done.
|
|
*
|
|
* @param {goog.ui.Tooltip.Activation} activationType Information about what
|
|
* kind of event caused the popup to be shown.
|
|
* @return {!goog.positioning.AbstractPosition} The position object used
|
|
* to position the tooltip.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.getPositioningStrategy = function(activationType) {
|
|
if (activationType == goog.ui.Tooltip.Activation.CURSOR) {
|
|
var coord = this.cursorPosition.clone();
|
|
return new goog.ui.Tooltip.CursorTooltipPosition(coord);
|
|
}
|
|
return new goog.ui.Tooltip.ElementTooltipPosition(this.activeEl_);
|
|
};
|
|
|
|
|
|
/**
|
|
* Looks for an active tooltip whose element contains this tooltip's anchor.
|
|
* This allows us to prevent hides until they are really necessary.
|
|
*
|
|
* @private
|
|
*/
|
|
goog.ui.Tooltip.prototype.checkForParentTooltip_ = function() {
|
|
if (this.anchor) {
|
|
for (var tt, i = 0; tt = goog.ui.Tooltip.activeInstances_[i]; i++) {
|
|
if (goog.dom.contains(tt.getElement(), this.anchor)) {
|
|
tt.childTooltip_ = this;
|
|
this.parentTooltip_ = tt;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for mouse out and blur events.
|
|
*
|
|
* @param {goog.events.BrowserEvent} event Event object.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.handleMouseOutAndBlur = function(event) {
|
|
var el = this.getAnchorFromElement(/** @type {Element} */ (event.target));
|
|
var elTo = this.getAnchorFromElement(
|
|
/** @type {Element} */ (event.relatedTarget));
|
|
if (el == elTo) {
|
|
// We haven't really left the anchor, just moved from one child to
|
|
// another.
|
|
return;
|
|
}
|
|
|
|
if (el == this.activeEl_) {
|
|
this.activeEl_ = null;
|
|
}
|
|
|
|
this.clearShowTimer();
|
|
this.seenInteraction_ = false;
|
|
if (this.isVisible() && (!event.relatedTarget ||
|
|
!goog.dom.contains(this.getElement(), event.relatedTarget))) {
|
|
this.startHideTimer();
|
|
} else {
|
|
this.anchor = undefined;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for mouse over events for the tooltip element.
|
|
*
|
|
* @param {goog.events.BrowserEvent} event Event object.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.handleTooltipMouseOver = function(event) {
|
|
var element = this.getElement();
|
|
if (this.activeEl_ != element) {
|
|
this.clearHideTimer();
|
|
this.activeEl_ = element;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for mouse out events for the tooltip element.
|
|
*
|
|
* @param {goog.events.BrowserEvent} event Event object.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.handleTooltipMouseOut = function(event) {
|
|
var element = this.getElement();
|
|
if (this.activeEl_ == element && (!event.relatedTarget ||
|
|
!goog.dom.contains(element, event.relatedTarget))) {
|
|
this.activeEl_ = null;
|
|
this.startHideTimer();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper method, starts timer that calls maybeShow. Parameters are passed to
|
|
* the maybeShow method.
|
|
*
|
|
* @param {Element} el Element to show tooltip for.
|
|
* @param {goog.positioning.AbstractPosition=} opt_pos Position to display popup
|
|
* at.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.startShowTimer = function(el, opt_pos) {
|
|
if (!this.showTimer) {
|
|
this.showTimer = goog.Timer.callOnce(
|
|
goog.bind(this.maybeShow, this, el, opt_pos), this.showDelayMs_);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper method called to clear the show timer.
|
|
*
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.clearShowTimer = function() {
|
|
if (this.showTimer) {
|
|
goog.Timer.clear(this.showTimer);
|
|
this.showTimer = undefined;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper method called to start the close timer.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.startHideTimer = function() {
|
|
if (this.getState() == goog.ui.Tooltip.State.SHOWING) {
|
|
this.hideTimer = goog.Timer.callOnce(
|
|
goog.bind(this.maybeHide, this, this.anchor), this.getHideDelayMs());
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper method called to clear the close timer.
|
|
* @protected
|
|
*/
|
|
goog.ui.Tooltip.prototype.clearHideTimer = function() {
|
|
if (this.hideTimer) {
|
|
goog.Timer.clear(this.hideTimer);
|
|
this.hideTimer = undefined;
|
|
}
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.Tooltip.prototype.disposeInternal = function() {
|
|
this.setVisible(false);
|
|
this.clearShowTimer();
|
|
this.detach();
|
|
if (this.getElement()) {
|
|
goog.dom.removeNode(this.getElement());
|
|
}
|
|
this.activeEl_ = null;
|
|
delete this.dom_;
|
|
goog.ui.Tooltip.superClass_.disposeInternal.call(this);
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Popup position implementation that positions the popup (the tooltip in this
|
|
* case) based on the cursor position. It's positioned below the cursor to the
|
|
* right if there's enough room to fit all of it inside the Viewport. Otherwise
|
|
* it's displayed as far right as possible either above or below the element.
|
|
*
|
|
* Used to position tooltips triggered by the cursor.
|
|
*
|
|
* @param {number|!goog.math.Coordinate} arg1 Left position or coordinate.
|
|
* @param {number=} opt_arg2 Top position.
|
|
* @constructor
|
|
* @extends {goog.positioning.ViewportPosition}
|
|
*/
|
|
goog.ui.Tooltip.CursorTooltipPosition = function(arg1, opt_arg2) {
|
|
goog.positioning.ViewportPosition.call(this, arg1, opt_arg2);
|
|
};
|
|
goog.inherits(goog.ui.Tooltip.CursorTooltipPosition,
|
|
goog.positioning.ViewportPosition);
|
|
|
|
|
|
/**
|
|
* Repositions the popup based on cursor position.
|
|
*
|
|
* @param {Element} element The DOM element of the popup.
|
|
* @param {goog.positioning.Corner} popupCorner The corner of the popup element
|
|
* that that should be positioned adjacent to the anchorElement.
|
|
* @param {goog.math.Box=} opt_margin A margin specified in pixels.
|
|
* @override
|
|
*/
|
|
goog.ui.Tooltip.CursorTooltipPosition.prototype.reposition = function(
|
|
element, popupCorner, opt_margin) {
|
|
var viewportElt = goog.style.getClientViewportElement(element);
|
|
var viewport = goog.style.getVisibleRectForElement(viewportElt);
|
|
var margin = opt_margin ? new goog.math.Box(opt_margin.top + 10,
|
|
opt_margin.right, opt_margin.bottom, opt_margin.left + 10) :
|
|
new goog.math.Box(10, 0, 0, 10);
|
|
|
|
if (goog.positioning.positionAtCoordinate(this.coordinate, element,
|
|
goog.positioning.Corner.TOP_START, margin, viewport,
|
|
goog.positioning.Overflow.ADJUST_X | goog.positioning.Overflow.FAIL_Y
|
|
) & goog.positioning.OverflowStatus.FAILED) {
|
|
goog.positioning.positionAtCoordinate(this.coordinate, element,
|
|
goog.positioning.Corner.TOP_START, margin, viewport,
|
|
goog.positioning.Overflow.ADJUST_X |
|
|
goog.positioning.Overflow.ADJUST_Y);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Popup position implementation that positions the popup (the tooltip in this
|
|
* case) based on the element position. It's positioned below the element to the
|
|
* right if there's enough room to fit all of it inside the Viewport. Otherwise
|
|
* it's displayed as far right as possible either above or below the element.
|
|
*
|
|
* Used to position tooltips triggered by focus changes.
|
|
*
|
|
* @param {Element} element The element to anchor the popup at.
|
|
* @constructor
|
|
* @extends {goog.positioning.AnchoredPosition}
|
|
*/
|
|
goog.ui.Tooltip.ElementTooltipPosition = function(element) {
|
|
goog.positioning.AnchoredPosition.call(this, element,
|
|
goog.positioning.Corner.BOTTOM_RIGHT);
|
|
};
|
|
goog.inherits(goog.ui.Tooltip.ElementTooltipPosition,
|
|
goog.positioning.AnchoredPosition);
|
|
|
|
|
|
/**
|
|
* Repositions the popup based on element position.
|
|
*
|
|
* @param {Element} element The DOM element of the popup.
|
|
* @param {goog.positioning.Corner} popupCorner The corner of the popup element
|
|
* that should be positioned adjacent to the anchorElement.
|
|
* @param {goog.math.Box=} opt_margin A margin specified in pixels.
|
|
* @override
|
|
*/
|
|
goog.ui.Tooltip.ElementTooltipPosition.prototype.reposition = function(
|
|
element, popupCorner, opt_margin) {
|
|
var offset = new goog.math.Coordinate(10, 0);
|
|
|
|
if (goog.positioning.positionAtAnchor(this.element, this.corner, element,
|
|
popupCorner, offset, opt_margin,
|
|
goog.positioning.Overflow.ADJUST_X | goog.positioning.Overflow.FAIL_Y
|
|
) & goog.positioning.OverflowStatus.FAILED) {
|
|
goog.positioning.positionAtAnchor(this.element,
|
|
goog.positioning.Corner.TOP_RIGHT, element,
|
|
goog.positioning.Corner.BOTTOM_LEFT, offset, opt_margin,
|
|
goog.positioning.Overflow.ADJUST_X |
|
|
goog.positioning.Overflow.ADJUST_Y);
|
|
}
|
|
};
|