Browse Source

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
tags/8.8.1
Martin Vysny 5 years ago
parent
commit
1d52181257

+ 70
- 8
client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java View File

@@ -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);
}
}

@@ -190,6 +199,52 @@ public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> {
return false;
}

/**
* 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
@@ -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)

+ 102
- 0
uitest/src/main/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCells.java View File

@@ -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) {
}
}
}

+ 130
- 0
uitest/src/test/java/com/vaadin/tests/components/grid/GridEditorTabSkipsNonEditableCellsTest.java View File

@@ -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;
}
}

Loading…
Cancel
Save