Search code examples
javatreevaadinvaadin7

How to implement a lazy loading tree in Vaadin?


I have a fairly large tree in the database, loading all items and adding them to a HierarchicalContainer on startup performs poorly.
Instead I want to load the children of tree items on click.
Well actually a bit earlier, since I don't want areChildrenAllowed() or hasChildren() to return the wrong value.

I hoped to find something like in JFace TreeViewer is used ITreeContentProvider.

Is there any example or best practice description of this topic?

This is what I got so far:

public class OutputNodeContainer extends HierarchicalContainer {

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

    private List<Object> childrenRead = new ArrayList<>();

    @Override
    public boolean areChildrenAllowed(Object itemId) {
        if (!childrenRead.contains(itemId)) {
            OutputNode node = (OutputNode) itemId;
            List<OutputNode> children = service.getChildren(node.getNodeId(), false);
            for (OutputNode child : children) {
                addItem(child);
                setParent(child, itemId);
            }
            childrenRead.add(itemId);
            return !children.isEmpty();
        }
        return super.areChildrenAllowed(itemId);
    }
}

But on addItem(child); I run into this exception:

java.lang.IllegalStateException: A connector should not be marked as dirty while a response is being written.
        at com.vaadin.ui.ConnectorTracker.markDirty(ConnectorTracker.java:489)
        at com.vaadin.server.AbstractClientConnector.markAsDirty(AbstractClientConnector.java:143)
        at com.vaadin.ui.Tree.markAsDirty(Tree.java:348)
        at com.vaadin.ui.AbstractSelect.fireItemSetChange(AbstractSelect.java:1746)
        at com.vaadin.ui.AbstractSelect.containerItemSetChange(AbstractSelect.java:1713)
        at com.vaadin.ui.Tree.containerItemSetChange(Tree.java:992)
        at com.vaadin.data.util.AbstractContainer.fireItemSetChange(AbstractContainer.java:246)
        at com.vaadin.data.util.HierarchicalContainer.fireItemSetChange(HierarchicalContainer.java:436)
        at com.vaadin.data.util.IndexedContainer.fireItemSetChange(IndexedContainer.java:640)
        at com.vaadin.data.util.HierarchicalContainer.enableAndFireContentsChangeEvents(HierarchicalContainer.java:460)
        at com.vaadin.data.util.HierarchicalContainer.addItem(HierarchicalContainer.java:489)
        at ch.scodi.vaadin.viewer.OutputNodeContainer.areChildrenAllowed(OutputNodeContainer.java:78)
        at com.vaadin.ui.Tree.areChildrenAllowed(Tree.java:864)
        at com.vaadin.ui.Tree.paintContent(Tree.java:732)
        at com.vaadin.server.LegacyPaint.paint(LegacyPaint.java:65)
        at com.vaadin.server.communication.LegacyUidlWriter.write(LegacyUidlWriter.java:82)
        at com.vaadin.server.communication.UidlWriter.write(UidlWriter.java:143)
        at com.vaadin.server.communication.UIInitHandler.getInitialUidl(UIInitHandler.java:284)
        at com.vaadin.server.communication.UIInitHandler.synchronizedHandleRequest(UIInitHandler.java:80)
        at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
        at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1422)
        ... 63 common frames omitted

Solution

  • 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());
        }
    });