Processing...
Description & Source Code
  • Description
  • View
  • Controller
    CategoryController.java
    CategoryTreeNode.java
  • Model
    Car.java
    CarService.java
    Category.java

A Tree component displays hierarchical data. In this demo we'll see how to use a data model to render a Tree and how to handle events arose from a user's selection of items in Tree.


Access Controller's Variables in View

We may access variables in the controller from view by the use of an EL Expression:${expression}. The implicit variable win$composer is a reference to the controller which was assigned to the Window component with the ID: win. For instance, to assign the component attribute model with the a data model called categoriesModel in the controller, we write model="${win$composer.categoriesModel}".


Model Creation

An implementation of the TreeModel<E> interface provides a hierarchical data model to retrieve/store data in Tree. In order to represent hierarchical structure, a TreeNode<E> can be nested indefinitely in one and another. The default implementations are DefaultTreeModel<E> and DefaultTreeNode<E>.

A DefaultTreeNode represents a node in a tree structure which contains custom data and zero(ie. a leaf node) or more child nodes. It provides utility methods that facilitates tasks such as tree traversal and data retrieval.

public class CategoryTreeNode extends DefaultTreeNode<Category> {
	...
}
CategoryTreeNode node = ...
for(Category childCategory : ...) {
	CategoryTreeNode child = new CategoryTreeNode(...);
	node.add(child);
	...
}

Lastly, we wrap the root node into a TreeModel implementation such as DefaultTreeModel before it is assigned to a Tree. The method addOpenPath(int[]) called on DefaultTreeModel allows us to open a node of a specified path.

categoriesModel = new DefaultTreeModel<Category>(rootNode);
((DefaultTreeModel<Category>)categoriesModel).addOpenPath(new int[]{0});

Model Usage

When we access Tree data through a data model using the EL expression as shown in the snippet below, we're in effect calling the controller's getCategoriesModel() method.

The one or more Treecol components declared determines the number of columns present in a Tree component. Hence, a Treerow must contain as many Treecells as Treecols; the Treecells contained in the first Treecol will display the tree nodes which users can expand to see the nested Treerows.

<tree id="categoriesTree" model="${win$composer.categoriesModel}" width="220px" vflex="1">
	<treecols>
		<treecol label="Filter" />
		<treecol label="Count" align="center" width="55px" />
	</treecols>
	<template name="model">
		<treeitem>
			<treerow>
				<treecell label="${each.description}" />
				<treecell label="${each.count}" />
			</treerow>
		</treeitem>
	</template>
</tree>

hflex and vflex indicate respectively the horizontal and vertical "flexibility" of a component. "Flexibility" refers to how a component's parent should distribute the remaining empty space among its children components. See detailed explanation and examples here .

<template> is a tag that defines how to create components iteratively and the implicit object each represents the item being iterated from model.


Selection

We can attach an onSelect event listener to Tree so the method will be invoked the event occurs. We can then retrieve the selected node from the tree model through its method getSelection() .

Tree also supports multiple selection. When a Tree is assigned with a TreeModel or its variants, such as the DefaultTreeModel, we can invoke its setMultiple(boolean) method to enable multiple selection.

private TreeModel<Category> categoriesModel;

@Listen("onSelect = #categoriesTree")
public void displayCars() {
	TreeNode<Category> selectedNode = ((DefaultTreeModel<Category>)categoriesModel).getSelection()
			.iterator().next();
	Category selectedCategory = selectedNode.getData();
	...
}
demo.zul
<window id="win" title="Car Categories" border="normal"
	apply="demo.getting_started.tree.CategoryController">
	<hlayout height="300px">
		<tree id="categoriesTree" model="${win$composer.categoriesModel}" width="220px" vflex="1">
			<treecols>
				<treecol label="Filter" />
				<treecol label="Count" align="center" width="55px" />
			</treecols>
			<template name="model">
				<treeitem>
					<treerow>
						<treecell label="${each.description}" />
						<treecell label="${each.count}" />
					</treerow>
				</treeitem>
			</template>
		</tree>
		<grid id="resultGrid" hflex="1" vflex="1">
			<columns>
				<column label="Model" align="center" />
				<column label="Make" align="center" />
				<column label="Type" align="center" />
				<column label="Displacement" align="center" />
				<column label="Transmission" align="center" />
			</columns>
			<template name="model">
				<row>
					<label value="${each.model}" />
					<label value="${each.make}" />
					<label value="${each.type}" />
					<label value="${each.engineDisplacement} c.c." />
					<hlayout>
						<image src="/widgets/getting_started/img/${each.autoTransmission ? 'at' : 'mt' }.png" />
						<label value="${each.autoTransmission ? 'AT' : 'MT'}" />
					</hlayout>
				</row>
			</template>
		</grid>
	</hlayout>
</window>
CategoryController.java
package demo.getting_started.tree;

import java.util.LinkedList;
import java.util.List;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.DefaultTreeModel;
import org.zkoss.zul.DefaultTreeNode;
import org.zkoss.zul.Grid;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.TreeModel;
import org.zkoss.zul.TreeNode;
import demo.getting_started.Car;
import demo.getting_started.CarService;
import demo.getting_started.CarServiceImpl;
import demo.getting_started.Category;

public class CategoryController extends SelectorComposer<Component> {
	private static final long serialVersionUID = 1L;

	private CarService carService = new CarServiceImpl();
	private TreeModel<TreeNode<Category>> categoriesModel;

