From c6622ac5cbf4ddbcec35e02f92f74cf46d147e71 Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Wed, 19 Aug 2015 13:11:21 +0300 Subject: 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 --- .../widget/grid/DefaultEditorEventHandler.java | 225 +++++++++++++++++++++ client/src/com/vaadin/client/widgets/Grid.java | 222 +++++++++++++------- 2 files changed, 371 insertions(+), 76 deletions(-) create mode 100644 client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java 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 implements Editor.EventHandler { + + 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 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 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 event) { + if (isOpenEvent(event)) { + final EventCellReference 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 event) { + Event e = event.getDomEvent(); + final EventCellReference 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 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 event) { + if (isCloseEvent(event)) { + event.getEditor().cancel(); + FocusUtil.setFocus(event.getGrid(), true); + return true; + } + return false; + } + + protected void editRow(EditorDomEvent 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 event) { + final Editor 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; @@ -1126,24 +1124,139 @@ public class Grid 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 + * The row type of the grid + */ + public static class GridEvent { + private Event event; + private EventCellReference cell; + + protected GridEvent(Event event, EventCellReference 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 getCell() { + return cell; + } + + /** + * Returns the Grid instance this event originated from. + * + * @return the grid + */ + public Grid getGrid() { + return cell.getGrid(); + } + } + + /** + * A wrapper for native DOM events related to the {@link Editor Grid editor} + * . + * + * @since + * @param + * the row type of the grid + */ + public static class EditorDomEvent extends GridEvent { + + protected EditorDomEvent(Event event, EventCellReference cell) { + super(event, cell); + } + + /** + * Returns the editor of the Grid this event originated from. + * + * @return the related editor instance + */ + public Editor 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 + * the row type of the grid */ - protected static class Editor { - - public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER; - public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE; + public static class Editor { 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 + * the row type of the grid + */ + public interface EventHandler { + /** + * 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 event); + } + protected enum State { INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING } private Grid grid; private EditorHandler handler; + private EventHandler 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 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 handler) { + eventHandler = handler; + } + + /** + * Returns the event handler of this Editor. + * + * @since + * @return the current event handler + */ + public EventHandler getEventHandler() { + return eventHandler; + } } public static abstract class AbstractGridKeyEvent @@ -3739,10 +3873,6 @@ public class Grid 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 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 extends ResizeComposite implements return footer.isVisible(); } - protected Editor getEditor() { + public Editor getEditor() { return editor; } @@ -6641,7 +6771,7 @@ public class Grid 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 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(event, getEventCell())); } private boolean handleRendererEvent(Event event, RowContainer container) { -- cgit v1.2.3