Search code examples
javavaadinvaadin-gridvaadin8

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...


Solution

  • 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);