1
public class LocationBasedRole extends AbstractEntity{
    @ManyToMany(fetch=FetchType.LAZY)
    private Set<Role> roles=new HashSet<Role>();

    @ManyToMany(fetch=FetchType.LAZY)
    private Set<Location> locations=new HashSet<Location>();
}


public class Role extends AbstractEntity{
    private String name;
}


public class Location extends AbstractEntity{
    private String location;
}

I have an entity named locationBasedRole which has 2 properties named roles and locations. Both roles and locations have a @ManyToMany relation with locationBasedRole.
Now I want to have one property of each in a Vaadin Table.
It should be something like this,

public class UserForm extends OgsAbstractForm<User>{

    MTable<LocationBasedRole> locationBasedRoleTable = new MTable<LocationBasedRole>().withHeight("100%").withWidth("100%");


    @Override
    protected Component createContent() {

        Set<LocationBasedRole> lbRoles=new HashSet<LocationBasedRole>();
        roles.addAll(locationBasedRoleFasade.findAll());

        BeanItemContainer<LocationBasedRole> bean=new BeanItemContainer<LocationBasedRole>(LocationBasedRole.class);

        //It returns an error on the next both lines and I know the reason, but don't know how to solve it.
        // If it was no ManyToMany relation and the properties weren't a collection, it would work
        bean.addNestedContainerProperty("roles.name");
        bean.addNestedContainerProperty("locations.location");

        bean.removeContainerProperty("persistent");
        bean.removeContainerProperty("id");

        bean.addAll(lbRoles);

        locationBasedRoleTable.setContainerDataSource(bean);

        return new VerticalLayout(locationBasedRoleTable);
    }
}

When I remove the properties from the NestedContainerProperties it shows me at least something in the table.

bean.addNestedContainerProperty("roles");
bean.addNestedContainerProperty("locations");

I could use any help!

Thanks in advance!

Reza P.
  • 306
  • 2
  • 18

1 Answers1

0

So if I understand your question right, you want to have the Collections of your BeanItemContainer-Entity displayed in one column each?

I see two possibilities for that.

Option 1 - use a wrapper class for your Sets and use addNestedContainerBean

One possibility would be to not use Sets inside your LocationBasedRole but to use a wrapper class that extends HashSet. Then you could use the addNestedContainerBean method.

I created a small example with the BeanItemContainer-Entity Team

public class Team {

    private String teamName;
    private Members teamMembers;

    public String getTeamName() {
        return teamName;
    }

    public void setTeamName(String teamName) {
        this.teamName = teamName;
    }

    public Members getTeamMembers() {
        return teamMembers;
    }

    public void setTeamMembers(Members teamMembers) {
        this.teamMembers = teamMembers;
    }
}

Which consists of a name and teamMembers. The latter is of type Members:

public class Members extends HashSet<TeamMember> {

    public String getMembers() {
        return this.stream()
                .map(member -> member.getFirstName() + " " + member.getLastName())
                .collect(Collectors.joining(","));
    }

}

Which is a simple wrapper for the Set that contains instances of TeamMember:

public class TeamMember {

    private String firstName;
    private String lastName;
    private Integer age;

    // getters and setters
}

As you can see in the Members class, there is a method getMembers which returns a String, containing a comma separated list of the team members names.

If we now use addNestedContainerBean("teamMembers") Vaadin tries to display all properties contained in the class Members. Vaadin will think getMembers is a getter for a String property called members and so generate a column for it.

Vaadin will also display a column "empty" because it will find the isEmpty method of Set and think empty is a property to display in a column. So we tell Vaadin to remove that column.

The final code of my example looks like:

protected Component createContent() {

        Set<Team> teams=new HashSet<>();
        for (int teamCounter = 0; teamCounter < 5; teamCounter++) {
            Team team = createTeam();
            addMembersToTeam(5, team);
            teams.add(team);
        }

        BeanItemContainer<Team> bean=new BeanItemContainer<>(Team.class);

        bean.addNestedContainerBean("teamMembers");
        bean.removeContainerProperty("teamMembers.empty");

        bean.addAll(teams);

        teamTable.setContainerDataSource(bean);

        return new VerticalLayout(teamTable);
    }

The result looks like:

Option 1

Option 2 - create fake getters and use addNestedContainerProperty

The only thing you have to do for this is extend your BeanItemContainer-Entity (LocationBasedRole) and create a fake getter for each Set you want to be displayed in a column. In your example those two fake getters could be public String getTheRoles() and public String getTheLocations(). Then you can use bean.addNestedContainerProperty("theRoles") and bean.addNestedContainerProperty("theLocations").

In my example my TeamMember class (the counterpart to your Role / Location classes) would still look like in the option above:

 public class TeamMember {

    private String firstName;
    private String lastName;
    private Integer age;

    // getters and setters
}

And my Team class (your LocationBasedRole) would look like:

public class Team {

    private String teamName;
    private Set<TeamMember> teamMembers;

    public String getTeamName() {
        return teamName;
    }

    public void setTeamName(String teamName) {
        this.teamName = teamName;
    }

    public Set<TeamMember> getTeamMembers() {
        return teamMembers;
    }

    public void setTeamMembers(Set<TeamMember> teamMembers) {
        this.teamMembers = teamMembers;
    }

    public String getMembers() {
        if (teamMembers != null) {
            return teamMembers.stream()
                    .map(member -> member.getFirstName() + " " + member.getLastName())
                    .collect(Collectors.joining(","));
        } else {
            return "No members";
        }
    }
}

Now you can tell vaadin to add the (not existing) property "members" and Vaadin will find the getter getMembers and use this for generating the column. We also have to tell vaadin not to display the original "teamMembers" property. So the final code is:

protected Component createContent() {

    Set<Team> teams=new HashSet<>();
    for (int teamCounter = 0; teamCounter < 5; teamCounter++) {
        Team team = createTeam();
        addMembersToTeam(5, team);
        teams.add(team);
    }

    BeanItemContainer<Team> bean=new BeanItemContainer<>(Team.class);

    bean.addNestedContainerProperty("members");
    bean.removeContainerProperty("teamMembers");

    bean.addAll(teams);

    teamTable.setContainerDataSource(bean);

    return new VerticalLayout(teamTable);
}

and the result looks like:

option 2

codinghaus
  • 2,218
  • 1
  • 14
  • 19
  • Tnx @codinghaus, but it's not what I want. I want to have 2 columns in my table, one shows the roles.name and the other must show the locations.location. They have a manytomany relation with LocationsBaseRole and they will be initialized LAZY. – Reza P. Jul 22 '19 at 07:56
  • I thought your main issue was how to display properties (roles.name and locations.location) of sets (roles/locations) in single columns (1 column for roles.name and 1 column for locations.location). Did I understand it wrong? Bc in my proposed options I describe how to solve this. You have LoctionBasedRoles with sets of locations/roles, I have Teams with set of TeamMembers. I created 1 column, in which the name property of each teammember is displayed. Why can't you apply that for your role and location sets? The only difference I see is LAZY, but that is hibernate/jpa related and not vaadin. – codinghaus Jul 22 '19 at 09:33