summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorHeikki Ohinmaa <heikki@vaadin.com>2014-08-07 13:27:14 +0300
committerVaadin Code Review <review@vaadin.com>2014-08-07 13:31:08 +0000
commit7a243926ca0b744f62078b78706ee6e71421ba33 (patch)
tree5cff3d695ccef4bb62fc8d42fe8f2be9742208b1 /client
parentff47bdd97b03a42dfc812b4dc9ad71fa45ce3827 (diff)
downloadvaadin-framework-7a243926ca0b744f62078b78706ee6e71421ba33.tar.gz
vaadin-framework-7a243926ca0b744f62078b78706ee6e71421ba33.zip
Context menu is not shown in Table body on mobile browsers (#13694)
Includes updated tests. Change-Id: I928b888302a2cd22b7c94600fd4f8b7608ba0ca8
Diffstat (limited to 'client')
-rw-r--r--client/src/com/vaadin/client/ui/VScrollTable.java227
1 files changed, 197 insertions, 30 deletions
diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java
index 6a1e8664ed..c1e83ef08b 100644
--- a/client/src/com/vaadin/client/ui/VScrollTable.java
+++ b/client/src/com/vaadin/client/ui/VScrollTable.java
@@ -47,8 +47,6 @@ import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ContextMenuEvent;
-import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -129,6 +127,137 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
ActionOwner, SubPartAware, DeferredWorker {
+ /**
+ * Simple interface for parts of the table capable of owning a context menu.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+ private interface ContextMenuOwner {
+ public void showContextMenu(Event event);
+ }
+
+ /**
+ * Handles showing context menu on "long press" from a touch screen.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+ private class TouchContextProvider {
+ private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
+ private Timer contextTouchTimeout;
+
+ private Event touchStart;
+ private int touchStartY;
+ private int touchStartX;
+
+ private ContextMenuOwner target;
+
+ /**
+ * Initializes a handler for a certain context menu owner.
+ *
+ * @param target
+ * the owner of the context menu
+ */
+ public TouchContextProvider(ContextMenuOwner target) {
+ this.target = target;
+ }
+
+ /**
+ * Cancels the current context touch timeout.
+ */
+ public void cancel() {
+ if (contextTouchTimeout != null) {
+ contextTouchTimeout.cancel();
+ contextTouchTimeout = null;
+ }
+ touchStart = null;
+ }
+
+ /**
+ * A function to handle touch context events in a table.
+ *
+ * @param event
+ * browser event to handle
+ */
+ public void handleTouchEvent(final Event event) {
+ int type = event.getTypeInt();
+
+ switch (type) {
+ case Event.ONCONTEXTMENU:
+ target.showContextMenu(event);
+ break;
+ case Event.ONTOUCHSTART:
+ // save position to fields, touches in events are same
+ // instance during the operation.
+ touchStart = event;
+
+ Touch touch = event.getChangedTouches().get(0);
+ touchStartX = touch.getClientX();
+ touchStartY = touch.getClientY();
+
+ if (contextTouchTimeout == null) {
+ contextTouchTimeout = new Timer() {
+
+ @Override
+ public void run() {
+ if (touchStart != null) {
+ // Open the context menu if finger
+ // is held in place long enough.
+ target.showContextMenu(touchStart);
+ event.preventDefault();
+ touchStart = null;
+ }
+ }
+ };
+ }
+ contextTouchTimeout.schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
+ break;
+ case Event.ONTOUCHCANCEL:
+ case Event.ONTOUCHEND:
+ cancel();
+ break;
+ case Event.ONTOUCHMOVE:
+ if (isSignificantMove(event)) {
+ // Moved finger before the context menu timer
+ // expired, so let the browser handle the event.
+ cancel();
+ }
+ }
+ }
+
+ /**
+ * Calculates how many pixels away the user's finger has traveled. This
+ * reduces the chance of small non-intentional movements from canceling
+ * the long press detection.
+ *
+ * @param event
+ * the Event for which to check the move distance
+ * @return true if this is considered an intentional move by the user
+ */
+ protected boolean isSignificantMove(Event event) {
+ if (touchStart == 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;
+ }
+ }
+
public static final String STYLENAME = "v-table";
public enum SelectMode {
@@ -372,8 +501,49 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
/** For internal use only. May be removed or replaced in the future. */
public final TableFooter tFoot = new TableFooter();
+ /** Handles context menu for table body */
+ private ContextMenuOwner contextMenuOwner = new ContextMenuOwner() {
+
+ @Override
+ public void showContextMenu(Event event) {
+ int left = Util.getTouchOrMouseClientX(event);
+ int top = Util.getTouchOrMouseClientY(event);
+ boolean menuShown = handleBodyContextMenu(left, top);
+ if (menuShown) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+ };
+
+ /** Handles touch events to display a context menu for table body */
+ private TouchContextProvider touchContextProvider = new TouchContextProvider(
+ contextMenuOwner);
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * Overwrites onBrowserEvent function on FocusableScrollPanel to give event
+ * access to touchContextProvider. Has to be public to give TableConnector
+ * access to the scrollBodyPanel field.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+ public class FocusableScrollContextPanel extends FocusableScrollPanel {
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ touchContextProvider.handleTouchEvent(event);
+ };
+
+ public FocusableScrollContextPanel(boolean useFakeFocusElement) {
+ super(useFakeFocusElement);
+ }
+ }
+
/** For internal use only. May be removed or replaced in the future. */
- public final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(
+ public final FocusableScrollContextPanel scrollBodyPanel = new FocusableScrollContextPanel(
true);
private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
@@ -613,16 +783,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
}
scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
- scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS);
-
- scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU);
- scrollBodyPanel.addDomHandler(new ContextMenuHandler() {
-
- @Override
- public void onContextMenu(ContextMenuEvent event) {
- handleBodyContextMenu(event);
- }
- }, ContextMenuEvent.getType());
+ scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
setStyleName(STYLENAME);
@@ -684,19 +845,23 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
});
}
- private void handleBodyContextMenu(ContextMenuEvent event) {
+ /**
+ * Handles a context menu event on table body.
+ *
+ * @param left
+ * left position of the context menu
+ * @param top
+ * top position of the context menu
+ * @return true if a context menu was shown, otherwise false
+ */
+ private boolean handleBodyContextMenu(int left, int top) {
if (enabled && bodyActionKeys != null) {
- int left = Util.getTouchOrMouseClientX(event.getNativeEvent());
- int top = Util.getTouchOrMouseClientY(event.getNativeEvent());
top += Window.getScrollTop();
left += Window.getScrollLeft();
client.getContextMenu().showAt(this, left, top);
-
- // Only prevent browser context menu if there are action handlers
- // registered
- event.stopPropagation();
- event.preventDefault();
+ return true;
}
+ return false;
}
/**
@@ -2420,7 +2585,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
int firstRendered = scrollBody.getFirstRendered();
int lastRendered = scrollBody.getLastRendered();
-
if (lastRendered > totalRows) {
lastRendered = totalRows - 1;
}
@@ -5137,7 +5301,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
return -1;
}
- public class VScrollTableRow extends Panel implements ActionOwner {
+ public class VScrollTableRow extends Panel implements ActionOwner,
+ ContextMenuOwner {
private static final int TOUCHSCROLL_TIMEOUT = 100;
private static final int DRAGMODE_MULTIROW = 2;
@@ -5155,6 +5320,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
private Timer dragTouchTimeout;
private int touchStartY;
private int touchStartX;
+
+ private TouchContextProvider touchContextProvider = new TouchContextProvider(
+ this);
+
private TooltipInfo tooltipInfo = null;
private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
private boolean isDragging = false;
@@ -5610,6 +5779,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
boolean touchEventHandled = false;
if (enabled && hasNativeTouchScrolling) {
+ touchContextProvider.handleTouchEvent(event);
+
final Element targetTdOrTr = getEventTargetTdOrTr(event);
final int type = event.getTypeInt();
@@ -5870,9 +6041,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
Util.simulateClickFromTouchEvent(touchStart, this);
touchStart = null;
}
- if (contextTouchTimeout != null) {
- contextTouchTimeout.cancel();
- }
+ touchContextProvider.cancel();
break;
case Event.ONTOUCHMOVE:
if (isSignificantMove(event)) {
@@ -5887,9 +6056,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
.getActiveScrollDelegate() == null)) {
startRowDrag(touchStart, type, targetTdOrTr);
}
- if (contextTouchTimeout != null) {
- contextTouchTimeout.cancel();
- }
+ touchContextProvider.cancel();
/*
* Avoid clicks and drags by clearing touch start
* flag.
@@ -6125,6 +6292,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
return getTdOrTr(eventTarget);
}
+ @Override
public void showContextMenu(Event event) {
if (enabled && actionKeys != null) {
// Show context menu if there are registered action handlers
@@ -6291,7 +6459,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
public Widget getWidgetForPaintable() {
return this;
}
-
}
protected class VScrollTableGeneratedRow extends VScrollTableRow {