* 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 teststags/8.8.1
@@ -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) |
@@ -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) { | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |