Processing...
Description & Source Code
  • Description
  • XML & Java Source
    dynamic_tree.zul
    dialog.zul
    DemoComposer.java
    AdvancedTreeModel.java
    ContactTreeNode.java
    ContactList.java
    Contact.java

In this demo, the items are made drag and drop-able using AdvancedTreeModel.
The AdvancedTreeModel provides API that allows the insertion and removal of tree items.

Following is a snap of AdvancedTreeModel :

public class AdvancedTreeModel extends SimpleTreeModel { 
    public void remove(SimpleTreeNode parent, int indexFrom, int indexTo) {...}
    public void remove(SimpleTreeNode target) {...}
    public void insert(Object parent, int indexFrom, int indexTo, SimpleTreeNode[] newNodes) {...}
    public void add(SimpleTreeNode parent, Object[] newNodes) {...}
}

dynamic_tree.zul
<window id="demoWindow" apply="demo.tree.dynamic_tree.DemoComposer">
	<style>
	.h-inline-block {
		display: inline-block;
		_display: inline;
	}
	</style>
	<tree id="tree" width="300px" >
		<treecols>
			<treecol label="My Contact List" />		
		</treecols>
	</tree>
</window>
dialog.zul
<zk>
	<style><![CDATA[
		.dialog .msg .z-label {
		  font-weight: bold;
		  font-style: italic;
		  color: #008BB6;
		}
		.dialog .z-borderlayout,
		.dialog .z-north, .dialog .z-south,
		.dialog .z-east,  .dialog .z-west,
		.dialog .z-center, .dialog .z-panel-children ,
		.dialog.z-panel-noheader div.z-toolbar {
			border: 0 none;
			background:transparent;
		}
		.dialog .viewer .z-label {
			font-weight: bold;
		}
		.dialog .viewer pre, .dialog .viewer .content {
			font-weight: normal;
			padding-left:20px;
			margin: 0;
		}
		.z-south-splitter {
			border: 0 none;
		}
	]]></style>
	<panel id="panel" width="500px" height="400px" border="normal" sclass="dialog">
		<toolbar sclass="msg">This is a sample IM dialog. Nothing will be send back.</toolbar>
		<panelchildren>
			<borderlayout>
				<west size="130px">
					<borderlayout height="370px" width="100%">
						<north>
							<image src="/widgets/tree/dynamic_tree/img/contact.gif" width="121px" height="118px"/>
						</north>
						<south>
							<image src="/widgets/tree/dynamic_tree/img/contact_zk.gif" width="121px" height="118px"/>
						</south>
					</borderlayout>
				</west>
				<east width="360px" border="0" vflex="1">
					<borderlayout height="370px">
						<center vflex="1">
							<vlayout id="viewer" sclass="viewer" style="overflow:auto;">
								Ryan@zkoss.org says:
								<label sclass="content">There ?</label>
								${arg.name} - (A ZK Lover) says:
								<label sclass="content">Yeap</label>
							</vlayout>
						</center>
						<south splittable="true" height="130px" >
							<div>
								<textbox id="text" multiline="true" width="100%" height="80px" style="margin: 0 0 0 1px"
									value="This Demo is so cool" />
								<toolbar mold="panel" align="center">
									<button label="Send" width="65px">
										<attribute name="onClick"><![CDATA[
											if (text.value.isEmpty())
												return;
											Div div = new Div();
											new Label("Ryan@zkoss.org says:").setParent(div);
											Html content = new Html("<pre>" + text.value + "</pre>");
											viewer.appendChild(div);
											viewer.appendChild(content);
											text.value = "";
										]]></attribute>
									</button>
									<button label="Clear" width="65px" onClick='text.value = ""' />
								</toolbar>
							</div>
						</south>
					</borderlayout>

				</east>
			</borderlayout>
		</panelchildren>
	</panel>
</zk>
DemoComposer.java
package demo.tree.dynamic_tree;

