I have a simple example containing a customer who might have several addresses. This is my JPA model:
@Entity
@NamedEntityGraph(name = "addressGraph", attributeNodes = { @NamedAttributeNode("addresses") })
public class Customer {
@Id
@GeneratedValue
private Long id;
@Size(min = 3)
private String firstName;
private String lastName;
@ElementCollection
private List<@NotNull @Valid Address> addresses = new ArrayList<>();
@Embeddable
public class Address {
@Size( min = 3)
private String street;
private String city;
I use a editable grid to maintain the addresses of a customer. I would like to use the binder as I would like to reflect the changes on the UI to the model. This is my appoach:
addressGrid.addComponentColumn((address) -> {
final Binder<Address> addressBinder = new BeanValidationBinder<>(Address.class);
TextField street = new TextField();
addressBinder.bind(street, Address::getStreet, Address::setStreet);
addressBinder.setBean(address);
return street;
}).setHeader("Street");
addressGrid.addComponentColumn((address) -> {
final Binder<Address> addressBinder = new BeanValidationBinder<>(Address.class);
TextField city = new TextField();
addressBinder.bind(city, Address::getCity, Address::setCity);
addressBinder.setBean(address);
return city;
}).setHeader("City");
It seems to work, but I wonder if it’s menaingful to create a binder for each column. Isn’t it sufficient to have a binder for each row? But how can I achieve this?
Using OP’s models, an editable grid for a customers addresses could look like this:
// initialize grid
Grid<Address> grid = new Grid<>(Address.class);
grid.setColumns("street", "city");
grid.setItems(customer.getAddresses());
// prepare editor
Binder<Address> editorBinder = new Binder<>();
Editor<Address> editor = grid.getEditor();
editor.setBinder(editorBinder);
editor.setBuffered(true); // true --> the edited values will only be applied upon clicking save, instead of immediately at value change of editor textfield
// set an editor component for each column that is editable
TextField streetEditField = new TextField();
editorBinder.forField(streetEditField).bind(Address::getStreet, Address::setStreet);
grid.getColumnByKey("street").setEditorComponent(streetEditField);
TextField cityEditField = new TextField();
editorBinder.forField(cityEditField).bind(Address::getCity, Address::setCity);
grid.getColumnByKey("city").setEditorComponent(cityEditField);
// add a column for editor actions (edit, save, cancel)
Grid.Column<Address> editorColumn = grid.addColumn(new ComponentRenderer<>(item -> {
Button editBtn = new Button("Edit", VaadinIcon.EDIT.create());
editBtn.addClickListener(click -> {
editor.editItem(item);
});
return editBtn;
}))
.setKey("edit");
// show save and cancel buttons when in edit mode
Button save = new Button(VaadinIcon.CHECK_CIRCLE.create(), e -> editor.save());
Button cancel = new Button(VaadinIcon.CLOSE_CIRCLE.create(), e -> editor.cancel());
Div buttons = new Div(save, cancel);
editorColumn.setEditorComponent(buttons);
// Add a keypress listener that listens for an escape key up event.
// Note! some browsers return key as Escape and some as Esc
grid.getElement().addEventListener("keyup", event -> editor.cancel())
.setFilter("event.key === 'Escape' || event.key === 'Esc'");
// saving the edits will lead to the address objects of the customer to change values without needing to add a saveListener.
// If you want to save either the customer or the address to the DB at this point, you can add a saveListener to the editor
editor.addSaveListener(saveEvent -> {
// customerService.save(customer);
// addressService.save(saveEvent.getItem());
});