ZK Tree Model"

From Documentation
Line 344: Line 344:
 
=Download=
 
=Download=
  
* Download [http://zkoss.org/smalltalks/zkTreeModel/TreeModelDemo.war TreeModelDemo.war]
+
* Download [http://sourceforge.net/projects/zkforge/files/Small%20Talks/ZK%20Tree%20Model/TreeModelDemo.war/download TreeModelDemo.war]
  
 
=Summary=
 
=Summary=

Revision as of 03:10, 26 March 2012

ZK Tree Model

Author
Jeff Liu, Engineer, Potix Corporation
Date
Aug 16, 2007
Version
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 &lt; 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

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.