aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2015-08-19 13:11:21 +0300
committerMika Murtojärvi <mika@vaadin.com>2015-08-27 12:10:38 +0000
commitc6622ac5cbf4ddbcec35e02f92f74cf46d147e71 (patch)
treeae3b6699124a05fa1062eb5f503f2412de1744cd
parent4afe7d033d345abc2dd1140afd09ec848fd460e7 (diff)
downloadvaadin-framework-c6622ac5cbf4ddbcec35e02f92f74cf46d147e71.tar.gz
vaadin-framework-c6622ac5cbf4ddbcec35e02f92f74cf46d147e71.zip
Make Grid editor event handling more customizable
The event handling is factored to a separate EventHandler interface, with the default behavior contained in class DefaultEditorEventHandler. The latter is made easily extensible by providing several customization points. Change-Id: I653d37197cc9314980a8d36983783f227af0cd5e
-rw-r--r--client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java225
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java222
2 files changed, 371 insertions, 76 deletions
diff --git a/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
new file mode 100644
index 0000000000..564b0f1a03
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2000-2014 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.widget.grid;
+
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.Event;
+import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.ui.FocusUtil;
+import com.vaadin.client.widget.grid.events.EditorMoveEvent;
+import com.vaadin.client.widget.grid.events.EditorOpenEvent;
+import com.vaadin.client.widgets.Grid.Editor;
+import com.vaadin.client.widgets.Grid.EditorDomEvent;
+
+/**
+ * The default handler for Grid editor events. Offers several overridable
+ * protected methods for easier customization.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> {
+
+ public static final int KEYCODE_OPEN = KeyCodes.KEY_ENTER;
+ public static final int KEYCODE_MOVE = KeyCodes.KEY_ENTER;
+ public static final int KEYCODE_CLOSE = KeyCodes.KEY_ESCAPE;
+
+ private double lastTouchEventTime = 0;
+ private int lastTouchEventX = -1;
+ private int lastTouchEventY = -1;
+ private int lastTouchEventRow = -1;
+
+ /**
+ * Returns whether the given event is a touch event that should open the
+ * editor.
+ *
+ * @param event
+ * the received event
+ * @return whether the event is a touch open event
+ */
+ protected boolean isTouchOpenEvent(EditorDomEvent<T> event) {
+ final Event e = event.getDomEvent();
+ final int type = e.getTypeInt();
+
+ final double now = Duration.currentTimeMillis();
+ final int currentX = WidgetUtil.getTouchOrMouseClientX(e);
+ final int currentY = WidgetUtil.getTouchOrMouseClientY(e);
+
+ final boolean validTouchOpenEvent = type == Event.ONTOUCHEND
+ && now - lastTouchEventTime < 500
+ && lastTouchEventRow == event.getCell().getRowIndex()
+ && Math.abs(lastTouchEventX - currentX) < 20
+ && Math.abs(lastTouchEventY - currentY) < 20;
+
+ if (type == Event.ONTOUCHSTART) {
+ lastTouchEventX = currentX;
+ lastTouchEventY = currentY;
+ }
+
+ if (type == Event.ONTOUCHEND) {
+ lastTouchEventTime = now;
+ lastTouchEventRow = event.getCell().getRowIndex();
+ }
+
+ return validTouchOpenEvent;
+ }
+
+ /**
+ * Returns whether the given event should open the editor. The default
+ * implementation returns true if and only if the event is a doubleclick or
+ * if it is a keydown event and the keycode is {@link #KEYCODE_OPEN}.
+ *
+ * @param event
+ * the received event
+ * @return true if the event is an open event, false otherwise
+ */
+ protected boolean isOpenEvent(EditorDomEvent<T> event) {
+ final Event e = event.getDomEvent();
+ return e.getTypeInt() == Event.ONDBLCLICK
+ || (e.getTypeInt() == Event.ONKEYDOWN && e.getKeyCode() == KEYCODE_OPEN)
+ || isTouchOpenEvent(event);
+ }
+
+ /**
+ * Opens the editor on the appropriate row if the received event is an open
+ * event. The default implementation uses
+ * {@link #isOpenEvent(EditorDomEvent) isOpenEvent}.
+ *
+ * @param event
+ * the received event
+ * @return true if this method handled the event and nothing else should be
+ * done, false otherwise
+ */
+ protected boolean handleOpenEvent(EditorDomEvent<T> event) {
+ if (isOpenEvent(event)) {
+ final EventCellReference<T> cell = event.getCell();
+
+ editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM());
+
+ // FIXME should be in editRow
+ event.getGrid().fireEvent(new EditorOpenEvent(cell));
+
+ event.getDomEvent().preventDefault();
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Moves the editor to another row if the received event is a move event.
+ * The default implementation moves the editor to the clicked row if the
+ * event is a click; otherwise, if the event is a keydown and the keycode is
+ * {@link #KEYCODE_MOVE}, moves the editor one row up or down if the shift
+ * key is pressed or not, respectively.
+ *
+ * @param event
+ * the received event
+ * @return true if this method handled the event and nothing else should be
+ * done, false otherwise
+ */
+ protected boolean handleMoveEvent(EditorDomEvent<T> event) {
+ Event e = event.getDomEvent();
+ final EventCellReference<T> cell = event.getCell();
+
+ // TODO: Move on touch events
+ if (e.getTypeInt() == Event.ONCLICK) {
+
+ editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM());
+
+ // FIXME should be in editRow
+ event.getGrid().fireEvent(new EditorMoveEvent(cell));
+
+ return true;
+ }
+
+ else if (e.getTypeInt() == Event.ONKEYDOWN
+ && e.getKeyCode() == KEYCODE_MOVE) {
+
+ editRow(event, event.getRowIndex() + (e.getShiftKey() ? -1 : +1),
+ event.getFocusedColumnIndex());
+
+ // FIXME should be in editRow
+ event.getGrid().fireEvent(new EditorMoveEvent(cell));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the given event should close the editor. The default
+ * implementation returns true if and only if the event is a keydown event
+ * and the keycode is {@link #KEYCODE_CLOSE}.
+ *
+ * @param event
+ * the received event
+ * @return true if the event is a close event, false otherwise
+ */
+ protected boolean isCloseEvent(EditorDomEvent<T> event) {
+ final Event e = event.getDomEvent();
+ return e.getTypeInt() == Event.ONKEYDOWN
+ && e.getKeyCode() == KEYCODE_CLOSE;
+ }
+
+ /**
+ * Closes the editor if the received event is a close event. The default
+ * implementation uses {@link #isCloseEvent(EditorDomEvent) isCloseEvent}.
+ *
+ * @param event
+ * the received event
+ * @return true if this method handled the event and nothing else should be
+ * done, false otherwise
+ */
+ protected boolean handleCloseEvent(EditorDomEvent<T> event) {
+ if (isCloseEvent(event)) {
+ event.getEditor().cancel();
+ FocusUtil.setFocus(event.getGrid(), true);
+ return true;
+ }
+ return false;
+ }
+
+ protected void editRow(EditorDomEvent<T> event, int rowIndex, int colIndex) {
+ int rowCount = event.getGrid().getDataSource().size();
+ // Limit rowIndex between 0 and rowCount - 1
+ rowIndex = Math.max(0, Math.min(rowCount - 1, rowIndex));
+
+ int colCount = event.getGrid().getVisibleColumns().size();
+ // Limit colIndex between 0 and colCount - 1
+ colIndex = Math.max(0, Math.min(colCount - 1, colIndex));
+
+ event.getEditor().editRow(rowIndex, colIndex);
+ }
+
+ @Override
+ public boolean handleEvent(EditorDomEvent<T> event) {
+ final Editor<T> editor = event.getEditor();
+ final boolean isBody = event.getCell().isBody();
+
+ if (event.getGrid().isEditorActive()) {
+ return (!editor.isBuffered() && isBody && handleMoveEvent(event))
+ || handleCloseEvent(event)
+ // Swallow events if editor is open and buffered (modal)
+ || editor.isBuffered();
+ } else {
+ return event.getGrid().isEnabled() && isBody
+ && handleOpenEvent(event);
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index 45d14fac30..7250ed76be 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -31,7 +31,6 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
-import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
@@ -108,6 +107,7 @@ import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
import com.vaadin.client.widget.grid.DataAvailableEvent;
import com.vaadin.client.widget.grid.DataAvailableHandler;
+import com.vaadin.client.widget.grid.DefaultEditorEventHandler;
import com.vaadin.client.widget.grid.DetailsGenerator;
import com.vaadin.client.widget.grid.EditorHandler;
import com.vaadin.client.widget.grid.EditorHandler.EditorRequest;
@@ -129,8 +129,6 @@ import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
import com.vaadin.client.widget.grid.events.EditorCloseEvent;
import com.vaadin.client.widget.grid.events.EditorEvent;
import com.vaadin.client.widget.grid.events.EditorEventHandler;
-import com.vaadin.client.widget.grid.events.EditorMoveEvent;
-import com.vaadin.client.widget.grid.events.EditorOpenEvent;
import com.vaadin.client.widget.grid.events.FooterClickHandler;
import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler;
import com.vaadin.client.widget.grid.events.FooterKeyDownHandler;
@@ -1127,23 +1125,138 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * A wrapper for native DOM events originating from Grid. In addition to the
+ * native event, contains a {@link CellReference} instance specifying which
+ * cell the event originated from.
+ *
+ * @since
+ * @param <T>
+ * The row type of the grid
+ */
+ public static class GridEvent<T> {
+ private Event event;
+ private EventCellReference<T> cell;
+
+ protected GridEvent(Event event, EventCellReference<T> cell) {
+ this.event = event;
+ this.cell = cell;
+ }
+
+ /**
+ * Returns the wrapped DOM event.
+ *
+ * @return the DOM event
+ */
+ public Event getDomEvent() {
+ return event;
+ }
+
+ /**
+ * Returns the Grid cell this event originated from.
+ *
+ * @return the event cell
+ */
+ public EventCellReference<T> getCell() {
+ return cell;
+ }
+
+ /**
+ * Returns the Grid instance this event originated from.
+ *
+ * @return the grid
+ */
+ public Grid<T> getGrid() {
+ return cell.getGrid();
+ }
+ }
+
+ /**
+ * A wrapper for native DOM events related to the {@link Editor Grid editor}
+ * .
+ *
+ * @since
+ * @param <T>
+ * the row type of the grid
+ */
+ public static class EditorDomEvent<T> extends GridEvent<T> {
+
+ protected EditorDomEvent(Event event, EventCellReference<T> cell) {
+ super(event, cell);
+ }
+
+ /**
+ * Returns the editor of the Grid this event originated from.
+ *
+ * @return the related editor instance
+ */
+ public Editor<T> getEditor() {
+ return getGrid().getEditor();
+ }
+
+ /**
+ * Returns the row index the editor is open at. If the editor is not
+ * open, returns -1.
+ *
+ * @return the index of the edited row or -1 if editor is not open
+ */
+ public int getRowIndex() {
+ return getEditor().rowIndex;
+ }
+
+ /**
+ * Returns the column index the editor was opened at. If the editor is
+ * not open, returns -1.
+ *
+ * @return the column index or -1 if editor is not open
+ */
+ public int getFocusedColumnIndex() {
+ return getEditor().focusedColumnIndex;
+ }
+ }
+
+ /**
* An editor UI for Grid rows. A single Grid row at a time can be opened for
* editing.
+ *
+ * @since
+ * @param <T>
+ * the row type of the grid
*/
- protected static class Editor<T> {
-
- public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
- public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;
+ public static class Editor<T> {
private static final String ERROR_CLASS_NAME = "error";
private static final String NOT_EDITABLE_CLASS_NAME = "not-editable";
+ /**
+ * A handler for events related to the Grid editor. Responsible for
+ * opening, moving or closing the editor based on the received event.
+ *
+ * @since
+ * @author Vaadin Ltd
+ * @param <T>
+ * the row type of the grid
+ */
+ public interface EventHandler<T> {
+ /**
+ * Handles editor-related events in an appropriate way. Opens,
+ * moves, or closes the editor based on the given event.
+ *
+ * @param event
+ * the received event
+ * @return true if the event was handled and nothing else should be
+ * done, false otherwise
+ */
+ boolean handleEvent(EditorDomEvent<T> event);
+ }
+
protected enum State {
INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING
}
private Grid<T> grid;
private EditorHandler<T> handler;
+ private EventHandler<T> eventHandler = GWT
+ .create(DefaultEditorEventHandler.class);
private DivElement editorOverlay = DivElement.as(DOM.createDiv());
private DivElement cellWrapper = DivElement.as(DOM.createDiv());
@@ -1998,6 +2111,27 @@ public class Grid<T> extends ResizeComposite implements
messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE);
}
}
+
+ /**
+ * Sets the event handler for this Editor.
+ *
+ * @since
+ * @param handler
+ * the new event handler
+ */
+ public void setEventHandler(EventHandler<T> handler) {
+ eventHandler = handler;
+ }
+
+ /**
+ * Returns the event handler of this Editor.
+ *
+ * @since
+ * @return the current event handler
+ */
+ public EventHandler<T> getEventHandler() {
+ return eventHandler;
+ }
}
public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
@@ -3739,10 +3873,6 @@ public class Grid<T> extends ResizeComposite implements
private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator();
private boolean enabled = true;
- private double lastTouchEventTime = 0;
- private int lastTouchEventX = -1;
- private int lastTouchEventY = -1;
- private int lastTouchEventRow = -1;
private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater();
@@ -5570,8 +5700,8 @@ public class Grid<T> extends ResizeComposite implements
if (visibleRows.contains(rowIndex)) {
rowElement = escalator.getBody().getRowElement(rowIndex);
} else {
- rowElement = null;
getLogger().warning("Row index was not among visible row range");
+ return;
}
row.set(rowIndex, dataSource.getRow(rowIndex), rowElement);
@@ -6114,7 +6244,7 @@ public class Grid<T> extends ResizeComposite implements
return footer.isVisible();
}
- protected Editor<T> getEditor() {
+ public Editor<T> getEditor() {
return editor;
}
@@ -6641,7 +6771,7 @@ public class Grid<T> extends ResizeComposite implements
eventCell.set(cell, getSectionFromContainer(container));
// Editor can steal focus from Grid and is still handled
- if (handleEditorEvent(event, container)) {
+ if (isEditorEnabled() && handleEditorEvent(event, container)) {
return;
}
@@ -6719,68 +6849,8 @@ public class Grid<T> extends ResizeComposite implements
}
private boolean handleEditorEvent(Event event, RowContainer container) {
- final int type = event.getTypeInt();
- final int key = event.getKeyCode();
- final boolean editorIsActive = editor.getState() != Editor.State.INACTIVE;
-
- double now = Duration.currentTimeMillis();
- int currentX = WidgetUtil.getTouchOrMouseClientX(event);
- int currentY = WidgetUtil.getTouchOrMouseClientY(event);
-
- final boolean validTouchOpenEvent = type == Event.ONTOUCHEND
- && now - lastTouchEventTime < 500
- && lastTouchEventRow == eventCell.getRowIndex()
- && Math.abs(lastTouchEventX - currentX) < 20
- && Math.abs(lastTouchEventY - currentY) < 20;
-
- final boolean openEvent = eventCell.isBody()
- && (type == Event.ONDBLCLICK
- || (type == Event.ONKEYDOWN && key == Editor.KEYCODE_SHOW) || validTouchOpenEvent);
-
- if (type == Event.ONTOUCHSTART) {
- lastTouchEventX = currentX;
- lastTouchEventY = currentY;
- }
-
- if (type == Event.ONTOUCHEND) {
- lastTouchEventTime = now;
- lastTouchEventRow = eventCell.getRowIndex();
- }
-
- // TODO: Move on touch events
- final boolean moveEvent = eventCell.isBody() && type == Event.ONCLICK;
-
- final boolean closeEvent = type == Event.ONKEYDOWN
- && key == Editor.KEYCODE_HIDE;
-
- if (!editorIsActive && editor.isEnabled() && openEvent) {
-
- editor.editRow(eventCell.getRowIndex(),
- eventCell.getColumnIndexDOM());
- fireEvent(new EditorOpenEvent(eventCell));
- event.preventDefault();
-
- return true;
-
- } else if (editorIsActive && !editor.isBuffered() && moveEvent) {
-
- cellFocusHandler.setCellFocus(eventCell);
- editor.editRow(eventCell.getRowIndex(),
- eventCell.getColumnIndexDOM());
- fireEvent(new EditorMoveEvent(eventCell));
-
- return true;
-
- } else if (editorIsActive && closeEvent) {
-
- editor.cancel();
- FocusUtil.setFocus(this, true);
-
- return true;
- }
-
- // Swallow events if editor is open and buffered (modal)
- return editor.isBuffered() && editorIsActive;
+ return getEditor().getEventHandler().handleEvent(
+ new EditorDomEvent<T>(event, getEventCell()));
}
private boolean handleRendererEvent(Event event, RowContainer container) {