Looks like I’m running into issues with my inline Grid editing because of this:
- I need to dynamically generate the editor field on each row
- I need to update values in other rows
For (2) I got some help back in October, but it now seems to be incomplete. Possibly because of (1).
Ref: While editing a grid row, how can I update other rows?
In the example I have a Grid with two columns (Start Location and End Location) and two rows. When you edit Start location on one of the rows, it should update End Location in the other row, and vice versa.
When I test it out, it works the 1st time, and then it stops. It looks like the bindings are fcked.
I’m using the lambda version of Column.setEditorComponent.
What I want is to recreate the EditorComponents each time we change line.
I learned earlier today that he lambda is called every time the Grid wants a reference to the editorComponent. To solve that I added caching.
Ref: “Uncaught TypeError: $0 is null” when setting focus on TextField in Grid
But, it still doesn’t work, because Vaadin calls the lambda for any row that I call grid.refreshItem(row) on, not only the edited row (in EditorRenderer.refreshData)
This is a problem, because the lambda is also responsible for setting up the binding, but the binder points to the row that is actually edited, so what should the lambda do?
I’m not sure if I’ve found a bug in vaadin, or if I’m using it wrong.
“Bug” is probably a bit strong, since I’m trying to use a workaround. The real issue is that Vaadin lacks an official way of refreshing rows and cells without closing the editor.
@Route(value="/testGridEdit")
public class TestGridEdit extends VerticalLayout {
public static class Row {
int id;
String startLocation;
String endLocation;
public String getStartLocation() {
return startLocation;
}
public void setStartLocation(String startLocation) {
this.startLocation = startLocation;
}
public String getEndLocation() {
return endLocation;
}
public void setEndLocation(String endLocation) {
this.endLocation = endLocation;
}
public Row(int id) {
this.id = id;
}
@Override
public String toString() {
return "Row " + id;
}
}
public static class EditorFieldCache {
private HashMap<Row, HashMap<Column, TextField>> cache = new HashMap<>();
public TextField get(Row row, Column column) {
var rowCache = cache.get(row);
return rowCache==null ? null : rowCache.get(column);
}
public void put(Row row, Column column, TextField field) {
var rowCache = cache.get(row);
if(rowCache==null) {
rowCache = new HashMap<>();
cache.put(row, rowCache);
}
rowCache.put(column, field);
}
}
/**
* Override Grid, so that I can avoid editor-row being closed when any row is refreshed
*/
public static class MyGrid<T> extends Grid<T> {
@Override
protected void onDataProviderChange() {
// Do not close the editor by default
}
public void refreshItem(T item) {
getDataProvider().refreshItem(item);
// Cancel / close the editor if the item is being edited
if (item.equals(getEditor().getItem())) {
super.onDataProviderChange();
}
}
public void refreshAll() {
getDataProvider().refreshAll();
// Always cancel / close the editor
super.onDataProviderChange();
}
}
public TestGridEdit() {
var grid = new MyGrid<Row>();
var row1 = new Row(1);
var row2 = new Row(2);
var rows = List.of(row1, row2);
var listDataProvider = new ListDataProvider<Row>(rows);
var editorFieldCache = new EditorFieldCache();
Binder<Row> binder = new Binder(Row.class);
Editor<Row> editor = grid.getEditor();
editor.setBinder(binder);
// Start Location
var startLocationColumn = grid.addColumn(row -> row.getStartLocation()).setHeader("Start Location");
startLocationColumn.setEditorComponent(row -> {
var cached = editorFieldCache.get(row, startLocationColumn);
if(cached==null) {
System.out.println("Create startLocationColumn editor");
cached = new TextField();
binder.forField(cached).bind(Row::getStartLocation, Row::setStartLocation);
editorFieldCache.put(row, startLocationColumn, cached);
}
return cached;
});
// End Location
var endLocationColumn = grid.addColumn(row -> row.getEndLocation()).setHeader("End Location");
endLocationColumn.setEditorComponent(row -> {
var cached = editorFieldCache.get(row, endLocationColumn);
if(cached==null) {
System.out.println("Create endLocationColumn editor");
cached = new TextField();
binder.forField(cached).bind(Row::getEndLocation, Row::setEndLocation);
editorFieldCache.put(row, endLocationColumn, cached);
}
return cached;
});
//
grid.setItems(listDataProvider);
add(grid);
grid.getEditor().editItem(row1);
binder.addValueChangeListener(event -> {
if(binder.getBean()==row1) {
row2.setStartLocation(row1.getEndLocation());
row2.setEndLocation(row1.getStartLocation());
grid.refreshItem(row2);
}
else if(binder.getBean()==row2) {
row1.setStartLocation(row2.getEndLocation());
row1.setEndLocation(row2.getStartLocation());
grid.refreshItem(row1);
}
});
grid.addItemDoubleClickListener(e -> {
if(e.getItem()!=editor.getItem()) {
editor.editItem(e.getItem());
Component editorComponent = e.getColumn().getEditorComponent();
if (editorComponent instanceof Focusable) {
((Focusable) editorComponent).focus();
}
}
});
}
}