Reputation: 95
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
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
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
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
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:
Collapsible
container as a data sourceTree.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