Iurii Dziuban
Iurii Dziuban

Reputation: 1091

Vaadin Tree. Lazy loading

I am trying to implement lazy loading of the elements on node Expand Events. There is an issue with this. So on expand event, I create new items for the expanded node using

HierarchicalContainer.addItem() 

But this method calls

containerItemSetChange()

method of AbstractSelect class. There

itemIdMapper.removeAll()

is called. So Map is cleaned and all items of tree get new ids in map. When the answer from server comes to client side, it doesn`t know this new id , because it has previous id (of expanded node) so new items are not added and not rendered on client side.

I have tried https://vaadin.com/forum/-/message_boards/view_message/131802 . But, actually, the data for the whole tree is sent from server to client. And I expect that only data needed for the particular node is sent. So there is no performance "+" for this.

Can Anyone help me with this problem ? Thanks in advance.

Edition

Actually I also needed to change field

partialUpdate

to true, because at first server send all the content of tree to client side.

Upvotes: 4

Views: 2772

Answers (2)

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, since I didn't find a real life example on the internet.

Upvotes: 1

Scott S
Scott S

Reputation: 31

To paraphrase your problem; most people will assume that the tree uses your hashcode/equals implementations on your objects (thats why they're there!) for comparisons. You create your tree, do a bunch of other stuff including modifying the items you put in the tree, and then try to modify the tree using these items as a key.

Here's the problem.

The tree basically takes a snapshot of the hashcode for an item when you call addItem(). That hashcode is used by it internally to map the client artifacts to the object on the server. For any subsequent removeItem(), etc calls it just uses that snapshotted hashcode instead of calling the object's hashcode/equals method like any other normal container. If you debug through it via removeItem() you'll see pretty quickly what the problem is.

  • Simple solution: don't modify your objects once you put them in the tree. But if you do you need to rebuild the whole tree.
  • Better solution: create a proxy object (TreeNode or something) which contains the item but provides it's own equals/hashcode methods.
  • Best solution: Vaadin rework their tree so that it behaves like a standard java container. As it stands currently, this quirky behaviour isn't mentioned in the javadoc's on the methods that are going to fail.

Hope this helps someone.

Upvotes: 3

Related Questions