ZK Tree Model
Jeff Liu, Engineer, Potix Corporation
Aug 16, 2007
Applicable to zk-2.5.0-FL-2007-08-16 and later.
Introduction
The Tree component is amazing enough. But, when the amount of data is enormous, lets say one hundred thousand, it is going to crash the browser and the server. In practice, the application users rarely view all one hundred thousand nodes at once. Therefore, there is paging feature in ZK Treeitem. even though burden on client side is reduced by paging feature, there is still hefty java objects loading that slows server down. Secondly, when the content of a node is modified data-wisely, a messy implementation of synchronizing the ZK Tree node and data entry is needed. Image coding all the dfs( depth first search) stuff. D’oh, it is a pain. Now, we are going to introduce something enables the “load on demand” feature in tree and automatically synchronize the ZK Tree node with its associated data, the “Tree Model.”
Problem:
1. When developer has huge amount of data entries to build a tree 2. When developer modify data entries in tree structure
Solution:
1. "Load on Demand," data entris are loaded on onOpen() event 2. "TreeDataListener, " listens to the event being fired from data changing
A binary tree with 100000 nodes
When there are enormous data entries in a tree structure, users not likely view all of them at once. They more likely want nodes are “load on demanded.” Also, the proportion of demanded entries is relatively small. For example, a binary tree with 100000 nodes. Do developers need to load all 100000 nodes at first? Obviously, no. Generally, only the root and its 2 children are needed to load at first. And, only 2 children of a node are needed to load whenever a branch is opened. By using BinaryTreeModel, its problem is solved.
The code is very straight-forward. We create an arrayList with 100000 data entries, then construct a binary tree model for it. Then, as what we do in the listbox and listModel, a treemodel "btm" is assigned to the tree.
<?xml version="1.0" encoding="utf-8"?>
<zk>
<window>
<zscript>
ArrayList al = new ArrayList();
int i=0;
for(; i < 100000; i++)
{
Object obj = ""+i;
al.add(obj);
}
BinaryTreeModel btm = new BinaryTreeModel(al);
</zscript>
<tree model="${btm}" id="tree" >
</tree>
</window>
</zk>
The BinaryTreeModel is a tree model class that extends AbstractTreeModel BinaryTreeModel.java
Why the node(0) is not displayed?
**
* Constructor
* @param tree the list is contained all data of nodes.
*/
public BinaryTreeModel(List tree){
// node(0) is root
super(tree.get(0));
_tree = (ArrayList)tree;
}
The root of TreeModel is mapped to the Tree Component, not the first Treeitem..! As a result, the root(0) is not displayed in this case.
Set, Insert, Append, and Remove Data Entries
When the content of a node is modified data-wisely, a messy implementation on synchronizing the ZK Tree node with data is needed. Image coding all the dfs( depth first search) stuff. By using TreeModel, the synchronization of data and UI is taken care automatically by Tree. In this demo, ArrayList is used to structure the tree. TreeModelA extends the AbstractTreeModel to handle the TreeDataEvent.
<zscript>
import demo.TreeModelA;
//An ArrayList is created to be the root of tree
ArrayList mother = new ArrayList();
//Create a branch "child1" and assign children to it
ArrayList child1 = new ArrayList();
child1.add("Tommy");
child1.add("Juile");
//Create a branch "child2" and assign children to it
ArrayList child2 = new ArrayList();
child2.add("Gray");
//Create a branch "grandchild" and assign children to it
ArrayList grandChild = new ArrayList();
grandChild.add("Paul");
grandChild.add("Eric");
//Assign branch "grandchild" to be branch "child2"'s child
child2.add(grandChild);
//Assign branch "grand2" to be branch "child1"'s child
child1.add(child2);
//Assign children to root "mother"
mother.add("Andy");
mother.add("Davis");
mother.add(child1);
mother.add("Matter");
mother.add("Kitty");
//TreeModelA class is constructed, only the root "mother" of tree is passed to its constructor.
TreeModelA tma = new TreeModelA(mother);
...
</zscript>
<vbox>
<tree model="${tma}" id="tree" pageSize="5">
</tree>
Insert, Append and Remove
You might notice from the demo. Nodes modification is not limited at a single node. Developers can modify a range of nodes. For example, removing nodes from index 0 to index 2. And its code is light, take a look the code below.
- TreeDataEvent.zul
<zscript>
...
public void remove(){
//Remove child's children nodes from index 0 to index 0, which means node is at index 0
stm.remove(child,0,0);
}
public void removeMuti(){
//Remove child's children nodes from index 0 to index 2
stm.remove(child,0,2);
}
...
</zscript>
Modify
Similar to removal, insert, and append, changing multi-nodes' contents is supported.
...
public void change2Data(){
Object[] data = {Array of Objects};
//Set child's children nodes from index 3 to index 5 with Object Array "data"
stm.set(child,3,5,data);
...
How TreeDataEvent is fired?
After modifying, inserting, removing or appending chidren nodes to the Branch(ArrayList), a TreeDataEvent is fired by the fireEvent method in TreeModelA to notify the associated Tree that a corresponding treeitem adjustment is needed. From this point, the Tree object will take care this adjustment "automatically". By the tree's blackbox operation, developers only deal with the data entries and don't worry the UI adjustment.
There are 4 arguments in fireEvent method:
- parent - the parent node that its children being modified .
- indexFrom - the lower index of the change range.
- indexTo - the upper index of the change range.
- type - one of CONTENTS_CHANGED, INTERVAL_ADDED, or INTERVAL_REMOVED.
TreeModelA
public void remove(Object parent, int indexFrom, int indexTo){
...
Remove children of index from indexFrom to indexTo from ArrayList parent
...
fireEvent(parent,indexFrom,indexTo,TreeDataEvent.INTERVAL_REMOVED);
}
...
public void add(Object parent, Object[] newNodes){
...
Append children of index from indexFrom to indexTo from ArrayList parent
...
fireEvent(parent,indexFrom,indexTo,TreeDataEvent.INTERVAL_ADDED);
}
...
public void insert(Object parent, int indexFrom, int indexTo, Object[] newNodes){
...
Insert children of index from indexFrom to indexTo from ArrayList parent
...
fireEvent(parent,indexFrom,indexTo,TreeDataEvent.INTERVAL_ADDED);
}
...
public void set(Object parent, int indexFrom, int indexTo, Object[] values){
...
Modify children of index from indexFrom to indexTo from ArrayList parent with new contents values
...
fireEvent(parent,indexFrom,indexTo,TreeDataEvent.CONTENTS_CHANGED);
}
...
Complete source code: TreeModelA.java
Important..!
When passing the from and to indexes to fireEvent, from and to indexes have to be in valid format. Ex: to > 0 , to > from and etc... Otherwise, an exception will be thrown
What should I Do with Multi-Columns Row - TreeitemRenderer
By implementing the TreeitemRenderer, developers can render multi-columns in node.
Multi-Columns
In this demo, PersonTreeitemRenderer is implemented for render Person object into a tree node. SimpleTreeNode and SimpleTreeModel are used to construct Tree.
<zscript>
import demo.Person;
import demo.PersonTreeitemRenderer;
//Create nodes for department R and D
String[] name = {"Pierre","Adam","Thomas"};
String[] accountId = {"p001","a002","t003"};
ArrayList alc = new ArrayList();
for(int i =0; i < name.length; i++){
Person person = new Person(name[i],name[i] + "@zkoss.org",accountId[i]);
SimpleTreeNode stn = new SimpleTreeNode(person,new ArrayList());
alc.add(stn);
}
//Create branch department R and D and assigned children to it. rdDep's children are contained in ArrayList alc.
SimpleTreeNode rdDep = new SimpleTreeNode(new Person("R&D","",""),alc);
//Create nodes for department sales
String[] name2 = {"Paul","Eric","Gray"};
String[] accountId2 = {"p002","e009","g019"};
ArrayList alc2 = new ArrayList();
for(int i =0; i < name.length; i++){
Person person = new Person(name2[i],name2[i] + "@zkoss.org",accountId2[i]);
SimpleTreeNode stn = new SimpleTreeNode(person,new ArrayList());
alc2.add(stn);
}
//Create branch department sales and assigned children to it
SimpleTreeNode salesDep = new SimpleTreeNode(new Person("Sales","",""),alc2);
//create root and assigned children to it
ArrayList al = new ArrayList();
al.add(salesDep);
al.add(rdDep);
SimpleTreeNode root = new SimpleTreeNode("ROOT",al);
//create treemodel and assign root
SimpleTreeModel stm = new SimpleTreeModel(root);
//create a PersonTreeitemRenderer
PersonTreeitemRenderer ptr = new PersonTreeitemRenderer();
</zscript>
<tree width="450px" treeitemRenderer="${ptr}" model="${stm}" id="tree" >
<treecols>
<treecol width="120px" label="Name" />
<treecol width="120px" label="Email" />
<treecol width="120px" label="AccountId" />
</treecols>
</tree>
SimpleTreeNode Constructor
public SimpleTreeNode(Object data, List children){
//Construct SimpleTreeNode
}
Important..!
Notice: Only SimpleTreeNode should be SimpleTreeNode's children, not another object
How the node is Rendered by PersonTreeitemRenderer?
Let's take a look at the render method in PersonTreeitemRenderer.
public void render(Treeitem item, Object data) throws Exception {
SimpleTreeNode t = (SimpleTreeNode)data;
Person person = (Person)t.getData();
//Construct treecells
Treecell tcEmail = new Treecell(person.getEmail());
Treecell tcName = new Treecell(person.getName());
Treecell tcAccountId = new Treecell(person.getAccountId());
Treerow tr = null;
/*
* Since only one treerow is allowed, if treerow is not null,
* append treecells to it. If treerow is null, construct a new
* treerow and attach it to item.
*/
if(item.getTreerow()==null){
tr = new Treerow();
tr.setParent(item);
}else{
tr = item.getTreerow();
tr.getChildren().clear();
}
//Attach treecells to treerow
tcName.setParent(tr);
tcEmail.setParent(tr);
tcAccountId.setParent(tr);
item.setOpen(false);
}
Complete source code: PersonTreeitemRenderer.java , Person.java , SimpleTreeNode.java , SimpleTreeModel.java
There are few things to notice here:
- A new treerow should be constructed and append to item, when treerow of item is null.
- Otherwise, when treerow of item is not null, modify the content of the treerow or detach the treerow's children first, since that only one treerow is allowed
- Do not append any treechildren to item in this method, a treechildren will be appended afterward.
- When a treerow is not appended to item, generally label of item is displayed.
Installation
1. Download the TreeModelDemo.war 2. Deploy it by Tomcat manager or extract folder into TOMCAT_PATH\webapps\
Download
- Download TreeModelDemo.war
Summary
As mentioned in this small talk, by implementing the TreeModel interface, developers are able to directly deal with data in model level. Treeitem will be loaded on demand and the Tree object will take care its adjustment "automatically". By the tree's blackbox operation, developers don't worry the UI adjustment anymore.
Please, check out the source code provided in this small talk. It will give you a good idea how to implement TreeModel interface and make use of it. If you have any suggestion or want to report a bug. Please, feel free to post it on ZK forum.
Questions for Fun
In the 100000 Nodes Binary Tree,
- How many levels are in the tree?
- How many nodes are in the last level?
- What are the equations for them?
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |