TheO
TheO

Reputation: 95

Too much items in a Vaadin Tree

I am building a web application using Vaadin, one of the features requires a Tree to show elements, the problem is that this tree is loaded with upto (40K) items or even more.

On the level of several thousand items, Vaadin tree is acceptable, but it is not now and it is slowing everything in the web browser.

What I have in mind is paginating the load between web server and web client and show several items which are updated upon user scrolling the tree, the problem is I don't know where to start and even if this is applicable or not.

Is this a good approach? Is there a better one? giving up a tree for a table is not a solution, the customer does not want to.

Upvotes: 3

Views: 1953

Answers (4)

flavio.donze
flavio.donze

Reputation: 8100

Even though in Vaadin documentation it says lazy loading for Tree is not supported, I managed to implement the following lazy loading Hierarchical interface.

It's very important to store all elements in a local structure (in my case in the HashMap hierarchy), do not read elements multiple times this does not work. I think because Vaadin does not use equals() and hashCode().

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.softmodeler.common.CommonPlugin;
import com.softmodeler.model.OutputNode;
import com.softmodeler.service.IViewService;
import com.vaadin.data.Container.Hierarchical;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.BeanItem;

/**
 * @author Flavio Donzé
 * @version 1.0
 */
public class OutputNodeHierachical implements Hierarchical {
    private static final long serialVersionUID = 8289589835030184018L;

    /** the view service */
    private IViewService service = CommonPlugin.getService(IViewService.class);

    /** collection of all root nodes */
    private List<OutputNode> rootNodes = null;
    /** parent=>children mapping */
    private Map<OutputNode, List<OutputNode>> hierarchy = new HashMap<>();

    /**
     * constructor
     *
     * @param rootNodes collection of all root nodes
     */
    public OutputNodeHierachical(List<OutputNode> rootNodes) {
        this.rootNodes = Collections.unmodifiableList(rootNodes);

        addToHierarchy(rootNodes);
    }

    @Override
    public Collection<?> getChildren(Object itemId) {
        try {
            List<OutputNode> children = hierarchy.get(itemId);
            if (children == null) {
                OutputNode node = (OutputNode) itemId;
                children = service.getChildren(node.getNodeId(), false);

                hierarchy.put(node, children);

                // add children to hierarchy, their children will be added on click
                addToHierarchy(children);
            }
            return children;
        } catch (Exception e) {
            VaadinUtil.handleException(e);
        }
        return null;
    }

    /**
     * add each element to the hierarchy without their children hierarchy(child=>null)
     *
     * @param children elements to add
     */
    private void addToHierarchy(List<OutputNode> children) {
        for (OutputNode child : children) {
            hierarchy.put(child, null);
        }
    }

    @Override
    public boolean areChildrenAllowed(Object itemId) {
        return !((OutputNode) itemId).getChilds().isEmpty();
    }

    @Override
    public boolean hasChildren(Object itemId) {
        return !((OutputNode) itemId).getChilds().isEmpty();
    }

    @Override
    public Object getParent(Object itemId) {
        String parentId = ((OutputNode) itemId).getParentId();
        for (OutputNode node : hierarchy.keySet()) {
            if (node.getNodeId().equals(parentId)) {
                return node;
            }
        }
        return null;
    }

    @Override
    public Collection<?> rootItemIds() {
        return rootNodes;
    }

    @Override
    public boolean isRoot(Object itemId) {
        return rootNodes.contains(itemId);
    }

    @Override
    public Item getItem(Object itemId) {
        return new BeanItem<OutputNode>((OutputNode) itemId);
    }

    @Override
    public boolean containsId(Object itemId) {
        return hierarchy.containsKey(itemId);
    }

    @Override
    public Collection<?> getItemIds() {
        return hierarchy.keySet();
    }

    @Override
    public int size() {
        return hierarchy.size();
    }

    @Override
    public boolean setParent(Object itemId, Object newParentId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item addItem(Object itemId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object addItem() throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeItem(Object itemId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeAllItems() throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Class<?> getType(Object propertyId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Collection<?> getContainerPropertyIds() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Property<?> getContainerProperty(Object itemId, Object propertyId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

}

Adding the container to the Tree like this:

OutputNodeHierachical dataSource = new OutputNodeHierachical(rootNodes);

Tree mainTree = new Tree();
mainTree.setSizeFull();
mainTree.setContainerDataSource(dataSource);
mainTree.addItemClickListener(new ItemClickListener() {
    private static final long serialVersionUID = -413371711541672605L;

    @Override
    public void itemClick(ItemClickEvent event) {
        OutputNode node = (OutputNode) event.getItemId();
        openObject(node.getObjectId());
    }
});


I hope this example helps others since I didn't find a real life example on the internet.

Upvotes: 0

melc
melc

Reputation: 11671

You can lazily load the content of the tree by adding dynamically children nodes when expanding the nodes of the tree. The basic idea of the approach i have used plenty of times is,

final Tree tree = new Tree();
//get the top level collection of entities
        Collection<MyEntity> myEntitiesCategories = findEntities(MyEntity.class);//findEntities is one of your method that retrieves entities from a datasource
        for (MyEntity myEntity : myEntitiesCategories) {
            tree.addItem(myEntity);
        }
        tree.addListener(new Tree.ExpandListener() {

            @Override
            public void nodeExpand(ExpandEvent event) {
                MyEntity myEntityCategory = (MyEntity) event.getItemId();
                Collection<MyEntity> myEntities = myEntityCategory.getChildrenMyEntities();
                for (MyEntity myEntity : myEntities) {
                    tree.addItem(myEntity);
                    tree.setParent(myEntity, myEntityCategory);
                    tree.setChildrenAllowed(myEntity, false);//the boolean value could also be true, it depends on whether it can have children or not
                }
            }
        });

Upvotes: 2

Henri Kerola
Henri Kerola

Reputation: 4967

I would recommend you to use TreeTable instead of Tree in this case. It loads rows lazily from server to client so it won't slow down the browser.

Upvotes: 3

wypieprz
wypieprz

Reputation: 8219

As far as I know Tree component does not support built-in lazy loading (which would be the most convenient for all of us).

One approach would be to:

  • implement your own Collapsible container as a data source
  • load tree elements lazily from the container after expanding a tree node with use of Tree.ExpandListener

Here you can find an example of using Tree.ExpandListener.
Here you can find an example implementation of Collapsible container.

I hope it helps.

Upvotes: 1

Related Questions