/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.widget.tree; import java.util.ArrayList; import java.util.List; import com.extjs.gxt.ui.client.PartFactory; import com.extjs.gxt.ui.client.core.XDOM; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.EventType; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.TreeEvent; import com.extjs.gxt.ui.client.widget.Component; import com.extjs.gxt.ui.client.widget.tree.Tree.Joint; import com.extjs.gxt.ui.client.widget.treepanel.TreePanel; import com.google.gwt.dom.client.NodeList; import com.google.gwt.user.client.Element; /** * A item in a <code>Tree</code>. All events are bubbled to the item's parent * tree. * * <dl> * <dt><b>Events:</b></dt> * * <dd><b>BeforeAdd</b> : TreeEvent(tree, parent, item, index)<br> * <div>Fires before a item is added or inserted. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>tree : the source tree</li> * <li>parent : this</li> * <li>item : the item being added</li> * <li>index : the index at which the item will be added</li> * </ul> * </dd> * * <dd><b>BeforeRemove</b> : TreeEvent(tree, parent, item)<br> * <div>Fires before a item is removed. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>tree : the source tree</li> * <li>parent : this</li> * <li>item : the item being removed</li> * </ul> * </dd> * * <dd><b>BeforeExpand</b> : TreeEvent(tree, item)<br> * <div>Fires before a item is expanded. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>tree : the source tree</li> * <li>item : this</li> * </ul> * </dd> * * <dd><b>BeforeCollapse</b> : TreeEvent(tree, item)<br> * <div>Fires before a item is collapsed. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>tree : the source tree</li> * <li>item : this</li> * </ul> * </dd> * * <dd><b>Add</b> : TreeEvent(tree, parent, item, index)<br> * <div>Fires after a item has been added or inserted.</div> * <ul> * <li>tree : the source tree</li> * <li>parent : this</li> * <li>item : the item that was added</li> * <li>index : the index at which the item will be added</li> * </ul> * </dd> * * <dd><b>Remove</b> : TreeEvent(tree, parent, item)<br> * <div>Fires after a item has been removed.</div> * <ul> * <li>tree : the source tree</li> * <li>parent : this</li> * <li>item : the item being removed</li> * </ul> * </dd> * * <dd><b>Expand</b> : TreeEvent(tree, item)<br> * <div>Fires after a item has been expanded.</div> * <ul> * <li>tree : the source tree</li> * <li>item : this</li> * </ul> * </dd> * * <dd><b>Collapse</b> : TreeEvent(tree, item)<br> * <div>Fires after a item is collapsed.</div> * <ul> * <li>tree : the source tree</li> * <li>item : this</li> * </ul> * </dd> * * <dd><b>CheckChange</b> : TreeEvent(tree, item)<br> * <div>Fires after a check state change.</div> * <ul> * <li>tree : the source tree</li> * <li>item : this</li> * </ul> * </dd> * * <dt><b>CSS:</b></dt> * <dd>.my-tree-item (the item itself)</dd> * <dd>.my-tree-item-text span (the tree item text)</dd> * </dl> * * @deprecated see {@link TreePanel} */ public class TreeItem extends Component { protected boolean childrenRendered; protected boolean root, expanded, checked; protected Tree tree; protected TreeItemUI ui; TreeItem parentItem; String text, iconStyle, itemStyleName; private List<TreeItem> children; private boolean expandOnRender; private boolean leaf = true; private String textStyle; /** * Creates a new tree item. */ public TreeItem() { children = new ArrayList<TreeItem>(); } /** * Creates a new tree item. * * @param text the item's text */ public TreeItem(String text) { this(); setText(text); } /** * Adds a child item. * * @param item the item to be added */ public void add(TreeItem item) { add(item, getItemCount()); } public int getIndent() { return (getDepth()) * tree.getIndentWidth(); } @Override public boolean equals(Object obj) { if (obj instanceof TreeItem) { return getId().equals(((TreeItem)obj).getId()); } return false; } /** * Inserts a child item at the specified position. * * @param item the item to be added * @param index index at which the specified element is to be inserted */ public void add(TreeItem item, int index) { TreeEvent te = new TreeEvent(tree); te.setParent(this); te.setItem(item); te.setIndex(index); if (fireEvent(Events.BeforeAdd, te)) { if (item.parentItem != null && item.parentItem != this) { item.parentItem.remove(item); } else if (item.parentItem != null && item.parentItem == parentItem) { children.remove(item); if (childrenRendered) { item.el().removeFromParent(); } } item.parentItem = this; item.setTree(tree); tree.registerItem(item); children.add(index, item); leaf = false; if (childrenRendered) { Element target = root ? getContainer() : ui.getContainerElement(); if (item.isRendered()) { fly(target).insertChild(item.getElement(), index); item.getUI().onIndentChange(getDepth()); List<TreeItem> list = new ArrayList<TreeItem>(); getAllChildren(list, item); for (TreeItem ti : list) { tree.registerItem(ti); } } else { item.render(target, index); } } if (rendered && !root && children.size() == 1) { fly(ui.getContainerElement()).setVisible(true); updateJointStyle(); ui.onIconStyleChange(getIconStyle()); } fireEvent(Events.Add, te); } } /** * Returns the item's first child. * * @return the first child or <code>null</code> */ public TreeItem firstChild() { for (TreeItem child : getItems()) { if (child.isVisible()) return child; } return null; } /** * Returns the item's container element. * * @return the container */ public Element getContainer() { return root ? getElement() : ui.getContainerElement(); } /** * Returns the item's node depth. * * @return the depth */ public int getDepth() { int depth = 0; TreeItem p = getParentItem(); while (p != null) { depth++; p = p.getParentItem(); } return depth; } /** * Returns the item's icon style. * * @return the icon style */ public String getIconStyle() { return iconStyle; } /** * Returns the item at the specified index. * * @param index the index * @return the item at the index */ public TreeItem getItem(int index) { if ((index < 0) || (index >= getItemCount())) return null; return children.get(index); } /** * Returns the number of child items. * * @return the child count */ public int getItemCount() { return children.size(); } /** * Returns the item's children. * * @return the children items */ public List<TreeItem> getItems() { return new ArrayList<TreeItem>(children); } /** * Returns the item's children. * * @param deep true to retrieve all the item's children * @return the children items */ @SuppressWarnings("unchecked") public List<TreeItem> getItems(boolean deep) { if (deep) { List list = new ArrayList(); getAllChildren(list, this); return list; } else { return getItems(); } } /** * Returns the item's style name. * * @return the style name */ public String getItemStyleName() { return itemStyleName; } /** * Returns the item's parent. * * @return the parent item */ public TreeItem getParentItem() { return parentItem; } /** * Returns the path for this node. The path can be used to expand or select * this node programmatically. * * @return a comma separated list of tree item id's */ public String getPath() { StringBuffer sb = new StringBuffer(); TreeItem p = this; while (p != null) { String id = p.getId(); sb.insert(0, "," + id); p = p.getParentItem(); } return sb.toString().substring(1); } /** * Returns the item's text. * * @return the text */ public String getText() { return text; } /** * Returns the item's text style. * * @return the text style */ public String getTextStyle() { return textStyle; } /** * Returns the item's ui instance. * * @return the ui instance */ public TreeItemUI getUI() { return ui; } /** * Returns <code>true</code> if the item's has children. * * @return the children state */ public boolean hasChildren() { return getItemCount() > 0; } /** * Returns the index of the item or -1 if not found. * * @param item the child item * @return the item's index */ public int indexOf(TreeItem item) { return children.indexOf(item); } /** * Returns <code>true</code> if the item is checked. * * @return the checked state */ public boolean isChecked() { return checked; } /** * Returns <code>true</code> if the item is expanded, and <code>false</code> * otherwise. * * @return the expanded state */ public boolean isExpanded() { return expanded; } /** * Returns <code>true</code> if the item is a leaf, and <code>false</code> * otherwise. The leaf state allows a tree item to specify if it has children * before the children have been realized. * * @return the leaf state */ public boolean isLeaf() { return leaf; } /** * Returns <code>true</code> if the item is a root item. * * @return the root state */ public boolean isRoot() { return root; } /** * Returns the item's last child. * * @return the last child */ public TreeItem lastChild() { for (int i = getItemCount() - 1; i >= 0; i--) { TreeItem child = getItem(i); if (child.isVisible()) return child; } return null; } /** * Returns the item next sibling. * * @return the next sibling */ public TreeItem nextSibling() { if (parentItem == null) return null; int index = parentItem.indexOf(this); for (int i = index + 1; i < parentItem.getItemCount(); i++) { if (parentItem.getItem(i).isVisible()) return parentItem.getItem(i); } return null; } public void onComponentEvent(ComponentEvent ce) { // delegate event handling to ui if (ui != null) { ui.handleEvent((TreeEvent) ce); } } /** * Returns the item's previous sibling. * * @return the previous sibling */ public TreeItem previousSibling() { if (parentItem == null) return null; int index = parentItem.indexOf(this); for (int i = index - 1; i >= 0; i--) { if (parentItem.getItem(i).isVisible()) return parentItem.getItem(i); } return null; } /** * Removes a child from the item. * * @param item the item to be removed */ public void remove(TreeItem item) { if (!children.contains(item)) { return; } TreeEvent te = new TreeEvent(tree); te.setParent(this); te.setItem(item); if (fireEvent(Events.BeforeRemove, te)) { children.remove(item); tree.unregisterItem(item); item.tree = null; item.parentItem = null; if (rendered && item.rendered) { if (root) { item.el().removeFromParent(); } else { ui.onRemoveChild(item); } } fireEvent(Events.Remove, te); } } /** * Removes all child items. */ public void removeAll() { int count = getItemCount(); for (int i = 0; i < count; i++) { remove(getItem(0)); } leaf = true; } /** * Sets the item's checked value. * * @param checked <code>true</code> to check */ public void setChecked(boolean checked) { this.checked = checked; if (rendered) { if (fireEvent(Events.BeforeCheckChange, new TreeEvent(tree, this))) { ui.onCheckChange(checked); if (checked) { switch (tree.getCheckStyle()) { case PARENTS: TreeItem p = getParentItem(); while (p != null && !p.root) { p.setChecked(true); p = p.getParentItem(); } break; case CHILDREN: for (int i = 0; i < getItemCount(); i++) { getItem(i).setChecked(true); } break; } } else { switch (tree.getCheckStyle()) { case PARENTS: clearCheckChildren(this); break; case CHILDREN: for (int i = 0; i < getItemCount(); i++) { getItem(i).setChecked(false); } break; } } } } } @Override public void setElement(Element elem) { super.setElement(elem); if (!root) { getUI().afterRender(); rendered = true; if (expandOnRender) { boolean anim = tree.getAnimate(); tree.setAnimate(false); setExpanded(true); tree.setAnimate(anim); } } } public void setTreeTableElement(Element elem) { super.setElement(elem); } /** * Sets the item's expanded state. * * @param expanded <code>true</code> to expand */ public void setExpanded(boolean expanded) { setExpanded(expanded, false); } /** * Sets the item's expand state. * * @param expanded <code>true</code> to expand * @param deep <code>true</code> to expand all children */ public void setExpanded(boolean expanded, boolean deep) { if (!rendered) { expandOnRender = expanded; return; } if (expanded && root) { this.expanded = false; } else if (!expanded && root) { this.expanded = true; } TreeEvent te = new TreeEvent(tree, this); if (expanded) { if (!this.expanded && !isLeaf()) { if (fireEvent(Events.BeforeExpand, te)) { this.expanded = true; if (!childrenRendered) { renderChildren(); } ui.expand(); TreeItem p = parentItem; while (p != null && !p.root) { if (p.expanded == false) { p.setExpanded(true); } p = p.parentItem; } } if (deep) { expandChildren(deep); } } else { if (deep) { expandChildren(deep); } } } else if (this.expanded && !expanded) { if (fireEvent(Events.BeforeCollapse, te)) { this.expanded = false; ui.collapse(); } if (deep) { for (int i = 0; i < getItemCount(); i++) { TreeItem item = getItem(i); item.setExpanded(false, true); } } } } /** * Sets the item's icon style. The style name should match a CSS style that * specifies a background image using the following format: * * <pre><code> * .my-icon { * background: url(images/icons/my-icon.png) no-repeat center left !important; * } * </code></pre> * * @param style the icon style */ public void setIconStyle(String style) { this.iconStyle = style; if (rendered) { updateIconStyle(); } } /** * Sets a style name that will be added to the tree item's element, not the * container element. * * @param itemStyleName the style name */ public void setItemStyleName(String itemStyleName) { this.itemStyleName = itemStyleName; } /** * Sets the item's leaf state. The leaf state allows a tree item to specify if * it has children before the children have been realized. * * @param leaf the state */ public void setLeaf(boolean leaf) { this.leaf = leaf; } /** * Sets the item's text. * * @param text the new text */ public void setText(String text) { this.text = text; if (rendered) { ui.onTextChange(text); } } /** * Sets the item's text style. * * @param style the text style */ public void setTextStyle(String style) { this.textStyle = style; if (rendered) { getUI().onTextStyleChange(textStyle); } } public void setUI(TreeItemUI ui) { this.ui = ui; } /** * Toggles the item's expand state. */ public void toggle() { setExpanded(!isExpanded()); } public String toString() { return "tree: " + (text != null ? text : "" + " ") + el(); } public void updateIconStyle() { String style = calculateIconStyle(); getUI().onIconStyleChange(style); } public void updateJointStyle() { getUI().onJointChange(calculateJoint()); Element checkEl = ui.getCheckElement(); if (tree.getCheckable()) { switch (tree.getCheckNodes()) { case BOTH: fly(checkEl).setVisible(true); break; case PARENT: fly(checkEl).setVisible(!isLeaf()); break; case LEAF: fly(checkEl).setVisible(isLeaf()); break; } } } protected Joint calculateJoint() { if (root) { return Joint.NONE; } Joint joint = Joint.NONE; if (!isLeaf()) { boolean children = false; boolean binder = tree != null ? tree.getData("binder") != null : false; boolean loaded = getData("loaded") != null; if ((!binder) || (binder && !loaded) || (binder && hasChildren())) { children = true; } if (isExpanded()) { joint = children ? Joint.EXPANDED : Joint.NONE; } else { joint = children ? Joint.COLLAPSED : Joint.NONE; } } else { joint = Joint.NONE; } return joint; } protected String calculateIconStyle() { String style = null; if (iconStyle != null) { style = iconStyle; } else { TreeStyle ts = tree.getStyle(); if (!isLeaf()) { if (isExpanded() && ts.getNodeOpenIconStyle() != null) { style = ts.getNodeOpenIconStyle(); } else if (isExpanded() && ts.getNodeOpenIconStyle() != null) { style = ts.getNodeCloseIconStyle(); } else if (!isExpanded()) { style = ts.getNodeCloseIconStyle(); } } else { style = ts.getLeafIconStyle(); } } return style; } protected boolean fireEvent(EventType type, TreeEvent te) { boolean result = super.fireEvent(type, te); if (tree != null && result) { return tree.fireEvent(type, te); } return result; } protected TreeItemUI getTreeItemUI() { return ui; } protected void onRender(Element target, int index) { String s = getUI().getTemplate(getId(), getText(), calculateIconStyle(), 0, getDepth()); setElement(XDOM.create(s), target, index); if (textStyle != null) { ui.onTextStyleChange(textStyle); } if (expandOnRender) { boolean anim = tree.getAnimate(); tree.setAnimate(false); setExpanded(true); tree.setAnimate(anim); } } protected void renderChildren() { int count = getItemCount(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < count; i++) { TreeItem child = getItem(i); Joint joint = child.calculateJoint(); sb.append(child.getUI().getTemplate(child.getId(), child.getText(), child.calculateIconStyle(), joint.value(), getDepth())); } getContainer().setInnerHTML(sb.toString()); NodeList<Element> elems = getContainer().getChildNodes().cast(); for (int i = 0, len = elems.getLength(); i < len; i++) { TreeItem child = getItem(i); child.setElement(elems.getItem(i)); } childrenRendered = true; } protected void setChildrenRendered(boolean rendered) { childrenRendered = rendered; } protected void setRoot(boolean isRoot) { root = isRoot; } protected void setTree(Tree tree) { this.tree = tree; ui = PartFactory.createPart(tree.getTreeItemPartId()); ui.bind(this); } boolean isFirst() { if (isRoot()) return true; return this == parentItem.getItem(0); } boolean isLast() { if (isRoot()) return true; return this == parentItem.getItem(parentItem.getItemCount() - 1); } private void clearCheckChildren(TreeItem parent) { for (int i = 0; i < parent.getItemCount(); i++) { TreeItem sub = parent.getItem(i); sub.setChecked(false); clearCheckChildren(sub); } } private void expandChildren(boolean deep) { for (int i = 0; i < getItemCount(); i++) { TreeItem item = getItem(i); item.setExpanded(true, deep); } } private void getAllChildren(List<TreeItem> list, TreeItem parent) { list.add(parent); for (TreeItem child : parent.getItems()) { list.add(child); getAllChildren(list, child); } } }