Tree Model"

From Documentation
Line 129: Line 129:
 
<source lang="xml">
 
<source lang="xml">
 
package foo;
 
package foo;
 +
import org.zkoss.zul.*;
 
public class FileInfoRenderer implements TreeitemRenderer {
 
public class FileInfoRenderer implements TreeitemRenderer {
 
     public void render(Treeitem item, Object data) throws Exception {
 
     public void render(Treeitem item, Object data) throws Exception {
         FileInfo fi = data.getData();
+
         FileInfo fi = (FileInfo)data.getData();
 
         Treerow tr = new Treerow();
 
         Treerow tr = new Treerow();
 
         item.appendChild(tr);
 
         item.appendChild(tr);

Revision as of 08:49, 6 January 2011

Here we describe how to implement a tree model (TreeModel). For the concept about component, model and render, please refer to the Model-driven Display section.

A tree model is used to control how to display a tree-like component, such as Tree.

Instead of implementing TreeModel from scratch, it is suggested to extend from AbstractTreeModel, which will handle the data listeners transparently, while it allows the maximal flexibility, such as load-on-demand and caching.

In additions, if the tree is small enough to be loaded completely, you could use the default implementation, DefaultTreeModel, which uses DefaultTreeNode to construct a tree[1].


  1. DefaultTreeModel was available in 5.0.6. For 5.0.5 or prior, please use SimpleModel, which is similar except it assumes the tree structure is immutable

Example: Load-on-Demand Tree with AbstractTreeModel

Implementing all TreeModel directly provides the maximal flexibility, such as load-on-demand and caching. For example, you don't have to load a node until TreeModel.getChild(Object, int) is called. In additions, you could load and cache all children of a given node when TreeModel.getChild(Object, int) is called first time against a particular node, and then return a child directly if it is in the cache.

For example (pseudo code):

public class MyModel extends AbstractTreeModel {
    public Object getChild(Object parent, int index) {
        Object[] children = _cache.get(parent); //assume you have a cache for children of a given node
        if (children == null)
            children = _cache.loadChildren(parent);
        return children[index];
    }
...

By extending from AbstractTreeModel, you need only to implement three methods: TreeModel.getChild(Object, int), TreeModel.getChildCount(Object), and TreeModel.isLeaf(Object). Optionally, you could implement TreeModel.getIndexOfChild(Object, langObject)[1], if you have a better algorithm than iterating through all children.

Here is a simple example, which generates a four-level tree and each branch has five children:

package foo;
public class FooModel extends AbstractTreeModel {
    public FooModel() {
        super("Root");
    }
    public boolean isLeaf(Object node) {
        return getLevel((String)node) >= 4; //at most 4 levels
    }
    public Object getChild(Object parent, int index) {
        return parent + "." + index;
    }
    public int getChildCount(Object parent) {
        return 5; //each node has 5 children
    }
    public int getIndexOfChild(Object parent, Object child) {
        String data = (String)child;
        int i = data.lastIndexOf('.');
        return Integer.parseInt(data.substring(i + 1));
    }
    private int getLevel(String data) {
        for (int i = -1, level = 0;; ++level)
            if ((i = data.indexOf('.', i + 1)) < 0)
                return level;
    }
};

Then, we could have a ZUML document to display it as follows.

<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
<tree model="${c:new('foo.FooModel')}">
    <treecols>
        <treecol label="Names"/>
    </treecols>
</tree>

And, the result

DrTreeModel1.png


  1. TreeModel.getIndexOfChild(Object, langObject) is available in 5.0.6 and later.

Example: In-Memory Tree with DefaultTreeModel

Since 5.0.6

If you prefer to use TreeNode to construct the tree dynamically, you could use DefaultTreeModel and DefaultTreeNode. The use is straightfoward, but it means the whole tree must be constructed before having it being displayed.

For example, suppose we want to show up a tree of file information, and the file information is stored as FileInfo:

package foo;
public class FileInfo {
    public final String path;
    public final String description;
    public FileInfo(String path, String description) {
           this.path = path;
           this.description = description;
    }
}

Then, we could create a tree of file information with DefaultTreeModel as follows.

TreeModel model = new DefaultTreeModel(
  new DefaultTreeNode(null,
    new DefaultTreeNode[] {
      new DefaultTreeNode(new FileInfo("/doc", "Release and License Notes")),
      new DefaultTreeNode(new FileInfo("/dist", "Distribution"),
        new DefaultTreeNode[] {
          new DefaultTreeNode(new FileInfo("/lib", "ZK Libraries"),
            new DefaultTreeNode[] {
              new DefaultTreeNode(new FileInfo("zcommon.jar", "ZK Common Library")),
              new DefaultTreeNode(new FileInfo("zk.jar", "ZK Core Library"))
            }),
          new DefaultTreeNode(new FileInfo("/src", "Source Code")),
          new DefaultTreeNode(new FileInfo("/xsd", "XSD Files"))
        })
      }
  ));

To render FileInfo, you have to implement a custom renderer. For example,

package foo;
import org.zkoss.zul.*;
public class FileInfoRenderer implements TreeitemRenderer {
    public void render(Treeitem item, Object data) throws Exception {
        FileInfo fi = (FileInfo)data.getData();
        Treerow tr = new Treerow();
        item.appendChild(tr);
        tr.appendChild(new Treecell(fi.path));
        tr.appendChild(new Treecell(fi.description));
    }
}

Then, we could put them together in a ZUML document:

<?variable-resolver class="foo.FooResolver"?>
<tree model="${model}" itemRenderer="${renderer}">
    <treecols>
        <treecol label="Path"/>
        <treecol label="Description"/>
    </treecols>
</tree>

where we assume you have a variable resolver (VariableResolver) that could resolve model and renderer. For example,

package foo;
public class FooResolver implements org.zkoss.xel.VariableResolver {
    public Object resolveVariable(String name) {
        if ("model".equals(name))
            return new DefaultTreeModel(..../*as shown above*/);
        if ("renderer".equals(name))
            return new FooRenderer();
        return null;
    }
}

Then, the result:

DrTreeModel2.png

Notice that you could manipulate the tree dynamically (such as adding a node with DefaultTreeNode.add(TreeNode)). The tree shown at the browser will be modified accordingly.

Version History

Last Update : 2011/01/06


Version Date Content
5.0.6 January 2011 TreeNode, DefaultTreeNode and DefaultTreeModel were intrdocued.



Last Update : 2011/01/06

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.