// Copyright 2012 Google Inc. 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. package org.eclipse.che.ide.util.dom; import elemental.dom.Element; import elemental.dom.Node; import elemental.events.Event; import elemental.events.EventListener; import elemental.events.MouseEvent; import elemental.html.ClientRect; import elemental.html.DivElement; import elemental.js.dom.JsElement; import org.eclipse.che.ide.util.browser.UserAgent; import com.google.gwt.user.client.DOM; /** Utility methods for DOM manipulation. */ public final class DomUtils { public static class Offset { public int top = 0; public int left = 0; private Offset() { } private Offset(int top, int left) { this.top = top; this.left = left; } } private static final EventListener STOP_PROPAGATION_EVENT_LISTENER = new EventListener() { @Override public void handleEvent(Event evt) { evt.stopPropagation(); evt.preventDefault(); } }; /** Returns the client offset to the top-left of the given element. */ @Deprecated public static Offset calculateElementClientOffset(Element element) { return calculateElementOffset(element, null, false); } /** * Returns an offset to the top-left of a child element relative to the * top-left of an ancestor element, optionally including any scroll top or * left in elements from the ancestor (inclusive) to the child (exclusive). * * @param ancestorElement * optional, if null the offset from the top-left of * the page will be given. Should not be the childElement. */ @Deprecated public static Offset calculateElementOffset(Element childElement, Element ancestorElement, boolean includeScroll) { Offset offset = new Offset(); Element element = childElement; for (; element.getOffsetParent() != null && element != ancestorElement; element = element.getOffsetParent()) { offset.top += element.getOffsetTop(); offset.left += element.getOffsetLeft(); if (!includeScroll) { offset.top -= element.getOffsetParent().getScrollTop(); offset.left -= element.getOffsetParent().getScrollLeft(); } } return offset; } /** * Wrapper for getting the offsetX from a mouse event that provides a fallback * implementation for Firefox. (See * https://bugzilla.mozilla.org/show_bug.cgi?id=122665#c3 ) */ public static int getOffsetX(MouseEvent event) { if (UserAgent.isFirefox()) { return event.getClientX() - calculateElementClientOffset((Element)event.getTarget()).left; } else { return event.getOffsetX(); } } /** @see #getOffsetX(MouseEvent) */ public static int getOffsetY(MouseEvent event) { if (UserAgent.isFirefox()) { return event.getClientY() - calculateElementClientOffset((Element)event.getTarget()).top; } else { return event.getOffsetY(); } } public static Element getNthChild(Element element, int index) { Element child = getFirstChildElement(element); while (child != null && index > 0) { --index; child = getNextSiblingElement(element); } return child; } public static Element getNthChildWithClassName(Element element, int index, String className) { Element child = getFirstChildElement(element); while (child != null) { if (Elements.hasClassName(className, child)) { --index; if (index < 0) { break; } } child = getNextSiblingElement(child); } return child; } /** @return number of previous sibling elements that have the given class */ public static int getSiblingIndexWithClassName(Element element, String className) { int index = 0; while (element != null) { element = (Element)element.getPreviousSibling(); if (element != null && Elements.hasClassName(className, element)) { ++index; } } return index; } public static Element getFirstElementByClassName(Element element, String className) { return (Element)element.getElementsByClassName(className).item(0); } public static DivElement appendDivWithTextContent(Element root, String className, String text) { DivElement element = Elements.createDivElement(className); element.setTextContent(text); root.appendChild(element); return element; } /** * Ensures that the {@code scrollable} element is scrolled such that * {@code target} is visible. * <p/> * Note: This can trigger a synchronous layout. */ public static boolean ensureScrolledTo(Element scrollable, Element target) { ClientRect targetBounds = target.getBoundingClientRect(); ClientRect scrollableBounds = scrollable.getBoundingClientRect(); int deltaBottoms = (int)(targetBounds.getBottom() - scrollableBounds.getBottom()); int deltaTops = (int)(targetBounds.getTop() - scrollableBounds.getTop()); if (deltaTops >= 0 && deltaBottoms <= 0) { // In bounds return false; } if (targetBounds.getHeight() > scrollableBounds.getHeight() || deltaTops < 0) { /* * Selected is taller than viewport height or selected is scrolled above * viewport, so set to top */ scrollable.setScrollTop(scrollable.getScrollTop() + deltaTops); } else { // Selected is scrolled below viewport scrollable.setScrollTop(scrollable.getScrollTop() + deltaBottoms); } return true; } /** * Checks whether the given {@code target} element is fully visible in * {@code scrollable}'s scrolled viewport. * <p/> * Note: This can trigger a synchronous layout. */ public static boolean isFullyInScrollViewport(Element scrollable, Element target) { ClientRect targetBounds = target.getBoundingClientRect(); ClientRect scrollableBounds = scrollable.getBoundingClientRect(); return targetBounds.getTop() >= scrollableBounds.getTop() && targetBounds.getBottom() <= scrollableBounds.getBottom(); } /** * Stops propagation for the common mouse events (down, move, up, click, * dblclick). */ public static void stopMousePropagation(Element element) { element.addEventListener(Event.MOUSEDOWN, STOP_PROPAGATION_EVENT_LISTENER, false); element.addEventListener(Event.MOUSEMOVE, STOP_PROPAGATION_EVENT_LISTENER, false); element.addEventListener(Event.MOUSEUP, STOP_PROPAGATION_EVENT_LISTENER, false); element.addEventListener(Event.CLICK, STOP_PROPAGATION_EVENT_LISTENER, false); element.addEventListener(Event.DBLCLICK, STOP_PROPAGATION_EVENT_LISTENER, false); } /** * Prevent propagation of scrolling to parent containers on mouse wheeling, * when target container can not be scrolled anymore. */ public static void preventExcessiveScrollingPropagation(final Element container) { // The MOUSEWHEEL does not exist on FF, so in FF the common browser behavior won't be canceled and the parent container will be scrolled. container.addEventListener(Event.MOUSEWHEEL, new EventListener() { @Override public void handleEvent(Event evt) { int deltaY = DOM.eventGetMouseWheelVelocityY((com.google.gwt.user.client.Event)evt); int scrollTop = container.getScrollTop(); if (deltaY < 0 && scrollTop == 0) { evt.preventDefault(); } else if (deltaY > 0 && scrollTop == (container.getScrollHeight() - container.getClientHeight())) { evt.preventDefault(); } evt.stopPropagation(); } }, false); } /** * Doing Elements.asJsElement(button).setDisabled(true); doesn't work for buttons, possibly * because they're actually AnchorElements */ public static void setDisabled(Element element, boolean disabled) { if (disabled) { element.setAttribute("disabled", "disabled"); } else { element.removeAttribute("disabled"); } } public static boolean getDisabled(Element element) { return element.hasAttribute("disabled"); } /** @return true if the provided element or one of its children have focus. */ public static boolean isElementOrChildFocused(Element element) { Element active = element.getOwnerDocument().getActiveElement(); return element.contains(active); } public static JsElement getFirstChildElement(Element element) { elemental.dom.Node child = element.getFirstChild(); while ((child != null) && child.getNodeType() != Node.ELEMENT_NODE) { child = child.getNextSibling(); } return ((JsElement)child); } public static JsElement getNextSiblingElement(Element element) { Node sib = element.getNextSibling(); while ((sib != null) && sib.getNodeType() != Node.ELEMENT_NODE) { sib = sib.getNextSibling(); } return ((JsElement)sib); } public static void removeFromParent(Element element) { Element parent = getParentElement(element); if (parent != null) { parent.removeChild(element); } } public static elemental.js.dom.JsElement getParentElement(Element element) { Node parent = element.getParentNode(); if ((parent == null) || parent.getNodeType() != Node.ELEMENT_NODE) { parent = null; } return (JsElement)parent; } private DomUtils() { } // COV_NF_LINE }