I have some related beans (for the sake of simplicity in the example, let’s talk about
CountryDTO and
CityDTO ). A
CountryDTO have a field “cities”, which points to a
List (files CountryDTO.java and CityDTO.java in the Gist).
Of course, I would like to have a form where I can choose some cities for the country. So I though on using a multiselect
OptionGroup to acomplish that. But, smart as I am, I chose to use a
BeanFieldGroup to arrange all this stuff together (files AddCountryUI.java and EditCountryUI.java in the Gist).
After some struggle, I saw that I needed a converter for the
OptionGroup , to convert the values between IDs and DTO, so I did (AbstractMultiselectToDTOConverter.java file in the Gist).
But now I’m stuck with the following:
In the case of saving cities in the country, I have to do it manually calling
setCities() of the
CountryDTO right after the
commit() of the
BeanFieldGroup (see comments in AddCountryUI.java file in the Gist).
In the case of editing an existing country, and that country having cities, the
OptionGroup does not automatically checks the cities for the given
CountryDTO . I have to do it manually because the
setValue() of the
OptionGroup never gets called. (see comments in EditCountryUI.java).
Thanks a lot for your time, and your efforts on making Vaading an outstanding framework!
Caused by: com.vaadin.data.util.converter.Converter$ConversionException: Unable to convert value of type org.hibernate.collection.internal.PersistentBag to presentation type interface java.util.Set. No converter is set and the types are not compatible.
at com.vaadin.data.util.converter.ConverterUtil.convertFromModel(ConverterUtil.java:116)
at com.vaadin.ui.AbstractField.convertFromModel(AbstractField.java:708)
at com.vaadin.ui.AbstractField.convertFromModel(AbstractField.java:693)
at com.vaadin.ui.AbstractField.setPropertyDataSource(AbstractField.java:629)
... 49 more
It’s weird, because my field DOES have a
Converter , but the exception says that it doesn’t.
This is my custom
buildAndBindOptionGroup() for the OptionGroups. This method is defined in my
CustomBeanFieldGroup class, and I use it to build the OptionGroups instead of the classic
buildAndBind()
public OptionGroup buildAndBindOptionGroup(String caption, Object propertyId, Container container, String captionPropertyId, Boolean multiselect) throws BindException {
OptionGroup field;
if (multiselect) {
field = proxy.buildMultiselectOptionGroup(propertyId, container, captionPropertyId);
} else {
field = proxy.buildOptionGroup(propertyId, container, captionPropertyId);
}
field.setCaption(caption);
bind(field, propertyId);
return field;
}
Notice that
proxy is just a helper to create the fields. Nothing fancy:
public OptionGroup buildOptionGroup(Object fieldId, Container container, String captionPropertyId) {
OptionGroup field = new OptionGroup();
if (container.size() > 0) {
field.setContainerDataSource(container);
field.setConverter(new AbstractSelectToDTOConverter(field));
field.setItemCaptionPropertyId(captionPropertyId);
} else {
field.setVisible(false);
}
return field;
}
public OptionGroup buildMultiselectOptionGroup(Object fieldId, Container container, String captionPropertyId) {
OptionGroup field = buildOptionGroup(fieldId, container, captionPropertyId);
field.setMultiSelect(true);
field.setConverter(new AbstractMultiSelectToDTOConverter(field));
return field;
}
AppUI.java => Initial screen. Sample data is created, OptionGroup is built and attached to the UI
CountryDTO.java and CityDTO.java => Beans. One country has many cities.
BaseDTO.java => Abstract class for the DTOs. All of them extends this one.
CustomBeanFieldGroup.java => Has a custom buildAndBindOptionGroup() to create and bind the OptionGroup with our needs
FieldGroupProxy.java => Helper to actually build form fields
AbstractMultiSelectToDTOConverter.java => Converter for the OptionGroup: ids<=>models
I’m really bit shamed such an easy task is so tricky with vanilla Vaadin. Thats why, when I saw your message, I just had to make one helper I had planned a long ago to Viritin. Check the brand new CheckBoxGroup class from it. It, and its cousin, MultiSelectTable, are “multiselect fields” that really modify the edited collection. I have been using the MultiSelectTable in couple of this kind of cases and I believe this is probably exactly what you want as well.
Your project has Maddon as dependency so upgrade that
to Viritin (new name since couple of releases ago). In the tests there is
one example that edits List groups field. It uses AbstractForm, but those fields can be used with raw BeanFieldGroup as well.
@Johannes: it works like a charm! Thank you so much. I tried to change the List to a Set in my initial version of the Converter, but I was doing it for all the Lists, not just for the itemIds and, of course, it was not working, hehe Your solution works perfectly. Thank you.
@Matti, it looks fantastic! I saw Viritin before, but never used it. I don’t know why I have the dependency to Maddon, though… Maybe because of copy&paste, I guess… Hehehe. I’ll give it a try to see if it fits our needs for the project. But, at a first glance, it seems that it fits all of the, for sure! Thank you!
@Johannes : I needed to create a crud for an entity that had a many to many relationship to another table and was using a twin column select. I was using Vaadin JPA. So i ended up editing the code you posted to use a Vaadin Container. Thank you for posting that code.
[code]
public class AbstractMultiSelectToEntityConverter implements Converter<Set, List> {
private static final long serialVersionUID = -4042387995585589967L;
private final static Logger logger = LogManager.getLogger(AbstractMultiSelectToEntityConverter.class);
private JPAContainer<T> jpaContainer;
private String excludedFields;
private final List<T> MODEL_TYPE_INSTANCE = new ArrayList<T>();
private final Set<Object> PRESENTER_TYPE_INSTANCE = new HashSet<Object>();
@SuppressWarnings("unchecked")
public AbstractMultiSelectToEntityConverter(AbstractSelect field, String excludedFields) {
this.jpaContainer = (JPAContainer<T>) field.getContainerDataSource();
this.excludedFields = excludedFields;
}
@Override
public List<T> convertToModel(Set<Object> itemIds,
Class<? extends List<T>> targetType, Locale locale)
throws com.vaadin.data.util.converter.Converter.ConversionException {
List<T> models = new ArrayList<T>();
if (jpaContainer.size() > 0) {
for (Object itemId : itemIds) {
if (jpaContainer.containsId(itemId)) {
models.add(jpaContainer.getItem(itemId).getEntity());
}
}
}
return models;
}
@Override
public Set<Object> convertToPresentation(List<T> models,
Class<? extends Set<Object>> targetType, Locale locale)
throws com.vaadin.data.util.converter.Converter.ConversionException {
Set<Object> ids = new HashSet<Object>();
for (Object itemId : jpaContainer.getItemIds()) {
T bean = jpaContainer.getItem(itemId).getEntity();
if(models!=null){
for (Object model : models) {
if (compare(model,bean)) {
ids.add(itemId);
}
}
}
}
return ids;
}
@Override
public Class<List<T>> getModelType() {
return (Class<List<T>>) MODEL_TYPE_INSTANCE.getClass();
}
@Override
public Class<Set<Object>> getPresentationType() {
return (Class<Set<Object>>) PRESENTER_TYPE_INSTANCE.getClass();
}
public boolean compare(Object a, Object b) {
return EqualsBuilder.reflectionEquals(a, b, excludedFields);
}
}
[/code]Called from the UI as such
protected TwinColSelect servicesSelect;
....
services = JPAContainerFactory.make(Service.class,"itmspersistence");
....
servicesSelect.setContainerDataSource(services);
servicesSelect.setItemCaptionPropertyId("serviceName");
//Fields in the entity bean that I didn't want to equalsbuilder to use
String excludedFields = {"agencyRates","invoiceDetails","category","syndicateDiscounts","syndicateDiscountServices","syndicateRates"};
servicesSelect.setConverter(new AbstractMultiSelectToEntityConverter(servicesSelect,excludedFields));