import java.util.HashMap;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.DropEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.DefaultTreeNode;
import org.zkoss.zul.Hlayout;
import org.zkoss.zul.Image;
import org.zkoss.zul.Label;
import org.zkoss.zul.Tree;
import org.zkoss.zul.Treecell;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.TreeitemRenderer;
import org.zkoss.zul.Treerow;
import org.zkoss.zul.Window;

import demo.data.ContactList;
import demo.data.pojo.Contact;

public class DemoComposer extends SelectorComposer<Component> {
	private static final long serialVersionUID = 3814570327995355261L;
	
	@Wire
	private Window demoWindow;
	@Wire
	private Tree tree;

	private AdvancedTreeModel contactTreeModel;

	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);		
		contactTreeModel = new AdvancedTreeModel(new ContactList().getRoot());
		tree.setItemRenderer(new ContactTreeRenderer());
		tree.setModel(contactTreeModel);
	}

	/**
	 * The structure of tree
	 * 
	 * <pre>
	 * &lt;treeitem>
	 *   &lt;treerow>
	 *     &lt;treecell>...&lt;/treecell>
	 *   &lt;/treerow>
	 *   &lt;treechildren>
	 *     &lt;treeitem>...&lt;/treeitem>
	 *   &lt;/treechildren>
	 * &lt;/treeitem>
	 * </pre>
	 */
	private final class ContactTreeRenderer implements TreeitemRenderer<ContactTreeNode> {
		@Override
		public void render(final Treeitem treeItem, ContactTreeNode treeNode, int index) throws Exception {
			ContactTreeNode ctn = treeNode;
			Contact contact = (Contact) ctn.getData();
			Treerow dataRow = new Treerow();
			dataRow.setParent(treeItem);
			treeItem.setValue(ctn);
			treeItem.setOpen(ctn.isOpen());

			if (!isCategory(contact)) { // Contact Row
				Hlayout hl = new Hlayout();
				hl.appendChild(new Image("/widgets/tree/dynamic_tree/img/" + contact.getProfilepic()));
				hl.appendChild(new Label(contact.getName()));
				hl.setSclass("h-inline-block");
				Treecell treeCell = new Treecell();
				treeCell.appendChild(hl);
				dataRow.setDraggable("true");
				dataRow.appendChild(treeCell);
				dataRow.addEventListener(Events.ON_DOUBLE_CLICK, new EventListener<Event>() {
					@Override
					public void onEvent(Event event) throws Exception {
						ContactTreeNode clickedNodeValue = (ContactTreeNode) ((Treeitem) event.getTarget().getParent())
								.getValue();
						Window w = new Window("ZK IM - " + ((Contact) clickedNodeValue.getData()).getName(), "normal",
								true);
						w.setPosition("parent");
						w.setParent(demoWindow);
						HashMap<String, String> dataArgs = new HashMap<String, String>();
						dataArgs.put("name", clickedNodeValue.getData().getName());
						Executions.createComponents("/widgets/tree/dynamic_tree/dialog.zul", w, dataArgs);
						w.doOverlapped();
					}
				});
			} else { // Category Row
				dataRow.appendChild(new Treecell(contact.getCategory()));
			}
			// Both category row and contact row can be item dropped
			dataRow.setDroppable("true");
			dataRow.addEventListener(Events.ON_DROP, new EventListener<Event>() {
				@SuppressWarnings("unchecked")
				@Override
				public void onEvent(Event event) throws Exception {
					// The dragged target is a TreeRow belongs to an
					// Treechildren of TreeItem.
					Treeitem draggedItem = (Treeitem) ((DropEvent) event).getDragged().getParent();
					ContactTreeNode draggedValue = (ContactTreeNode) draggedItem.getValue();
					Treeitem parentItem = treeItem.getParentItem();
					contactTreeModel.remove(draggedValue);
					if (isCategory((Contact) ((ContactTreeNode) treeItem.getValue()).getData())) {
						contactTreeModel.add((ContactTreeNode) treeItem.getValue(),
								new DefaultTreeNode[] { draggedValue });
					} else {
						int index = parentItem.getTreechildren().getChildren().indexOf(treeItem);
						if(parentItem.getValue() instanceof ContactTreeNode) {
							contactTreeModel.insert((ContactTreeNode)parentItem.getValue(), index, index,
									new DefaultTreeNode[] { draggedValue });
						}
						
					}
				}
			});

		}

		private boolean isCategory(Contact contact) {
			return contact.getName() == null;
		}
	}
}
AdvancedTreeModel.java
package demo.tree.dynamic_tree;

