diff options
author | Martin Vysny <martin@vysny.me> | 2019-05-16 08:27:33 +0200 |
---|---|---|
committer | Sun Zhe <31067185+ZheSun88@users.noreply.github.com> | 2019-05-16 09:27:33 +0300 |
commit | 07fe51a15eaac560c58eabc969940660c192c134 (patch) | |
tree | e0bac8f0b8e5fe29b38deb676b1ab4e4174dd07d | |
parent | 6bffdc53c207c177c2cedf1c1432c696bd6c4a7a (diff) | |
download | vaadin-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
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; + } +} |