Stephane Grenier Stephane Grenier - 1 month ago 9
Java Question

Is there a way to set a validation and edit a long value in an editable Vaadin 8 Grid

I have a Vaadin 8 Grid where I would like to set one column as editable. For this I have where

Food.calories
is a long (yes in this case it could be an int but keep in mind this is an example and my specific use case requires a long):

Binder<Food> binder = foodGrid.getEditor().getBinder();
TextField caloriesTextField = new TextField();
binder.forField(caloriesTextField)
.withValidator(CustomCaloryValidator::isValidValue, "Must be valid a positive amount")
.withConverter(new StringToCaloriesConverter("Must be an integer"))
.bind(Food::getCalories, Food::setCalories);

// This line fails with the error because the types do not match.
foodGrid.addColumn(Food::getCalories, new NumberRenderer(myNumberFormat))
.setEditorComponent(new TextField(), Food::setCalories);


Unfortunately this doesn't work and has the following error:

Inferred type 'C' for type parameter 'C' is not within its bound; should implement 'com.vaadin.data.HasValue'

I looked everywhere I could and couldn't find any example of anything beyond simple edits. The demo sampler did have a more complex example using a slider but I couldn't figure out how to extrapolate from that example...

I understand the error, it's trying to map a long to a String. However I can't find a way to add a converter to the addColumn to make it work...

Answer Source

Firstly the main issue was that the Binder did not specify the generic type, it needed to be:

Binder<Food> binder = foodGrid.getEditor().getBinder();

And NOT:

Binder binder = foodGrid.getEditor().getBinder();

That being said there were several other gotchas. First when you do a forField() you need to keep track of that binding to be able to set it later with the column. This wasn't clear at all for me. Specifically you need to:

Binder.Binding<Food, Long> caloriesBinder = binder.forField(caloriesTextField)
        .withValidator(CustomCaloryValidator::isValidValue, "Must be valid a positive amount")
        .withConverter(new StringToCaloriesConverter("Must be an integer"))
        .bind(Food::getCalories, Food::setCalories);

I'm not 100% sure on the caloriesBinder because my code is different and this is an example, but you need that binding. You then take that binding and do:

foodGrid.getColumn("calories").setEditorBinding(caloriesBinding);

This allows the correct editor to work. This is in the documentation but the example is very simple so I missed that.

The next step which is extremely important depending on what you're displaying, is to add a renderer otherwise you can run into some weird issues. For example if you're using long to store a currency then you need to convert it to display a currency amount. Similarly if you're using a date then you probably also want to format it. You then need to add a renderer. The only way I could find how to do it without compilation errors (mismatched types) was:

((Grid.Column<Food, Long>)foodGrid.getColumn("calories")).setRenderer(new CaloriesRenderer());

Just for completeness you then need to enable the editor with:

foodGrid.getEditor().setEnabled(true);

Lastly, if the table is part of a bigger bean then you need to call foodGrid.setItems(), you cannot just rely on binder.readBean() because it cannot accept a list. So for example if instead of food the bean was a meal which consisted of a number of ingredients, then you could not do binder.readBean(meal) nor could you do binder.readBean(meal.getIngredients) even though you can do binder.readBean(meal) for the rest of the form. The only way I could make it work was to do:

binder.readBean(meal);
foodGrid.setItems(meal.getIngredients);