	@Wire
	private Grid resultGrid;

	public CategoryController() {
		// construct tree nodes by categories
		Category categoryRoot = carService.getCarCategoriesRoot();
		CategoryTreeNode rootNode = constructCategoryTreeNode(categoryRoot);

		// create tree model
		categoriesModel = new DefaultTreeModel<Category>(rootNode);
		((DefaultTreeModel<Category>)categoriesModel).addOpenPath(new int[]{0});
	}

	private CategoryTreeNode constructCategoryTreeNode(Category category) {
		CategoryTreeNode categoryNode = new CategoryTreeNode(category,carService.countByFilter(category.getName()));
		LinkedList<CategoryTreeNode> queue = new LinkedList<CategoryTreeNode>(); // BFS
		queue.add(categoryNode);
		while(!queue.isEmpty()) {
			CategoryTreeNode node = queue.remove();
			for(Category childCategory : node.getData().getChildren()) {
				CategoryTreeNode child = new CategoryTreeNode(childCategory,carService.countByFilter(childCategory.getName()));
				node.add(child);
				queue.add(child);
			}
		}
		CategoryTreeNode rootNode = new CategoryTreeNode(null,-1); // won't show
		rootNode.add(categoryNode);
		return rootNode;
	}

	public TreeModel<TreeNode<Category>> getCategoriesModel() {
		return categoriesModel;
	}

	@Listen("onSelect = #categoriesTree")
	public void displayCars() {
		TreeNode<Category> selectedNode = ((DefaultTreeModel<Category>)categoriesModel).getSelection()
				.iterator().next();
		Category selectedCategory = selectedNode.getData();
		List<Car> cars = carService.queryByFilter(selectedCategory.getName());
		resultGrid.setModel(new ListModelList<Car>(cars));
	}
}
CategoryTreeNode.java
package demo.getting_started.tree;

import java.util.LinkedList;

import org.zkoss.zul.DefaultTreeNode;
import org.zkoss.zul.TreeNode;

import demo.getting_started.Category;

public class CategoryTreeNode extends DefaultTreeNode<Category> {
	private static final long serialVersionUID = 1L;
	int count;

	public CategoryTreeNode(Category category, int count) {
		super(category, new LinkedList<TreeNode<Category>>()); // assume not a leaf-node
		this.count = count;
	}

	public String getDescription() {
		return getData().getDescription();
	}

	public int getCount() {
		return count;
	}

	public boolean isLeaf() {
		return getData() != null && getData().getChildren().isEmpty();
	}
}
Car.java
package demo.getting_started;

import java.util.Set;

public class Car {

	private String carId;
	private String model;
	private String picture;
	private String make;
	private String country;
	private String type;
	private double cost;
	private int engineDisplacement;
	private boolean autoTransmission;
	private Accessories accessories;
	private Set<String> salesmen;

	public Car() {
	}

	public String getCarId() {
		return carId;
	}

	public void setCarId(String carId) {
		this.carId = carId;
	}

	public String getModel() {
		return model;
	}

	public void setModel(String model) {
		this.model = model;
	}

	public String getPicture() {
		return picture;
	}

	public void setPicture(String picture) {
		this.picture = picture;
	}

	public String getMake() {
		return make;
	}

	public void setMake(String make) {
		this.make = make;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public double getCost() {
		return cost;
	}

	public void setCost(double cost) {
		this.cost = cost;
	}

	public int getEngineDisplacement() {
		return engineDisplacement;
	}

	public void setEngineDisplacement(int engineDisplacement) {
		this.engineDisplacement = engineDisplacement;
	}

	public boolean isAutoTransmission() {
		return autoTransmission;
	}

	public void setAutoTransmission(boolean autoTransmission) {
		this.autoTransmission = autoTransmission;
	}

	public Accessories getAccessories() {
		return accessories;
	}

	public void setAccessories(Accessories accessories) {
		this.accessories = accessories;
	}

	public Set<String> getSalesmen() {
		return salesmen;
	}

	public void setSalesmen(Set<String> salesmen) {
		this.salesmen = salesmen;
	}

	public String toString() {
		return model;
	}
}
CarService.java
package demo.getting_started;

import java.util.List;

public interface CarService {

	/**
	 * Retrieve all cars in the car store.
	 * @return all cars.
	 */
	public List<Car> findAll();

	/**
	 * Store or modify a car in car store.
	 */
	void store(Car car);

	/**
	 * Store or modify a inventory item in car store.
	 */
	void store(InventoryItem inventoryItem);

	/**
	 * Order cars.
	 */
	void order(List<OrderItem> orderItems);

	/**
	 * Retrieve the root of car categories.
	 */
	Category getCarCategoriesRoot();

	/**
	 * Count cars by filter.
	 */
	int countByFilter(String filter);

	/**
	 * Query cars by filter.
	 */
	List<Car> queryByFilter(String filter);
}
Category.java
package demo.getting_started;

import java.util.LinkedList;
import java.util.List;

public class Category {
	private String name;
	private String description;
	private List<Category> children = new LinkedList<Category>();

	public Category(String name, String description) {
		this.name = name;
		this.description = description;
	}

	public void addChild(Category child) {
		if(child != null)
			children.add(child);
	}

	public void removeChild(Category child) {
		if(child != null)
			children.remove(child);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public List<Category> getChildren() {
		return children;
	}

	public void setChildren(List<Category> children) {
		this.children = children;
	}
}