summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Vysny <martin@vysny.me>2019-05-16 08:27:33 +0200
committerSun Zhe <31067185+ZheSun88@users.noreply.github.com>2019-05-16 09:27:33 +0300
commit07fe51a15eaac560c58eabc969940660c192c134 (patch)
treee0bac8f0b8e5fe29b38deb676b1ab4e4174dd07d
parent6bffdc53c207c177c2cedf1c1432c696bd6c4a7a (diff)
downloadvaadin-framework-07fe51a15eaac560c58eabc969940660c192c134.tar.gz
vaadin-framework-07fe51a15eaac560c58eabc969940660c192c134.zip
Grid editor: TAB now skips non-editable columns (#11573)
* Grid editor: TAB now skips non-editable columns Pressing TAB would shift the focus to non-editable cells when the Grid was in edit mode. This patch makes DefaultEditorEventHandler to skip such columns. Closes #10970 * Add tests
-rw-r--r--client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java78
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCells.java102
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCellsTest.java130
3 files changed, 302 insertions, 8 deletions
diff --git a/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
index ad99719aa8..426d7c3016 100644
--- a/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
+++ b/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
@@ -23,9 +23,12 @@ import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.FocusUtil;
+import com.vaadin.client.widgets.Grid;
import com.vaadin.client.widgets.Grid.Editor;
import com.vaadin.client.widgets.Grid.EditorDomEvent;
+import java.util.List;
+
/**
* The default handler for Grid editor events. Offers several overridable
* protected methods for easier customization.
@@ -164,20 +167,26 @@ public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> {
int columnCount = event.getGrid().getVisibleColumns().size();
- int colIndex = event.getFocusedColumnIndex() + colDelta;
+ int colIndex = colDelta > 0
+ ? findNextEditableColumnIndex(event.getGrid(),
+ event.getFocusedColumnIndex() + colDelta)
+ : findPrevEditableColumnIndex(event.getGrid(),
+ event.getFocusedColumnIndex() + colDelta);
int rowIndex = event.getRowIndex();
// Handle row change with horizontal move when column goes out
// of range.
- if (rowDelta == 0) {
- if (colIndex >= columnCount
+ if (rowDelta == 0 && colIndex < 0) {
+ if (colDelta > 0
&& rowIndex < event.getGrid().getDataSource().size()
- 1) {
rowDelta = 1;
- colIndex = 0;
- } else if (colIndex < 0 && rowIndex > 0) {
+ colIndex = findNextEditableColumnIndex(event.getGrid(),
+ 0);
+ } else if (colDelta < 0 && rowIndex > 0) {
rowDelta = -1;
- colIndex = columnCount - 1;
+ colIndex = findPrevEditableColumnIndex(event.getGrid(),
+ columnCount - 1);
}
}
@@ -191,6 +200,52 @@ public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> {
}
/**
+ * Finds index of the first editable column, starting at the specified
+ * index.
+ *
+ * @param grid
+ * the current grid, not null.
+ * @param startingWith
+ * start with this column. Index into the
+ * {@link Grid#getVisibleColumns()}.
+ * @return the index of the nearest visible column; may return the
+ * <code>startingWith</code> itself. Returns -1 if there is no such
+ * column.
+ */
+ private int findNextEditableColumnIndex(Grid<T> grid, int startingWith) {
+ final List<Grid.Column<?, T>> columns = grid.getVisibleColumns();
+ for (int i = startingWith; i < columns.size(); i++) {
+ if (columns.get(i).isEditable()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Finds index of the last editable column, searching backwards starting at
+ * the specified index.
+ *
+ * @param grid
+ * the current grid, not null.
+ * @param startingWith
+ * start with this column. Index into the
+ * {@link Grid#getVisibleColumns()}.
+ * @return the index of the nearest visible column; may return the
+ * <code>startingWith</code> itself. Returns -1 if there is no such
+ * column.
+ */
+ private int findPrevEditableColumnIndex(Grid<T> grid, int startingWith) {
+ final List<Grid.Column<?, T>> columns = grid.getVisibleColumns();
+ for (int i = startingWith; i >= 0; i--) {
+ if (columns.get(i).isEditable()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
* Moves the editor to another column if the received event is a move event.
* By default the editor is moved on a keydown event with keycode
* {@link #KEYCODE_MOVE_HORIZONTAL}. This moves the editor left or right if
@@ -218,8 +273,15 @@ public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> {
// Prevent tab out of Grid Editor
event.getDomEvent().preventDefault();
- editRow(event, event.getRowIndex(), event.getFocusedColumnIndex()
- + (e.getShiftKey() ? -1 : +1));
+ final int newColIndex = e.getShiftKey()
+ ? findPrevEditableColumnIndex(event.getGrid(),
+ event.getFocusedColumnIndex() - 1)
+ : findNextEditableColumnIndex(event.getGrid(),
+ event.getFocusedColumnIndex() + 1);
+
+ if (newColIndex >= 0) {
+ editRow(event, event.getRowIndex(), newColIndex);
+ }
return true;
} else if (e.getType().equals(BrowserEvents.KEYDOWN)
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCells.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCells.java
new file mode 100644
index 0000000000..cf8c673c09
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCells.java
@@ -0,0 +1,102 @@
+package com.vaadin.tests.components.grid;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.provider.ListDataProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.TextField;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows a Grid with the only editable columns being the column 1 and 3. That
+ * will allow us to test that Tab/Shift+Tab skips cells that are not editable.
+ */
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class GridEditorTabSkipsNonEditableCells extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ final List<TestBean> items = new ArrayList<>();
+ Grid<TestBean> grid = new Grid<TestBean>(TestBean.class);
+ for (int i = 0; i < 10; i++) {
+ items.add(new TestBean(i));
+ }
+ grid.setDataProvider(new ListDataProvider<>(items));
+ grid.setWidth("100%");
+ grid.setHeight("400px");
+ grid.getEditor().setEnabled(true);
+ grid.getColumn("col1").setEditorComponent(new TextField());
+ grid.getColumn("col3").setEditorComponent(new TextField());
+ grid.setColumnOrder("col0", "col1", "col2", "col3", "col4");
+
+ getLayout().addComponent(
+ new Button("Set Editor Buffered Mode On", event -> {
+ grid.getEditor().cancel();
+ grid.getEditor().setBuffered(true);
+ }));
+ getLayout().addComponent(
+ new Button("Set Editor Buffered Mode Off", event -> {
+ grid.getEditor().cancel();
+ grid.getEditor().setBuffered(false);
+ }));
+
+ getLayout().addComponent(grid);
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 11573;
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "Pressing TAB doesn't shift the focus to non-editable cells when the Grid is in edit mode.";
+ }
+
+ public class TestBean {
+ private final int row;
+
+ public TestBean(int row) {
+ this.row = row;
+ }
+
+ public String getCol0() {
+ return "col0_" + row;
+ }
+
+ public String getCol1() {
+ return "col1_" + row;
+ }
+
+ public String getCol2() {
+ return "col2_" + row;
+ }
+
+ public String getCol3() {
+ return "col3_" + row;
+ }
+
+ public String getCol4() {
+ return "col4_" + row;
+ }
+
+ public void setCol0(String value) {
+ }
+
+ public void setCol1(String value) {
+ }
+
+ public void setCol2(String value) {
+ }
+
+ public void setCol3(String value) {
+ }
+
+ public void setCol4(String value) {
+ }
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCellsTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCellsTest.java
new file mode 100644
index 0000000000..9092f0a22f
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCellsTest.java
@@ -0,0 +1,130 @@
+package com.vaadin.tests.components.grid;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.elements.TextFieldElement;
+import com.vaadin.testbench.parallel.TestCategory;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Makes sure that pressing Tab when the Grid is in edit mode will make focus
+ * skip cells that are not editable.
+ */
+@TestCategory("grid")
+public class GridEditorTabSkipsNonEditableCellsTest extends MultiBrowserTest {
+ /**
+ * The grid with 5 columns. First, third and fifth columns are not editable.
+ */
+ private GridElement grid;
+
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+ openTestURL();
+ grid = $(GridElement.class).first();
+ }
+
+ @Test
+ public void tabSkipsOverNotEditableFieldBuffered() {
+ setBuffered(true);
+ openEditor(0, 1);
+ pressTab();
+ Assert.assertEquals("col3_0", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void tabDoesNothingIfAlreadyOnLastEditableFieldBuffered() {
+ setBuffered(true);
+ openEditor(0, 3);
+ pressTab();
+ Assert.assertEquals("col3_0", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void tabSkipsOverNotEditableFieldUnbuffered() {
+ setBuffered(false);
+ openEditor(0, 1);
+ pressTab();
+ Assert.assertEquals("col3_0", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void tabMovesToNextRowFirstEditableFieldUnbuffered() {
+ setBuffered(false);
+ openEditor(0, 3);
+ pressTab();
+ Assert.assertEquals("col1_1", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void shiftTabSkipsOverNotEditableFieldBuffered() {
+ setBuffered(true);
+ openEditor(0, 3);
+ pressShiftTab();
+ Assert.assertEquals("col1_0", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void shiftTabDoesNothingIfAlreadyOnLastEditableFieldBuffered() {
+ setBuffered(true);
+ openEditor(0, 1);
+ pressShiftTab();
+ Assert.assertEquals("col1_0", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void shiftTabSkipsOverNotEditableFieldUnbuffered() {
+ setBuffered(false);
+ openEditor(0, 3);
+ pressShiftTab();
+ Assert.assertEquals("col1_0", getFocusedEditorCellContents());
+ }
+
+ @Test
+ public void shiftTabMovesToNextRowFirstEditableFieldUnbuffered() {
+ setBuffered(false);
+ openEditor(1, 1);
+ pressShiftTab();
+ Assert.assertEquals("col3_0", getFocusedEditorCellContents());
+ }
+
+ private void openEditor(int rowIndex, int colIndex) {
+ grid.getCell(rowIndex, colIndex).doubleClick();
+ }
+
+ private void pressTab() {
+ new Actions(getDriver()).sendKeys(Keys.TAB).perform();
+ }
+
+ private void pressShiftTab() {
+ new Actions(getDriver()).keyDown(Keys.SHIFT).sendKeys(Keys.TAB)
+ .keyUp(Keys.SHIFT).perform();
+ }
+
+ private void setBuffered(boolean buffered) {
+ $(ButtonElement.class).caption(buffered ? "Set Editor Buffered Mode On"
+ : "Set Editor Buffered Mode Off").first().click();
+ }
+
+ private String getFocusedEditorCellContents() {
+ final GridElement.GridEditorElement editor = grid.getEditor();
+ final WebElement focusedElement = getFocusedElement();
+ for (int i = 0; i < 5; i++) {
+ if (editor.isEditable(i)
+ && editor.getField(i).equals(focusedElement)) {
+ return (editor.getField(i).wrap(TextFieldElement.class))
+ .getValue();
+ }
+ }
+ fail("Currently focused element is not a cell editor: "
+ + focusedElement);
+ return null;
+ }
+}