Search code examples
vaadinvaadin8

Vaadin 8 - ComboBox value remains after the bean has changed


Edit: I found the solution myself, there is no need to read all this if you are simply trying to help and not having a similar problem yourself ;)


First of all, I have asked this question in the Vaadin Forum, but I would like to ask here too in case it is not a bug and I am just doing something wrong.

A ComboBox (ComboBox<Bar>) of mine that relies on the value of another ComboBox (ComboBox<Foo>) does not update its value as expected when the ComboBox<Foo> value changes.

At first, both comboBoxes are empty.
When I select an option for ComboBox<Foo> the options of the other ComboBox are being manually rebuilt.
If I then select an option for ComboBox<Bar> it's getting selected, all is fine until now.
But when I now select another option for ComboBox<Foo>, let's say none/null, the Options of ComboBox<Bar> are being rebuilt, but its value remains! (it should be null)

Here is my (simplified) code to eplain/reproduce my problem. I know it's a bit much but I really want to be clear what my problem is and what I did:

public class FooBar {
    private Foo foo = null;

    public FooBar(){
    }

    // getters and setters
    ...
}

public class Foo {
    private Collection<Bar> bars;
    private Bar selectedBar = null;

    public Foo(Collection<Bar> bars){
        this.bars = bars;
    }

    // getters and setters
    ...
}

public class Bar {
    private String name;

    public Bar(String name){
        this.name = name;
    }

    // getters and setters
    ...
}

In my View, I have a Binder and there is a ComboBox  and also a ComboBox. They are being set up like this:

Binder<FooBar> binder = new Binder<>(new FooBar()); 
binder.addValueChangeListener(event -> {
    binder.setBean(binder.getBean()); // forces bound components to get the value again / refresh
    refillBarOptions(); 
})

ComboBox<Foo> cbFoo = new ComboBox<>();
cbFoo.setCaption("foo");

Collection<Foo> allFoos = fooService.findAll(); // the foos do NOT have a selectedBar yet
ListDataProvider<Foo> ldpFoo = new ListDataProvider<Foo>(allFoos);
cbFoo.setDataProvider(ldpFoo);

binder.forField(cbFoo)
    .bind(
        fooBar -> fooBar.getFoo(), //valueprovider
        (fooBar, foo) -> fooBar.setFoo(foo) //setter
    );
// so initially this combobox' value is empty because the FooBar.getFoo() returns null. thats OK!


ComboBox<Bar> cbBar = new ComboBox<>();
cbBar.setCaption("bar");

cbBar.setDataProvider(new ListDataProvider<Bar>(new HashSet<>())); // no items initially because the Foo-ComboBox has no value yet
cbBar.setItemCaptionGenerator(Bar::getName);

binder.forField(cbBar)
    .bind(
        fooBar -> fooBar.getFoo() == null ? null : fooBar.getFoo().getSelectedBar(), //valueprovider
        (fooBar, bar) -> {
            if(fooBar.getFoo() != null){
                fooBar.getFoo().setSelectedBar(bar);
            }
        }
    );
cbBar.setEnabled(false) // disable because no items initially    


/**
 * gets called when binder value has changed. 
 * This way I circumvent the fact that a normal valueChangeListener of a component is fired before the binder-bean is updated
 */
private void refillBarOptions(){
    Foo selectedFoo = binder.getBean().getFoo();
    if(selectedFoo != null){
        cbBar.setDataProvider<>(new ListDataProvider<>(selectedFoo.getBars())); //cbBar is actually not a local variable so it can be accessed here
        cbBar.setEnabled(true);
    } else {
        // there is no foo selected, therefore no bars should be allowed to be selected
        cbBar.setDataProvider(new ListDataProvider<Bar>(new HashSet<>()));
        cbBar.setEnabled(false);
    }
}

Should I open a new Issue about this or am I doing something wrong? Any help would be appreciated. Thanks


Solution

  • I could not see the wood for the trees here.. The problem was solved by calling binder.setBean(binder.getBean()); again at the end of refillBarOptions()

    private void refillBarOptions(){
        Foo selectedFoo = binder.getBean().getFoo();
        if(selectedFoo != null){
            cbBar.setDataProvider<>(new ListDataProvider<>(selectedFoo.getBars())); //cbBar is actually not a local variable so it can be accessed here
            cbBar.setEnabled(true);
        } else {
            // there is no foo selected, therefore no bars should be allowed to be selected
            cbBar.setDataProvider(new ListDataProvider<Bar>(new HashSet<>()));
            cbBar.setEnabled(false);
        }
        binder.setBean(binder.getBean());
    }