import org.zkoss.zul.DefaultTreeModel;
import org.zkoss.zul.DefaultTreeNode;

import demo.data.pojo.Contact;

public class AdvancedTreeModel extends DefaultTreeModel<Contact> {
	private static final long serialVersionUID = -5513180500300189445L;
	
	DefaultTreeNode<Contact> _root;

	public AdvancedTreeModel(ContactTreeNode contactTreeNode) {
		super(contactTreeNode);
		_root = contactTreeNode;
	}

	/**
	 * remove the nodes which parent is <code>parent</code> with indexes
	 * <code>indexes</code>
	 * 
	 * @param parent
	 *            The parent of nodes are removed
	 * @param indexFrom
	 *            the lower index of the change range
	 * @param indexTo
	 *            the upper index of the change range
	 * @throws IndexOutOfBoundsException
	 *             - indexFrom < 0 or indexTo > number of parent's children
	 */
	public void remove(DefaultTreeNode<Contact> parent, int indexFrom, int indexTo) throws IndexOutOfBoundsException {
		DefaultTreeNode<Contact> stn = parent;
		for (int i = indexTo; i >= indexFrom; i--)
			try {
				stn.getChildren().remove(i);
			} catch (Exception exp) {
				exp.printStackTrace();
			}
	}

	public void remove(DefaultTreeNode<Contact> target) throws IndexOutOfBoundsException {
		int index = 0;
		DefaultTreeNode<Contact> parent = null;
		// find the parent and index of target
		parent = dfSearchParent(_root, target);
		for (index = 0; index < parent.getChildCount(); index++) {
			if (parent.getChildAt(index).equals(target)) {
				break;
			}
		}
		remove(parent, index, index);
	}

	/**
	 * insert new nodes which parent is <code>parent</code> with indexes
	 * <code>indexes</code> by new nodes <code>newNodes</code>
	 * 
	 * @param parent
	 *            The parent of nodes are inserted
	 * @param indexFrom
	 *            the lower index of the change range
	 * @param indexTo
	 *            the upper index of the change range
	 * @param newNodes
	 *            New nodes which are inserted
	 * @throws IndexOutOfBoundsException
	 *             - indexFrom < 0 or indexTo > number of parent's children
	 */
	public void insert(DefaultTreeNode<Contact> parent, int indexFrom, int indexTo, DefaultTreeNode<Contact>[] newNodes)
			throws IndexOutOfBoundsException {
		DefaultTreeNode<Contact> stn = parent;
		for (int i = indexFrom; i <= indexTo; i++) {
			try {
				stn.getChildren().add(i, newNodes[i - indexFrom]);
			} catch (Exception exp) {
				throw new IndexOutOfBoundsException("Out of bound: " + i + " while size=" + stn.getChildren().size());
			}
		}
	}

	/**
	 * append new nodes which parent is <code>parent</code> by new nodes
	 * <code>newNodes</code>
	 * 
	 * @param parent
	 *            The parent of nodes are appended
	 * @param newNodes
	 *            New nodes which are appended
	 */
	public void add(DefaultTreeNode<Contact> parent, DefaultTreeNode<Contact>[] newNodes) {
		DefaultTreeNode<Contact> stn = (DefaultTreeNode<Contact>) parent;

		for (int i = 0; i < newNodes.length; i++)
			stn.getChildren().add(newNodes[i]);

	}

