All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.client.VTooltip Maven / Gradle / Ivy

Go to download

Vaadin is a web application framework for Rich Internet Applications (RIA). Vaadin enables easy development and maintenance of fast and secure rich web applications with a stunning look and feel and a wide browser support. It features a server-side architecture with the majority of the logic running on the server. Ajax technology is used at the browser-side to ensure a rich and interactive user experience.

There is a newer version: 8.27.1
Show newest version
/*
 * Copyright 2000-2016 Vaadin Ltd.
 *
 * 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.
 */
package com.vaadin.client;

import com.google.gwt.aria.client.LiveValue;
import com.google.gwt.aria.client.RelevantValue;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.PreElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.VOverlay;

/**
 * TODO open for extension
 */
public class VTooltip extends VOverlay {
    private static final String CLASSNAME = "v-tooltip";
    private static final int MARGIN = 4;
    public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN | Event.ONMOUSEOVER
            | Event.ONMOUSEOUT | Event.ONMOUSEMOVE | Event.ONCLICK;
    VErrorMessage em = new VErrorMessage();
    HTML description = GWT.create(HTML.class);

    private TooltipInfo currentTooltipInfo = new TooltipInfo(" ");

    private boolean closing = false;
    private boolean opening = false;

    // Open next tooltip faster. Disabled after 2 sec of showTooltip-silence.
    private boolean justClosed = false;

    private String uniqueId = DOM.createUniqueId();
    private int maxWidth;

    // Delays for the tooltip, configurable on the server side
    private int openDelay;
    private int quickOpenDelay;
    private int quickOpenTimeout;
    private int closeTimeout;

    /**
     * Current element hovered
     */
    private com.google.gwt.dom.client.Element currentElement = null;

    /**
     * Used to show tooltips; usually used via the singleton in
     * {@link ApplicationConnection}. NOTE that #setOwner(Widget)} should be
     * called after instantiating.
     *
     * @see ApplicationConnection#getVTooltip()
     */
    public VTooltip() {
        super(false, false); // no autohide, not modal
        setStyleName(CLASSNAME);
        FlowPanel layout = new FlowPanel();
        setWidget(layout);
        layout.add(em);
        description.setStyleName(CLASSNAME + "-text");
        layout.add(description);

        // When a tooltip is shown, the content of the tooltip changes. With a
        // tooltip being a live-area, this change is notified to a assistive
        // device.
        Roles.getTooltipRole().set(getElement());
        Roles.getTooltipRole().setAriaLiveProperty(getElement(),
                LiveValue.ASSERTIVE);
        Roles.getTooltipRole().setAriaRelevantProperty(getElement(),
                RelevantValue.ADDITIONS);

        // Tooltip needs to be on top of other VOverlay elements.
        setZIndex(VOverlay.Z_INDEX + 1);
    }

    /**
     * Show the tooltip with the provided info for assistive devices.
     *
     * @param info
     *            with the content of the tooltip
     */
    public void showAssistive(TooltipInfo info) {
        updatePosition(null, true);
        setTooltipText(info);
        showTooltip();
    }

    /**
     * Initialize the tooltip overlay for assistive devices.
     *
     * @param info
     *            with the content of the tooltip
     * @since 7.2.4
     */
    public void initializeAssistiveTooltips() {
        updatePosition(null, true);
        setTooltipText(new TooltipInfo(" "));
        showTooltip();
        hideTooltip();
        description.getParent().getElement().getStyle().clearWidth();
    }

