Search code examples
javadata-bindingvaadinvaadin8

Vaadin 8: Binded CheckBoxGroup SelectionListener not fired on deselection


I'm struggeling to detect the deselection of items in a CheckBoxGroup that is binded to a bean. Here is a simplified example:

I have a class Person:

package com.vaadin.test;

import java.util.Date;
import java.util.Set;

public class Person {

    private String name;
    private Set<Tag> tags;

    public Person(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Tag> getTags() {
        return tags;
    }

    public void setTags(Set<Tag> tags) {
        this.tags = tags;
    }
}

An object of the class "Person" can have many Tags. The class Tag looks like this:

package com.vaadin.test;

public class Tag {

    private String name;

    public Tag(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj.getClass().equals(Tag.class)) {
            return ((Tag) obj).getName().equals(this.getName());
        }
        return super.equals(obj);
    }
}

I created a simple UI with a TextField, a CheckBoxGroup and a Binder:

import com.vaadin.ui.CheckBoxGroup;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Theme("valo")
public class TestUI
        extends UI {

    private static final long serialVersionUID = 1L;

    @Override
    protected void init(VaadinRequest request) {

        Person person = new Person("Marcus");
        person.setTags(new HashSet<Tag>() {{ add(new Tag("Foo")); add(new Tag("Bar")); }});

        Binder<Person> binder = new Binder<>(Person.class);

        TextField name = new TextField();
        binder.bind(name, Person::getName, Person::setName);

        CheckBoxGroup<Tag> tags = new CheckBoxGroup<>();
        tags.setItems(new HashSet<Tag>() {{ add(new Tag("Foo")); add(new Tag("Bar")); add(new Tag("Test")); }});
        tags.addSelectionListener(e -> {

            Set<Tag> selectedTags = e.getAllSelectedItems();
            Set<Tag> addedTags = e.getAddedSelection();
            Set<Tag> newTags = e.getNewSelection();
            Set<Tag> oldTags = e.getOldSelection();
            Set<Tag> removedTags = e.getRemovedSelection();
        });
        binder.bind(tags, Person::getTags, Person::setTags);

        this.setContent(new VerticalLayout(new Label("Hello Vaadin!"), name, tags));

        binder.setBean(person);
    }
}

The result looks like this:

enter image description here

When I select and/or deselect the CheckBox "Test" the SelectionListener of the CheckBoxGroup gets fired and I can deal with the changed selection. However if I deselect and/or (re)select one of the CheckBoxes "Bar" or "Foo" the SelectionListener is not fired.

How can I deal with the deselction of preselected CheckBoxes in a CheckBoxGroup that is binded to a Bean?

Thanks in advance.


Solution

  • The equals method in your Tag class looks off. You could benefit from a hashCode too.

    After these edits, your selection listener will fire correctly:

    public class Tag {
    
        private String name;
    
        public Tag(String name) {
            super();
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return name;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            Tag tag = (Tag) o;
    
            return name.equals(tag.name);
        }
    
        @Override
        public int hashCode() {
            return name.hashCode();
        }
    
    }