	private DefaultTreeNode<Contact> dfSearchParent(DefaultTreeNode<Contact> node, DefaultTreeNode<Contact> target) {
		if (node.getChildren() != null && node.getChildren().contains(target)) {
			return node;
		} else {
			int size = getChildCount(node);
			for (int i = 0; i < size; i++) {
				DefaultTreeNode<Contact> parent = dfSearchParent((DefaultTreeNode<Contact>) getChild(node, i), target);
				if (parent != null) {
					return parent;
				}
			}
		}
		return null;
	}

}
ContactTreeNode.java
package demo.tree.dynamic_tree;

import org.zkoss.zul.DefaultTreeNode;

import demo.data.pojo.Contact;

public class ContactTreeNode extends DefaultTreeNode<Contact> {
	private static final long serialVersionUID = -7012663776755277499L;
	
	private boolean open = false;

	public ContactTreeNode(Contact data, DefaultTreeNode<Contact>[] children) {
		super(data, children);
	}

	public ContactTreeNode(Contact data, DefaultTreeNode<Contact>[] children, boolean open) {
		super(data, children);
		setOpen(open);
	}

	public ContactTreeNode(Contact data) {
		super(data);

	}

	public boolean isOpen() {
		return open;
	}

	public void setOpen(boolean open) {
		this.open = open;
	}

}
ContactList.java
package demo.data;

import demo.data.pojo.Contact;
import demo.tree.dynamic_tree.ContactTreeNode;

public class ContactList {
	public final static String Category = "Category";
	public final static String Contact = "Contact";
	
	private ContactTreeNode root;
	public ContactList() {
		root = new ContactTreeNode(null,
			new ContactTreeNode[] {
				new ContactTreeNode(new Contact("Friend"),new ContactTreeNode[] {
					new ContactTreeNode(new Contact("High School"), new ContactTreeNode[] {
						new ContactTreeNode(new Contact("Fernando Terrell", "Contact.png")),
						new ContactTreeNode(new Contact("Stanley Larson", "Contact.png"))
					},true),
					new ContactTreeNode(new Contact("University"), new ContactTreeNode[] {
						new ContactTreeNode(new Contact("Camryn Breanna", "Contact.png")),
						new ContactTreeNode(new Contact("Juliana Isabela","Contact-gu.png")),
						new ContactTreeNode(new Contact("Holden Craig", "Contact-g.png"))
					}),
					new ContactTreeNode(new Contact("Emma Jones", "Contact-i.png")),
					new ContactTreeNode(new Contact("Eric Franklin",  "Contact.png")),
					new ContactTreeNode(new Contact("Alfred Wong", "Contact.png")),
					new ContactTreeNode(new Contact("Miguel Soto",  "Contact.png"))
				},true),
				new ContactTreeNode(new Contact("Work"),new ContactTreeNode[] {
					new ContactTreeNode(new Contact("Andrew Willis",  "Contact.png")),
					new ContactTreeNode(new Contact("Russell Thomas",  "Contact-jq.png")),
					new ContactTreeNode(new Contact("Donovan Marcus",  "Contact.png"))
				})
			},true
		);
	}
	public ContactTreeNode getRoot() {
		return root;
	}
}
Contact.java
package demo.data.pojo;

public class Contact {
	private final String name;
	private final String category;

	public Contact(String category) {
		this.category = category;
		this.name = null;
		this.profilepic = null;
	}

	public Contact(String name, String profilepic) {
		this.name = name;
		this.profilepic = profilepic;
		this.category = null;
	}

	public String getName() {
		return name;
	}

	public String getCategory() {
		return category;
	}

	public String getProfilepic() {
		return profilepic;
	}

	private final String profilepic;
}