diff options
author | Markus Koivisto <markus@vaadin.com> | 2015-10-19 11:51:03 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2015-10-27 12:27:22 +0000 |
commit | bd76f568d5b7d2c61870d2b10975f03a6acd741d (patch) | |
tree | 19405e2b38778d014a11b340d07dfa2195017400 /client | |
parent | 521663c216bd979d5fd2e2734f1c70b64e8a72d4 (diff) | |
download | vaadin-framework-bd76f568d5b7d2c61870d2b10975f03a6acd741d.tar.gz vaadin-framework-bd76f568d5b7d2c61870d2b10975f03a6acd741d.zip |
Add ContextClickEvent for touch events (#19206)
Touchstart events now fire a contextclick event if there is a context
click listener.
Refactored sendContextClickEvent to accomodate the change.
Change-Id: I9bce5948f89149e9ecb261cfd8ae918470ccec3e
Diffstat (limited to 'client')
4 files changed, 205 insertions, 31 deletions
diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index cd7007efec..336469139c 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -31,8 +31,8 @@ import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Timer; @@ -1183,7 +1183,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements } @Override - protected void sendContextClickEvent(ContextMenuEvent event) { + protected void sendContextClickEvent(MouseEventDetails details, + EventTarget eventTarget) { EventCellReference<JsonObject> eventCell = getWidget().getEventCell(); Section section = eventCell.getSection(); @@ -1193,14 +1194,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements } String columnId = getColumnId(eventCell.getColumn()); - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent()); getRpcProxy(GridServerRpc.class).contextClick(eventCell.getRowIndex(), rowKey, columnId, section, details); - - event.preventDefault(); - event.stopPropagation(); } /** diff --git a/client/src/com/vaadin/client/ui/AbstractComponentConnector.java b/client/src/com/vaadin/client/ui/AbstractComponentConnector.java index c0a1d92b1e..7afefe8061 100644 --- a/client/src/com/vaadin/client/ui/AbstractComponentConnector.java +++ b/client/src/com/vaadin/client/ui/AbstractComponentConnector.java @@ -18,9 +18,18 @@ package com.vaadin.client.ui; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.EventTarget; +import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.dom.client.ContextMenuHandler; +import com.google.gwt.event.dom.client.TouchEndEvent; +import com.google.gwt.event.dom.client.TouchEndHandler; +import com.google.gwt.event.dom.client.TouchMoveEvent; +import com.google.gwt.event.dom.client.TouchMoveHandler; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.Widget; @@ -46,6 +55,7 @@ import com.vaadin.shared.ComponentConstants; import com.vaadin.shared.Connector; import com.vaadin.shared.ContextClickRpc; import com.vaadin.shared.EventId; +import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.ui.ComponentStateUtil; import com.vaadin.shared.ui.TabIndexState; @@ -67,6 +77,19 @@ public abstract class AbstractComponentConnector extends AbstractConnector */ private JsArrayString styleNames = JsArrayString.createArray().cast(); + private Timer longTouchTimer; + + // TODO encapsulate into a nested class + private HandlerRegistration touchStartHandler; + private HandlerRegistration touchMoveHandler; + private HandlerRegistration touchEndHandler; + private int touchStartX; + private int touchStartY; + + // long touch event delay + // TODO replace with global constant for accessibility + private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 250; + /** * Default constructor */ @@ -80,14 +103,184 @@ public abstract class AbstractComponentConnector extends AbstractConnector new ContextMenuHandler() { @Override public void onContextMenu(ContextMenuEvent event) { - sendContextClickEvent(event); + final MouseEventDetails mouseEventDetails = MouseEventDetailsBuilder.buildMouseEventDetails( + event.getNativeEvent(), getWidget() + .getElement()); + + event.preventDefault(); + event.stopPropagation(); + sendContextClickEvent(mouseEventDetails, event + .getNativeEvent().getEventTarget()); } }, ContextMenuEvent.getType()); + + // if the widget has a contextclick listener, add touch support as + // well. + + registerTouchHandlers(); + } else if (contextHandler != null && !hasEventListener(EventId.CONTEXT_CLICK)) { contextHandler.removeHandler(); contextHandler = null; + + // remove the touch handlers as well + unregisterTouchHandlers(); + + } + } + + /** + * The new default behaviour is for long taps to fire a contextclick event + * if there's a contextclick listener attached to the component. + * + * If you do not want this in your component, override this with a blank + * method to get rid of said behaviour. + * + * @since 7.6 + */ + protected void unregisterTouchHandlers() { + touchStartHandler.removeHandler(); + touchStartHandler = null; + touchMoveHandler.removeHandler(); + touchMoveHandler = null; + touchEndHandler.removeHandler(); + touchEndHandler = null; + } + + /** + * The new default behaviour is for long taps to fire a contextclick event + * if there's a contextclick listener attached to the component. + * + * If you do not want this in your component, override this with a blank + * method to get rid of said behaviour. + * + * @since 7.6 + */ + protected void registerTouchHandlers() { + touchStartHandler = getWidget().addDomHandler(new TouchStartHandler() { + + @Override + public void onTouchStart(final TouchStartEvent event) { + + /* + * we need to build mouseEventDetails eagerly - the event won't + * be guaranteed to be around when the timer executes. At least + * this was the case with iOS devices. + */ + + final MouseEventDetails mouseEventDetails = MouseEventDetailsBuilder.buildMouseEventDetails( + event.getNativeEvent(), getWidget().getElement()); + + final EventTarget eventTarget = event.getNativeEvent() + .getEventTarget(); + + longTouchTimer = new Timer() { + + @Override + public void run() { + cancelParentTouchTimers(); // we're handling this event, + // our parent components + // don't need to bother with + // it anymore. + // The default context click + // implementation only provides the + // mouse coordinates relative to root + // element of widget. + + sendContextClickEvent(mouseEventDetails, eventTarget); + + } + }; + + Touch touch = event.getChangedTouches().get(0); + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + + longTouchTimer.schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + + } + }, TouchStartEvent.getType()); + + touchMoveHandler = getWidget().addDomHandler(new TouchMoveHandler() { + + @Override + public void onTouchMove(TouchMoveEvent event) { + if (isSignificantMove(event)) { + // Moved finger before the context menu timer + // expired, so let the browser handle the event. + cancelTouchTimer(); + } + + } + + // mostly copy-pasted code from VScrollTable + // TODO refactor main logic to a common class + private boolean isSignificantMove(TouchMoveEvent event) { + if (longTouchTimer == null) { + // no touch start + return false; + } + + // Calculate the distance between touch start and the current + // touch + // position + Touch touch = event.getChangedTouches().get(0); + int deltaX = touch.getClientX() - touchStartX; + int deltaY = touch.getClientY() - touchStartY; + int delta = deltaX * deltaX + deltaY * deltaY; + + // Compare to the square of the significant move threshold to + // remove the need for a square root + if (delta > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD + * TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + return false; + } + }, TouchMoveEvent.getType()); + + touchEndHandler = getWidget().addDomHandler(new TouchEndHandler() { + + @Override + public void onTouchEnd(TouchEndEvent event) { + // cancel the timer so the event doesn't fire + cancelTouchTimer(); + + } + }, TouchEndEvent.getType()); + } + + /** + * If a long touch event timer is running, cancel it. + * + * @since 7.6 + */ + private void cancelTouchTimer() { + if (longTouchTimer != null) { + longTouchTimer.cancel(); + } + } + + /** + * Cancel the timer recursively for parent components that have timers + * running + * + * @since 7.6 + */ + private void cancelParentTouchTimers() { + ServerConnector parent = getParent(); + + // we have to account for the parent being something other than an + // abstractcomponent. getParent returns null for the root element. + + while (parent != null) { + if (parent instanceof AbstractComponentConnector) { + ((AbstractComponentConnector) parent).cancelTouchTimer(); + } + parent = parent.getParent(); } + } /** @@ -98,15 +291,12 @@ public abstract class AbstractComponentConnector extends AbstractConnector * @since 7.6 * @param event */ - protected void sendContextClickEvent(ContextMenuEvent event) { - event.preventDefault(); - event.stopPropagation(); + protected void sendContextClickEvent(MouseEventDetails details, + EventTarget eventTarget) { // The default context click implementation only provides the mouse // coordinates relative to root element of widget. - getRpcProxy(ContextClickRpc.class).contextClick( - MouseEventDetailsBuilder.buildMouseEventDetails( - event.getNativeEvent(), getWidget().getElement())); + getRpcProxy(ContextClickRpc.class).contextClick(details); } /** diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java index a14ff15481..0cce611191 100644 --- a/client/src/com/vaadin/client/ui/table/TableConnector.java +++ b/client/src/com/vaadin/client/ui/table/TableConnector.java @@ -24,7 +24,6 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; @@ -34,7 +33,6 @@ import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; import com.vaadin.client.DirectionalManagedLayout; import com.vaadin.client.HasComponentsConnector; -import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.Paintable; import com.vaadin.client.ServerConnector; import com.vaadin.client.TooltipInfo; @@ -83,8 +81,9 @@ public class TableConnector extends AbstractFieldConnector implements } @Override - protected void sendContextClickEvent(ContextMenuEvent event) { - EventTarget eventTarget = event.getNativeEvent().getEventTarget(); + protected void sendContextClickEvent(MouseEventDetails details, + EventTarget eventTarget) { + if (!Element.is(eventTarget)) { return; } @@ -118,13 +117,6 @@ public class TableConnector extends AbstractFieldConnector implements } } - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent()); - - // Prevent browser default context menu - event.preventDefault(); - event.stopPropagation(); - getRpcProxy(TableServerRpc.class).contextClick(rowKey, colKey, section, details); } diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java index 842be9c4d3..6fec6b9f38 100644 --- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java +++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java @@ -23,10 +23,8 @@ import java.util.Set; import com.google.gwt.aria.client.Roles; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; -import com.google.gwt.event.dom.client.ContextMenuEvent; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; -import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.Paintable; import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; @@ -379,16 +377,14 @@ public class TreeConnector extends AbstractComponentConnector implements } @Override - protected void sendContextClickEvent(ContextMenuEvent event) { - EventTarget eventTarget = event.getNativeEvent().getEventTarget(); + protected void sendContextClickEvent(MouseEventDetails details, + EventTarget eventTarget) { if (!Element.is(eventTarget)) { return; } Element e = Element.as(eventTarget); String key = null; - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent()); if (getWidget().body.getElement().isOrHasChild(e)) { TreeNode t = WidgetUtil.findWidget(e, TreeNode.class); |