    private void setTooltipText(TooltipInfo info) {
        if (info.getErrorMessage() != null
                && !info.getErrorMessage().isEmpty()) {
            em.setVisible(true);
            em.updateMessage(info.getErrorMessage());
        } else {
            em.setVisible(false);
        }
        if (info.getTitle() != null && !info.getTitle().isEmpty()) {
            switch (info.getContentMode()) {
            case HTML:
                description.setHTML(info.getTitle());
                break;
            case TEXT:
                description.setText(info.getTitle());
                break;
            case PREFORMATTED:
                PreElement preElement = Document.get().createPreElement();
                preElement.setInnerText(info.getTitle());
                // clear existing content
                description.setHTML("");
                // add preformatted text to dom
                description.getElement().appendChild(preElement);
                break;
            default:
                break;
            }
            /*
             * Issue #11871: to correctly update the offsetWidth of description
             * element we need to clear style width of its parent DIV from old
             * value (in some strange cases this width=[tooltip MAX_WIDTH] after
             * tooltip text has been already updated to new shortly value:
             *
             * 
This is a short tooltip
* * and it leads to error during calculation offsetWidth (it is * native GWT method getSubPixelOffsetWidth()) of description * element") */ description.getParent().getElement().getStyle().clearWidth(); description.getElement().getStyle().clearDisplay(); } else { description.setHTML(""); description.getElement().getStyle().setDisplay(Display.NONE); } currentTooltipInfo = info; } /** * Show a popup containing the currentTooltipInfo * */ private void showTooltip() { if (currentTooltipInfo.hasMessage()) { // Issue #8454: With IE7 the tooltips size is calculated based on // the last tooltip's position, causing problems if the last one was // in the right or bottom edge. For this reason the tooltip is moved // first to 0,0 position so that the calculation goes correctly. setPopupPosition(0, 0); setPopupPositionAndShow(new PositionCallback() { @Override public void setPosition(int offsetWidth, int offsetHeight) { if (offsetWidth > getMaxWidth()) { setWidth(getMaxWidth() + "px"); // Check new height and width with reflowed content offsetWidth = getOffsetWidth(); offsetHeight = getOffsetHeight(); } int x = 0; int y = 0; if (BrowserInfo.get().isTouchDevice()) { setMaxWidth(Window.getClientWidth()); offsetWidth = getOffsetWidth(); offsetHeight = getOffsetHeight(); x = getFinalTouchX(offsetWidth); y = getFinalTouchY(offsetHeight); } else { x = getFinalX(offsetWidth); y = getFinalY(offsetHeight); } setPopupPosition(x, y); sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT); } /** * Return the final X-coordinate of the tooltip based on cursor * position, size of the tooltip, size of the page and necessary * margins. * * @param offsetWidth * @return The final X-coordinate */ private int getFinalX(int offsetWidth) { int x = 0; int widthNeeded = 10 + MARGIN + offsetWidth; int roomLeft = tooltipEventMouseX; int roomRight = Window.getClientWidth() - roomLeft; if (roomRight > widthNeeded) { x = tooltipEventMouseX + 10 + Window.getScrollLeft(); } else { x = tooltipEventMouseX + Window.getScrollLeft() - 10 - offsetWidth; } if (x + offsetWidth + MARGIN - Window.getScrollLeft() > Window .getClientWidth()) { x = Window.getClientWidth() - offsetWidth - MARGIN + Window.getScrollLeft(); } if (tooltipEventMouseX != EVENT_XY_POSITION_OUTSIDE) { // Do not allow x to be zero, for otherwise the tooltip // does not close when the mouse is moved (see // isTooltipOpen()). #15129 int minX = Window.getScrollLeft() + MARGIN; x = Math.max(x, minX); } return x; } /** * Return the final X-coordinate of the tooltip based on cursor * position, size of the tooltip, size of the page and necessary * margins. * * @param offsetWidth * @return The final X-coordinate */ private int getFinalTouchX(int offsetWidth) { int x = 0; int widthNeeded = 10 + offsetWidth; int roomLeft = currentElement != null ? currentElement.getAbsoluteLeft() : EVENT_XY_POSITION_OUTSIDE; int viewPortWidth = Window.getClientWidth(); int roomRight = viewPortWidth - roomLeft; if (roomRight > widthNeeded) { x = roomLeft; } else { x = roomLeft - offsetWidth; } if (x + offsetWidth - Window.getScrollLeft() > viewPortWidth) { x = viewPortWidth - offsetWidth + Window.getScrollLeft(); } if (roomLeft != EVENT_XY_POSITION_OUTSIDE) { // Do not allow x to be zero, for otherwise the tooltip // does not close when the mouse is moved (see // isTooltipOpen()). #15129 int minX = Window.getScrollLeft(); x = Math.max(x, minX); } return x; } /** * Return the final Y-coordinate of the tooltip based on cursor * position, size of the tooltip, size of the page and necessary * margins. * * @param offsetHeight * @return The final y-coordinate * */ private int getFinalY(int offsetHeight) { int y = 0; int heightNeeded = 10 + offsetHeight; int roomAbove = tooltipEventMouseY; int roomBelow = Window.getClientHeight() - roomAbove; if (roomBelow > heightNeeded) { y = tooltipEventMouseY + 10 + Window.getScrollTop(); } else { y = tooltipEventMouseY + Window.getScrollTop() - 10 - offsetHeight; } if (y + offsetHeight + MARGIN - Window.getScrollTop() > Window .getClientHeight()) { y = tooltipEventMouseY - 5 - offsetHeight + Window.getScrollTop(); if (y - Window.getScrollTop() < 0) { // tooltip does not fit on top of the mouse either, // put it at the top of the screen y = Window.getScrollTop(); } } if (tooltipEventMouseY != EVENT_XY_POSITION_OUTSIDE) { // Do not allow y to be zero, for otherwise the tooltip // does not close when the mouse is moved (see // isTooltipOpen()). #15129 int minY = Window.getScrollTop() + MARGIN; y = Math.max(y, minY); } return y; } /** * Return the final Y-coordinate of the tooltip based on cursor * position, size of the tooltip, size of the page and necessary * margins. * * @param offsetHeight * @return The final y-coordinate * */ private int getFinalTouchY(int offsetHeight) { int y = 0; int heightNeeded = 10 + offsetHeight; int roomAbove = currentElement != null ? currentElement.getAbsoluteTop() + currentElement.getOffsetHeight() : EVENT_XY_POSITION_OUTSIDE; int roomBelow = Window.getClientHeight() - roomAbove; if (roomBelow > heightNeeded) { y = roomAbove; } else { y = roomAbove - offsetHeight - (currentElement != null ? currentElement.getOffsetHeight() : 0); } if (y + offsetHeight - Window.getScrollTop() > Window .getClientHeight()) { y = roomAbove - 5 - offsetHeight + Window.getScrollTop(); if (y - Window.getScrollTop() < 0) { // tooltip does not fit on top of the mouse either, // put it at the top of the screen y = Window.getScrollTop(); } } if (roomAbove != EVENT_XY_POSITION_OUTSIDE) { // Do not allow y to be zero, for otherwise the tooltip // does not close when the mouse is moved (see // isTooltipOpen()). #15129 int minY = Window.getScrollTop(); y = Math.max(y, minY); } return y; } }); } else { hide(); } } /** * For assistive tooltips to work correctly we must have the tooltip visible * and attached to the DOM well in advance. For this reason both isShowing * and isVisible return false positives. We can't override either of them as * external code may depend on this behavior. * * @return boolean */ public boolean isTooltipOpen() { return super.isShowing() && super.isVisible() && getPopupLeft() > 0 && getPopupTop() > 0; } private void closeNow() { hide(); setWidth(""); closing = false; justClosedTimer.schedule(getQuickOpenTimeout()); justClosed = true; } private Timer showTimer = new Timer() { @Override public void run() { opening = false; showTooltip(); } }; private Timer closeTimer = new Timer() { @Override public void run() { closeNow(); } }; private Timer justClosedTimer = new Timer() { @Override public void run() { justClosed = false; } }; public void hideTooltip() { if (opening) { showTimer.cancel(); opening = false; } if (!isAttached()) { return; } if (closing) { // already about to close return; } if (isTooltipOpen()) { closeTimer.schedule(getCloseTimeout()); closing = true; } } @Override public void hide() { em.updateMessage(""); description.setHTML(""); updatePosition(null, true); setPopupPosition(tooltipEventMouseX, tooltipEventMouseY); } private int EVENT_XY_POSITION_OUTSIDE = -5000; private int tooltipEventMouseX; private int tooltipEventMouseY; public void updatePosition(Event event, boolean isFocused) { tooltipEventMouseX = getEventX(event, isFocused); tooltipEventMouseY = getEventY(event, isFocused); } private int getEventX(Event event, boolean isFocused) { return isFocused ? EVENT_XY_POSITION_OUTSIDE : DOM.eventGetClientX(event); } private int getEventY(Event event, boolean isFocused) { return isFocused ? EVENT_XY_POSITION_OUTSIDE : DOM.eventGetClientY(event); } @Override public void onBrowserEvent(Event event) { final int type = DOM.eventGetType(event); // cancel closing event if tooltip is mouseovered; the user might want // to scroll of cut&paste if (type == Event.ONMOUSEOVER) { // Cancel closing so tooltip stays open and user can copy paste the // tooltip closeTimer.cancel(); closing = false; } else if (type == Event.ONMOUSEOUT) { tooltipEventHandler.handleOnMouseOut(DOM.eventGetTarget(event)); } } /** * Replace current open tooltip with new content */ public void replaceCurrentTooltip() { if (closing) { closeTimer.cancel(); closeNow(); justClosedTimer.cancel(); justClosed = false; } showTooltip(); opening = false; } private class TooltipEventHandler implements MouseMoveHandler, KeyDownHandler, FocusHandler, BlurHandler, MouseDownHandler, MouseOutHandler { /** * Marker for handling of tooltip through focus */ private boolean handledByFocus; /** * Locate the tooltip for given element * * @param element * Element used in search * @return TooltipInfo if connector and tooltip found, null if not */ private TooltipInfo getTooltipFor(Element element) { ApplicationConnection ac = getApplicationConnection(); ComponentConnector connector = Util.getConnectorForElement(ac, RootPanel.get(), element); // Try to find first connector with proper tooltip info TooltipInfo info = null; while (connector != null) { info = connector.getTooltipInfo(element); if (info != null && info.hasMessage()) { break; } if (!(connector.getParent() instanceof ComponentConnector)) { connector = null; info = null; break; } connector = (ComponentConnector) connector.getParent(); } if (connector != null && info != null) { assert connector.hasTooltip() : "getTooltipInfo for " + Util.getConnectorString(connector) + " returned a tooltip even though hasTooltip claims there are no tooltips for the connector."; return info; } return null; } /** * Handle hide event * */ private void handleHideEvent() { hideTooltip(); } @Override public void onMouseMove(MouseMoveEvent mme) { handleShowHide(mme, false); } @Override public void onMouseDown(MouseDownEvent event) { handleHideEvent(); } @Override public void onKeyDown(KeyDownEvent event) { handleHideEvent(); } /** * Displays Tooltip when page is navigated with the keyboard. * * Tooltip is not visible. This makes it possible for assistive devices * to recognize the tooltip. */ @Override public void onFocus(FocusEvent fe) { handleShowHide(fe, true); } /** * Hides Tooltip when the page is navigated with the keyboard. * * Removes the Tooltip from page to make sure assistive devices don't * recognize it by accident. */ @Override public void onBlur(BlurEvent be) { handledByFocus = false; handleHideEvent(); } private void handleShowHide(DomEvent domEvent, boolean isFocused) { Event event = Event.as(domEvent.getNativeEvent()); Element element = Element.as(event.getEventTarget()); // We can ignore move event if it's handled by move or over already if (currentElement == element && handledByFocus == true) { return; } // If the parent (sub)component already has a tooltip open and it // hasn't changed, we ignore the event. // TooltipInfo contains a reference to the parent component that is // checked in it's equals-method. if (currentElement != null && isTooltipOpen()) { TooltipInfo newTooltip = getTooltipFor(element); if (currentTooltipInfo != null && currentTooltipInfo.equals(newTooltip)) { return; } } TooltipInfo info = getTooltipFor(element); if (info == null) { handleHideEvent(); } else { if (closing) { closeTimer.cancel(); closing = false; } if (isTooltipOpen()) { closeNow(); } setTooltipText(info); updatePosition(event, isFocused); // Schedule timer for showing the tooltip according to if it // was recently closed or not. if (BrowserInfo.get().isIOS()) { element.focus(); } int timeout = justClosed ? getQuickOpenDelay() : getOpenDelay(); if (timeout == 0) { showTooltip(); } else { showTimer.schedule(timeout); opening = true; } } handledByFocus = isFocused; currentElement = element; } @Override public void onMouseOut(MouseOutEvent moe) { Element element = WidgetUtil .getElementUnderMouse(moe.getNativeEvent()); handleOnMouseOut(element); } private void handleOnMouseOut(Element element) { if (element == null) { // hide if mouse is outside of browser window handleHideEvent(); } else { Widget owner = getOwner(); if (owner != null && !owner.getElement().isOrHasChild(element) && !hasCommonOwner(owner, element)) { // hide if mouse is no longer within the UI nor an overlay // that belongs to the UI, e.g. a Window handleHideEvent(); } } } private boolean hasCommonOwner(Widget owner, Element element) { ComponentConnector connector = Util .findPaintable(getApplicationConnection(), element); if (connector != null && connector.getConnection() != null && connector.getConnection().getUIConnector() != null) { return owner.equals( connector.getConnection().getUIConnector().getWidget()); } return false; } } private final TooltipEventHandler tooltipEventHandler = new TooltipEventHandler(); /** * Connects DOM handlers to widget that are needed for tooltip presentation. * * @param widget * Widget which DOM handlers are connected */ public void connectHandlersToWidget(Widget widget) { Profiler.enter("VTooltip.connectHandlersToWidget"); widget.addDomHandler(tooltipEventHandler, MouseOutEvent.getType()); widget.addDomHandler(tooltipEventHandler, MouseMoveEvent.getType()); widget.addDomHandler(tooltipEventHandler, MouseDownEvent.getType()); widget.addDomHandler(tooltipEventHandler, KeyDownEvent.getType()); widget.addDomHandler(tooltipEventHandler, FocusEvent.getType()); widget.addDomHandler(tooltipEventHandler, BlurEvent.getType()); Profiler.leave("VTooltip.connectHandlersToWidget"); } /** * Returns the unique id of the tooltip element. * * @return String containing the unique id of the tooltip, which always has * a value */ public String getUniqueId() { return uniqueId; } @Override public void setPopupPositionAndShow(PositionCallback callback) { if (isAttached()) { callback.setPosition(getOffsetWidth(), getOffsetHeight()); } else { super.setPopupPositionAndShow(callback); } } /** * Returns the time (in ms) the tooltip should be displayed after an event * that will cause it to be closed (e.g. mouse click outside the component, * key down). * * @return The close timeout (in ms) */ public int getCloseTimeout() { return closeTimeout; } /** * Sets the time (in ms) the tooltip should be displayed after an event that * will cause it to be closed (e.g. mouse click outside the component, key * down). * * @param closeTimeout * The close timeout (in ms) */ public void setCloseTimeout(int closeTimeout) { this.closeTimeout = closeTimeout; } /** * Returns the time (in ms) during which {@link #getQuickOpenDelay()} should * be used instead of {@link #getOpenDelay()}. The quick open delay is used * when the tooltip has very recently been shown, is currently hidden but * about to be shown again. * * @return The quick open timeout (in ms) */ public int getQuickOpenTimeout() { return quickOpenTimeout; } /** * Sets the time (in ms) that determines when {@link #getQuickOpenDelay()} * should be used instead of {@link #getOpenDelay()}. The quick open delay * is used when the tooltip has very recently been shown, is currently * hidden but about to be shown again. * * @param quickOpenTimeout * The quick open timeout (in ms) */ public void setQuickOpenTimeout(int quickOpenTimeout) { this.quickOpenTimeout = quickOpenTimeout; } /** * Returns the time (in ms) that should elapse before a tooltip will be * shown, in the situation when a tooltip has very recently been shown * (within {@link #getQuickOpenDelay()} ms). * * @return The quick open delay (in ms) */ public int getQuickOpenDelay() { return quickOpenDelay; } /** * Sets the time (in ms) that should elapse before a tooltip will be shown, * in the situation when a tooltip has very recently been shown (within * {@link #getQuickOpenDelay()} ms). * * @param quickOpenDelay * The quick open delay (in ms) */ public void setQuickOpenDelay(int quickOpenDelay) { this.quickOpenDelay = quickOpenDelay; } /** * Returns the time (in ms) that should elapse after an event triggering * tooltip showing has occurred (e.g. mouse over) before the tooltip is * shown. If a tooltip has recently been shown, then * {@link #getQuickOpenDelay()} is used instead of this. * * @return The open delay (in ms) */ public int getOpenDelay() { return openDelay; } /** * Sets the time (in ms) that should elapse after an event triggering * tooltip showing has occurred (e.g. mouse over) before the tooltip is * shown. If a tooltip has recently been shown, then * {@link #getQuickOpenDelay()} is used instead of this. * * @param openDelay * The open delay (in ms) */ public void setOpenDelay(int openDelay) { this.openDelay = openDelay; } /** * Sets the maximum width of the tooltip popup. * * @param maxWidth * The maximum width the tooltip popup (in pixels) */ public void setMaxWidth(int maxWidth) { this.maxWidth = maxWidth; } /** * Returns the maximum width of the tooltip popup. * * @return The maximum width the tooltip popup (in pixels) */ public int getMaxWidth() { return maxWidth; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy