rmuller
rmuller

Reputation: 12859

Replacement for collapseItemsRecursively and expandItemsRecursively in Vaadin 8.1 TreeGrid

Vaadin 8.1 introduced the TreeGrid component. It does not have the collapseItemsRecursively and expandItemsRecursively methods anymore (as available in the now legacy Tree component). Do i miss something or do you need to develop your own implementation? If so, what is a recommended way of doing this?

Upvotes: 0

Views: 736

Answers (2)

Morfic
Morfic

Reputation: 15508

As I'm sure you've noticed, the TreeGrid is a rather new component, currently being developed and available starting with v8.1.alphaX (current stable version is v8.0.6). As such, it probably has only some basic functionalities for the time being, with the rest to follow sometime in the future, although there are no guarantee. For example this similar feature request for the older TreeTable component has been in open state since 2011.

Vaadin docs warning

Either way, even if they're probably not the optimum solutions, there are a couple of work-arounds that you can use to achieve this behavior. I'm shamelessly using as a base sample, a slightly modified version of the code currently available in the vaadin-sampler for TreeGrid.

public class RecursiveExpansionTreeGrid extends VerticalLayout {

    private Random random = new Random();

    public RecursiveExpansionTreeGrid() {
        // common setup with some dummy data
        TreeGrid<Project> treeGrid = new TreeGrid<>();
        treeGrid.setItems(generateProjectsForYears(2010, 2016), Project::getSubProjects);
        treeGrid.addColumn(Project::getName).setCaption("Project Name").setId("name-column");
        treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");
        treeGrid.addColumn(Project::getLastModified).setCaption("Last Modified");
        addComponent(treeGrid);
    }

    // generate some dummy data to display in the tree grid
    private List<Project> generateProjectsForYears(int startYear, int endYear) {
        List<Project> projects = new ArrayList<>();

        for (int year = startYear; year <= endYear; year++) {
            Project yearProject = new Project("Year " + year);

            for (int i = 1; i < 2 + random.nextInt(5); i++) {
                Project customerProject = new Project("Customer Project " + i);
                customerProject.setSubProjects(Arrays.asList(
                        new LeafProject("Implementation", random.nextInt(100), year),
                        new LeafProject("Planning", random.nextInt(10), year),
                        new LeafProject("Prototyping", random.nextInt(20), year)));
                yearProject.addSubProject(customerProject);
            }
            projects.add(yearProject);
        }
        return projects;
    }

    // POJO for easy binding
    public class Project {
        private List<Project> subProjects = new ArrayList<>();
        private String name;

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

        public String getName() {
            return name;
        }

        public List<Project> getSubProjects() {
            return subProjects;
        }

        public void setSubProjects(List<Project> subProjects) {
            this.subProjects = subProjects;
        }

        public void addSubProject(Project subProject) {
            subProjects.add(subProject);
        }

        public int getHoursDone() {
            return getSubProjects().stream().map(project -> project.getHoursDone()).reduce(0, Integer::sum);
        }

        public Date getLastModified() {
            return getSubProjects().stream().map(project -> project.getLastModified()).max(Date::compareTo).orElse(null);
        }
    }

    // Second POJO for easy binding
    public class LeafProject extends Project {
        private int hoursDone;
        private Date lastModified;

        public LeafProject(String name, int hoursDone, int year) {
            super(name);
            this.hoursDone = hoursDone;
            lastModified = new Date(year - 1900, random.nextInt(12), random.nextInt(10));
        }

        @Override
        public int getHoursDone() {
            return hoursDone;
        }

        @Override
        public Date getLastModified() {
            return lastModified;
        }
    }
}

Next, recursively expanding or collapsing the nodes depends a bit on your scenario, but basically it breaks down to the same thing: making sure each node from the root to the deepest leaf is expanded/collapsed.The simplest way of doing it is to flatten your hierarchy into a list of nodes, and call the appropriate method, expand(List<T> items) or expand(T ... items) (the second delegates to the first and is probably a convenience method eg expand(myItem)).

For simplicity, I've added a flatten method in our Project implementation. If you can't do that for some reason, then create a recursive method that creates a list starting with the selected node and includes all the children, of the children, of the children.... well, you get the idea.

public Stream<Project> flatten() {
    return Stream.concat(Stream.of(this), getSubProjects().stream().flatMap(Project::flatten));
}

Possible scenarios:

  1. Automatically expand the entire hierarchy when expanding the root - add listeners, and expand/collapse the whole flattened hierarchy:
treeGrid.addCollapseListener(event -> {
    if (event.isUserOriginated()) {
        // event is triggered by all collapse calls, so only do it the first time, when the user clicks in the UI
        // and ignore the programmatic calls
        treeGrid.collapse(event.getCollapsedItem().flatten().collect(Collectors.toList()));
    }
});
treeGrid.addExpandListener(event -> {
    if (event.isUserOriginated()) {
        // event is triggered by all expand calls, so only do it the first time, when the user clicks in the UI
        // and ignore the programmatic calls
        treeGrid.expand(event.getExpandedItem().flatten().collect(Collectors.toList()));
    }
});
  1. Expanding the hierarchy or part of it with a custom action, such as a context menu
GridContextMenu<Project> contextMenu = new GridContextMenu<>(treeGrid);
contextMenu.addGridBodyContextMenuListener(contextEvent -> {
    contextMenu.removeItems();
    if (contextEvent.getItem() != null) {
        Project project = (Project) contextEvent.getItem();
        // update selection
        treeGrid.select(project);

        // show option for expanding
        contextMenu.addItem("Expand all", VaadinIcons.PLUS, event -> treeGrid.expand((project).flatten().collect(Collectors.toList())));

        // show option for collapsing
        contextMenu.addItem("Collapse all", VaadinIcons.MINUS, event -> treeGrid.collapse((project).flatten().collect(Collectors.toList())));
    }
});

In the end, you should be getting this effect:

TreeGrid - expand or collapse recursively

Upvotes: 2

petey
petey

Reputation: 17140

From the docs for treegrid, you can use the methods, collapse and expand, by passing a list or array of the treegrid's data items to expand or collapse:

treeGrid.expand(someTreeGridItem1, someTreeGridItem2);
treeGrid.collapse(someTreeGridItem1);

Also worthy of note, is a section showing the ability to prevent certain items from ever being collapsed

Upvotes: 0

Related Questions