/* * 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.treegrid; import java.util.HashMap; import java.util.List; import java.util.Map; import com.extjs.gxt.ui.client.core.FastMap; import com.extjs.gxt.ui.client.core.XDOM; import com.extjs.gxt.ui.client.data.ModelData; import com.extjs.gxt.ui.client.data.ModelIconProvider; import com.extjs.gxt.ui.client.data.TreeLoader; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.GridEvent; import com.extjs.gxt.ui.client.event.TreeGridEvent; import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.store.Record; import com.extjs.gxt.ui.client.store.Store; import com.extjs.gxt.ui.client.store.StoreEvent; import com.extjs.gxt.ui.client.store.StoreListener; import com.extjs.gxt.ui.client.store.TreeStore; import com.extjs.gxt.ui.client.store.TreeStoreEvent; import com.extjs.gxt.ui.client.widget.grid.ColumnModel; import com.extjs.gxt.ui.client.widget.grid.Grid; import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel; import com.extjs.gxt.ui.client.widget.grid.GridView; import com.extjs.gxt.ui.client.widget.treepanel.TreeStyle; import com.extjs.gxt.ui.client.widget.treepanel.TreePanel.Joint; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.AbstractImagePrototype; /** * A hierarchical tree grid bound to a <code>TreeStore</code>. * * <p /> * A <code>TreeGridCellRenderer</code> can be assigned to the * <code>ColumnConfig</code> in which the tree will be displayed. * * @param <M> the model type */ public class TreeGrid<M extends ModelData> extends Grid<M> { public class TreeNode { protected M m; protected String id; protected Element joint, check, text; private boolean expanded = false; private boolean expand; private boolean leaf = true; private boolean childrenRendered; private boolean loaded; public TreeNode(String id, M m) { this.id = id; this.m = m; if (loader != null && !loaded) { leaf = !loader.hasChildren(m); } } public int getItemCount() { return treeStore.getChildCount(m); } public M getModel() { return m; } public TreeNode getParent() { M p = treeStore.getParent(m); return findNode(p); } public int indexOf(TreeNode child) { M c = child.getModel(); return store.indexOf(c); } public boolean isExpanded() { return expanded; } public boolean isLeaf() { return !hasChildren(m); } public void setExpanded(boolean expand) { TreeGrid.this.setExpanded(m, expand); } } protected Map<String, TreeNode> nodes = new FastMap<TreeNode>(); protected Map<M, String> cache = new HashMap<M, String>(); protected TreeStore<M> treeStore; protected TreeLoader<M> loader; protected TreeGridView treeGridView; private ModelIconProvider<M> iconProvider; private TreeStyle style = new TreeStyle(); private boolean autoLoad, filtering; private boolean caching = true; private StoreListener<M> storeListener = new StoreListener<M>() { @Override public void storeAdd(StoreEvent<M> se) { onAdd((TreeStoreEvent<M>) se); } @Override public void storeClear(StoreEvent<M> se) { onDataChanged((TreeStoreEvent<M>) se); } @Override public void storeDataChanged(StoreEvent<M> se) { onDataChanged((TreeStoreEvent<M>) se); } @Override public void storeFilter(StoreEvent<M> se) { onFilter((TreeStoreEvent<M>) se); } @Override public void storeRemove(StoreEvent<M> se) { onRemove((TreeStoreEvent<M>) se); } @Override public void storeUpdate(StoreEvent<M> se) { onUpdate((TreeStoreEvent<M>) se); } }; private ListStore<M> listStore = new ListStore<M>() { public Record getRecord(M model) { return treeStore.getRecord(model); }; public boolean hasRecord(M model) { return treeStore.hasRecord(model); }; }; @SuppressWarnings("unchecked") public TreeGrid(TreeStore store, ColumnModel cm) { this.store = listStore; this.cm = cm; this.view = new GridView(); focusable = true; baseStyle = "x-grid-panel"; setSelectionModel(new GridSelectionModel<M>()); this.treeStore = store; this.loader = treeStore.getLoader(); addStyleName("x-treegrid"); treeStore.addStoreListener(storeListener); treeGridView = new TreeGridView(); setView(treeGridView); setSelectionModel(new TreeGridSelectionModel<M>()); } /** * Returns the tree style. * * @return the tree style */ public TreeStyle getStyle() { return style; } /** * Returns the tree's tree store. * * @return the tree store */ public TreeStore<M> getTreeStore() { return treeStore; } /** * Returns the tree's view. * * @return the view */ public TreeGridView getTreeView() { return treeGridView; } /** * Returns true if the model is expanded. * * @param model the model * @return true if expanded */ public boolean isExpanded(M model) { TreeNode node = findNode(model); return node.expanded; } /** * Returns true if the model is a leaf node. The leaf state allows a tree item * to specify if it has children before the children have been realized. * * @param model the model * @return the leaf state */ public boolean isLeaf(M model) { TreeNode node = findNode(model); return node.isLeaf(); } /** * Sets the item's expand state. * * @param model the model * @param expand true to expand */ public void setExpanded(M model, boolean expand) { setExpanded(model, expand, false); } /** * Sets the item's expand state. * * @param model the model * @param expand true to expand * @param deep true to expand all children recursively */ public void setExpanded(M model, boolean expand, boolean deep) { TreeNode node = findNode(model); if (node != null) { TreeGridEvent<M> tge = new TreeGridEvent<M>(this); tge.setModel(model); if (expand) { if (!node.isLeaf()) { // if we have a loader and node is not loaded make // load request and exit method if (loader != null && (!node.loaded || !caching) && !filtering) { treeStore.removeAll(model); node.expand = true; loader.loadChildren(model); return; } if (!node.expanded && fireEvent(Events.BeforeExpand, tge)) { node.expanded = true; if (!node.childrenRendered) { renderChildren(model); node.childrenRendered = true; } // expand treeGridView.expand(node); M parent = treeStore.getParent(model); while (parent != null) { TreeNode pnode = findNode(parent); if (!pnode.expanded) { setExpanded(pnode.m, true); } parent = treeStore.getParent(parent); } fireEvent(Events.Expand, tge); } } if (deep) { setExpandChildren(model, true); } } else { if (node.expanded && fireEvent(Events.BeforeCollapse, tge)) { node.expanded = false; // collapse treeGridView.collapse(node); fireEvent(Events.Collapse, tge); } if (deep) { setExpandChildren(model, false); } } } } /** * Toggles the model's expand state. * * @param model the model */ public void toggle(M model) { TreeNode node = findNode(model); if (node != null) { setExpanded(model, !node.expanded); } } @SuppressWarnings("unchecked") @Override protected ComponentEvent createComponentEvent(Event event) { return new TreeGridEvent(this, event); } protected TreeNode findNode(ModelData model) { if (model == null) return null; return nodes.get(cache.get(model)); } protected boolean hasChildren(M model) { TreeNode node = findNode(model); if (loader != null && !node.loaded) { return loader.hasChildren(model); } if (!node.leaf || treeStore.getChildCount(model) > 0) { return true; } return false; } protected void onAdd(TreeStoreEvent<M> se) { } @Override protected void onClick(GridEvent<M> e) { M m = e.getModel(); if (m != null) { TreeNode node = findNode(m); if (node != null) { if (e.within(treeGridView.getJointElement(node))) { toggle(m); } else { super.onClick(e); } } } } protected void onDataChanged(TreeStoreEvent<M> se) { if (!isRendered()) { return; } M p = se.getParent(); if (p == null) { store.removeAll(); nodes.clear(); renderChildren(null); } else { TreeNode n = findNode(p); n.loaded = true; if (n.childrenRendered) { // n.container.setInnerHTML(""); } renderChildren(p); if (n.expand && !n.isLeaf()) { n.expand = false; setExpanded(p, true); } } } @Override protected void onDoubleClick(GridEvent<M> e) { super.onDoubleClick(e); M m = e.getModel(); if (m != null) { TreeNode node = findNode(m); setExpanded(node.m, !node.expanded); } } protected void onFilter(TreeStoreEvent<M> se) { } protected void onRemove(TreeStoreEvent<M> se) { unregister(se.getModel()); } @Override protected void onRender(Element target, int index) { super.onRender(target, index); el().setTabIndex(0); el().setElementAttribute("hideFocus", "true"); if (treeStore.getRootItems().size() == 0 && loader != null) { loader.load(); } else { renderChildren(null); } sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS); } protected void onUpdate(TreeStoreEvent<M> se) { store.fireEvent(Store.Update, se); } protected void refresh(M model) { TreeNode node = findNode(model); if (node != null) { AbstractImagePrototype style = calculateIconStyle(model); treeGridView.onIconStyleChange(findNode(model), style); Joint j = calcualteJoint(model); treeGridView.onJointChange(node, j); } } protected String register(M m) { String id = XDOM.getUniqueId(); if (cache.get(m) != null) { id = cache.get(m); } if (!nodes.containsKey(id)) { cache.put(m, id); nodes.put(id, new TreeNode(id, m)); } return id; } protected void renderChildren(M parent) { List<M> children = parent == null ? treeStore.getRootItems() : treeStore.getChildren(parent); for (M child : children) { register(child); } if (parent == null) { store.add(children); } for (M child : children) { if (loader != null) { if (autoLoad) { if (store.isFiltered()) { renderChildren(child); } else { loader.loadChildren(child); } } } } } protected void unregister(M m) { if (m != null) { nodes.remove(cache.get(m)); cache.remove(m); } } Joint calcualteJoint(ModelData model) { if (model == null) { return Joint.NONE; } TreeNode node = findNode(model); Joint joint = Joint.NONE; if (!node.isLeaf()) { boolean children = true; if (node.isExpanded()) { joint = children ? Joint.EXPANDED : Joint.NONE; } else { joint = children ? Joint.COLLAPSED : Joint.NONE; } } return joint; } @SuppressWarnings("unchecked") AbstractImagePrototype calculateIconStyle(ModelData model) { AbstractImagePrototype style = null; if (iconProvider != null) { AbstractImagePrototype iconStyle = iconProvider.getIcon((M) model); if (iconStyle != null) { return iconStyle; } } TreeNode node = findNode(model); TreeStyle ts = getStyle(); if (!node.isLeaf()) { boolean ex = isExpanded((M) model); if (ex && ts.getNodeOpenIcon() != null) { style = ts.getNodeOpenIcon(); } else if (ex && ts.getNodeOpenIcon() != null) { style = ts.getNodeCloseIcon(); } else if (!ex) { style = ts.getNodeCloseIcon(); } } else { style = ts.getLeafIcon(); } return style; } private void setExpandChildren(M m, boolean expand) { for (M child : treeStore.getChildren(m)) { setExpanded(child, expand, true); } } }