How to bind value from combobox created from one entity to another

Viewed 69

I am trying to binding data from one entity to another.

First entity Car:

public class Car {

@Id
private Long carId;
private String carName;
private Long brandId;
.
.
.

}

refers to the second entity Brand:

public class Brand {

@Id
private Long brandId;
private String brandName;
.
.
.

}

In the view class CarDetailView I am using combobox with brand names, but binder is seted to Car entity

public class CarDetailView extends Div implements BeforeEnterObserver {

   private ComboBox<String> brandNames;
   private BeanValidationBinder<Car> binder;

   private void createBasicLayout(SplitLayout splitLayout) {
        brandNames = new ComboBox<>("Brand name");
        brandNames.setItems(brandRepository.findAllBrandNames()); //function returns all brandNames from database (e.g. Volkswagen, Volvo, Audi, ...)
   }

}

So it returns the correct result into the combobox (combobox contains all brand names), but when I choose value from combobox, it can't be binded into the Car entity.

The problem is, that I need to get the brandId from text in combobox and set it into the Car entity with binder.

I tried to use pairs:

private ComboBox<Pair<Long, String>> brandNames;

where I have seted the brandId (left side) and brandName (right side), but it still can't bind it into the Car, because it throw an exception:

Caused by: java.lang.IllegalStateException: Property type 'java.lang.Long' doesn't match the field type 'com.vaadin.flow.internal.Pair<java.lang.Long, java.lang.String>'. Binding should be configured manually using converter.

Does anyone have an idea how I could do this?

Working result:

I used the Simon Martinelli answer:

public class CarDetailView extends Div implements BeforeEnterObserver {

   private ComboBox<Brand> brand;
   private BeanValidationBinder<Car> binder;

   private void createBasicLayout(SplitLayout splitLayout) {
        binder = new BeanValidationBinder<>(Car.class);
        binder.bindInstanceFields(this);

      
        brand = new ComboBox<>("Brand name");
        brand.setItems(brandRepository.findAllBrands()); //function returns all Brands

        Map<Long, Brand> brandMap = new HashMap<>()
        {
           {
            put(2000000L, new Brand(2000000L, "Audi"));
            put(2000001L, new Brand(2000001L, "Volvo"));
           }
        };

        binder.forField(brand) //Line 251
        .withConverter(new Converter<Brand, Long>() { 
            @Override  superclass
            public Result<Long> convertToModel(Brand value, ValueContext context) {
                return Result.ok(value == null ? null : value.getBrandId());
            }

            @Override superclass
            public Brand convertToPresentation(Long value, ValueContext context) {
                return brandMap.get(value);
            }
        }).bind(Car::getBrandId, Car::setBrandId);
        
   }

   brand.setItemLabelGenerator(brand-> brand.getBrandName());

}
1 Answers

You can use the Brand in the ComboBox instead of a String.

Then you can implement setItemLabelGenerator to display what you need.

See the documentation: https://vaadin.com/docs/latest/components/combo-box/#custom-item-presentation

Edit:

You have to use a converter to bind the id of the Brand

binder.forField(brand)
        .withConverter(new Converter<Brand, Long>() {
            @Override
            public Result<Long> convertToModel(Brand value, ValueContext context) {
                return Result.ok(value == null ? null : value.getId());
            }

            @Override
            public Brand convertToPresentation(Long value, ValueContext context) {
                return brandMap.get(value);
            }
        })
        .bind(Car::getBrandId, Car::setBrandId);

The brandMap contains the id of the brand with the corresponding brand.

Related