Java tutorial
/** * Sencha GXT 4.0.0 - Sencha for GWT * Copyright (c) 2006-2015, Sencha Inc. * * licensing@sencha.com * http://www.sencha.com/products/gxt/license/ * * ================================================================================ * Open Source License * ================================================================================ * This version of Sencha GXT is licensed under the terms of the Open Source GPL v3 * license. You may use this license only if you are prepared to distribute and * share the source code of your application under the GPL v3 license: * http://www.gnu.org/licenses/gpl.html * * If you are NOT prepared to distribute and share the source code of your * application under the GPL v3 license, other commercial and oem licenses * are available for an alternate download of Sencha GXT. * * Please see the Sencha GXT Licensing page at: * http://www.sencha.com/products/gxt/license/ * * For clarification or additional options, please contact: * licensing@sencha.com * ================================================================================ * * * ================================================================================ * Disclaimer * ================================================================================ * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY, * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING. * ================================================================================ */ package com.sencha.gxt.widget.core.client.treegrid; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.google.gwt.cell.client.Cell; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.client.Event; import com.sencha.gxt.core.client.ValueProvider; import com.sencha.gxt.core.client.dom.XDOM; import com.sencha.gxt.core.client.dom.XElement; import com.sencha.gxt.core.client.gestures.LongPressOrTapGestureRecognizer; import com.sencha.gxt.core.client.gestures.TouchData; import com.sencha.gxt.core.shared.FastMap; import com.sencha.gxt.data.shared.IconProvider; import com.sencha.gxt.data.shared.ListStore; import com.sencha.gxt.data.shared.ModelKeyProvider; import com.sencha.gxt.data.shared.PropertyAccess; import com.sencha.gxt.data.shared.TreeStore; import com.sencha.gxt.data.shared.event.StoreAddEvent; import com.sencha.gxt.data.shared.event.StoreClearEvent; import com.sencha.gxt.data.shared.event.StoreDataChangeEvent; import com.sencha.gxt.data.shared.event.StoreFilterEvent; import com.sencha.gxt.data.shared.event.StoreHandlers; import com.sencha.gxt.data.shared.event.StoreRecordChangeEvent; import com.sencha.gxt.data.shared.event.StoreRemoveEvent; import com.sencha.gxt.data.shared.event.StoreSortEvent; import com.sencha.gxt.data.shared.event.StoreUpdateEvent; import com.sencha.gxt.data.shared.event.TreeStoreRemoveEvent; import com.sencha.gxt.data.shared.loader.TreeLoader; import com.sencha.gxt.messages.client.DefaultMessages; import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent; import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent.BeforeCollapseItemHandler; import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent.HasBeforeCollapseItemHandlers; import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent; import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent.BeforeExpandItemHandler; import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent.HasBeforeExpandItemHandlers; import com.sencha.gxt.widget.core.client.event.CollapseItemEvent; import com.sencha.gxt.widget.core.client.event.CollapseItemEvent.CollapseItemHandler; import com.sencha.gxt.widget.core.client.event.CollapseItemEvent.HasCollapseItemHandlers; import com.sencha.gxt.widget.core.client.event.ExpandItemEvent; import com.sencha.gxt.widget.core.client.event.ExpandItemEvent.ExpandItemHandler; import com.sencha.gxt.widget.core.client.event.ExpandItemEvent.HasExpandItemHandlers; import com.sencha.gxt.widget.core.client.grid.ColumnConfig; import com.sencha.gxt.widget.core.client.grid.ColumnModel; import com.sencha.gxt.widget.core.client.grid.Grid; import com.sencha.gxt.widget.core.client.grid.GridView; import com.sencha.gxt.widget.core.client.grid.GridView.GridAppearance; import com.sencha.gxt.widget.core.client.tree.Tree.Joint; import com.sencha.gxt.widget.core.client.tree.Tree.TreeAppearance; import com.sencha.gxt.widget.core.client.tree.Tree.TreeNode; import com.sencha.gxt.widget.core.client.tree.TreeStyle; /** * A {@link TreeGrid} provides support for displaying and editing hierarchical data where each item may contain multiple * properties. The tree grid gets its data from a {@link TreeStore} and its column definitions from a * {@link ColumnModel}. Each model in the store is rendered as an item in the tree. The fields in the model provide the * data for each column associated with the item. Any updates to the store are automatically pushed to the tree grid. * This includes inserting, removing, sorting and filter. * <p/> * In GXT version 3, {@link ModelKeyProvider}s and {@link ValueProvider}s provide the interface between your data model * and the list store and {@link ColumnConfig} classes. This enables a tree grid to work with data of any object type. * <p/> * You can provide your own implementation of these interfaces, or you can use a Sencha supplied generator to create * them for you automatically. A generator runs at compile time to create a Java class that is compiled to JavaScript. * The Sencha supplied generator can create classes for interfaces that extend the {@link PropertyAccess} interface. The * generator transparently creates the class at compile time and the {@link GWT#create(Class)} method returns an * instance of that class at run time. The generated class is managed by GWT and GXT and you generally do not need to * worry about what the class is called, where it is located, or other similar details. * <p/> * Each tree grid has a {@link GridView}. The grid view provides many options for customizing the grid's appearance * (e.g. striping, mouse-over tracking, empty text). To set these options, get the current grid view using * {@link TreeGrid#getView()} and then set the desired option on the grid view. * <p/> * To customize the appearance of a column in a tree grid, provide a cell implementation using * {@link ColumnConfig#setCell(Cell)}. * <p/> * Tree grids support several ways to manage column widths: * <ol> * <li>The most basic approach is to simply give pixel widths to each column. Columns widths will match the specified * values.</li> * <li>A column can be identified as an auto-expand column. As the width of the tree grid changes, or columns are * resized, the specified column's width is adjusted so that the column fills the available width with no horizontal * scrolling. See @link {@link GridView#setAutoExpandColumn(ColumnConfig)}.</li> * <li>The tree grid can resize columns based on relative weights, determined by the pixel width assigned to each * column. As the width of the tree grid or columns change, the weight is used to allocate the available space. Use * {@link GridView#setAutoFill(boolean)} or {@link GridView#setForceFit(boolean)} to enable this feature:</li> * <ul> * <li>With auto fill, the calculations are run when the tree grid is created (or reconfigured). After the tree grid is * rendered, the column widths will not be adjusted when the available width changes.</li> * <li>With force fit the width calculations are run every time there are changes to the available width or column * sizes.</li> * </ul> * <li>To prevent a column from participating in auto fill or force fit, use {@link ColumnConfig#setFixed(boolean)}.</li> * </ol> * </p> * The following code snippet illustrates the creation of a simple tree grid with local data for test purposes. For more * practical examples that show how to load data from remote sources, see the Async TreeGrid example in the online * Explorer demo.</p> * <p/> * * <pre>{@code * // Generate the key provider and value provider for the Data class * DataProperties dp = GWT.create(DataProperties.class); * <p/> * // Create the configurations for each column in the tree grid * List<ColumnConfig<Data, ?>> ccs = new LinkedList<ColumnConfig<Data, ?>>(); * ccs.add(new ColumnConfig<Data, String>(dp.name(), 200, "Name")); * ccs.add(new ColumnConfig<Data, String>(dp.value(), 200, "Value")); * ColumnModel<Data> cm = new ColumnModel<Test.Data>(ccs); * <p/> * // Create the store that the contains the data to display in the tree grid * TreeStore<Data> s = new TreeStore<Test.Data>(dp.key()); * <p/> * Data r1 = new Data("Parent 1", "value1"); * s.add(r1); * s.add(r1, new Data("Child 1.1", "value2")); * s.add(r1, new Data("Child 1.2", "value3")); * <p/> * Data r2 = new Data("Parent 2", "value4"); * s.add(r2); * s.add(r2, new Data("Child 2.1", "value5")); * s.add(r2, new Data("Child 2.2", "value6")); * <p/> * // Create the tree grid using the store, column model and column config for the tree column * TreeGrid<Data> tg = new TreeGrid<Data>(s, cm, ccs.get(0)); * <p/> * // Add the tree to a container * RootPanel.get().add(tg); * }</pre> * <p/> * To use the Sencha supplied generator to create model key providers and value providers, extend the * <code>PropertyAccess</code> interface, parameterized with the type of data you want to access (as shown below) and * invoke the <code>GWT.create</code> method on its <code>class</code> member (as shown in the code snippet above). This * creates an instance of the class that can be used to initialize the tree and tree store. In the following code * snippet we define a new interface called <code>DataProperties</code> that extends the <code>PropertyAccess</code> * interface and is parameterized with <code>Data</code>, a Plain Old Java Object (POJO). * <p/> * <p/> * * <pre> * public interface DataProperties extends PropertyAccess<Data> { * @Path("name") * ModelKeyProvider<Data> key(); * ValueProvider<Data, String> name(); * ValueProvider<Data, String> value(); * } * * public class Data { * private String name; * private String value; * * public Data(String name, String value) { * super(); * this.name = name; * this.value = value; * } * public String getName() { * return name; * } * public String getValue() { * return value; * } * public void setName(String name) { * this.name = name; * } * public void setValue(String value) { * this.value = value; * } * } * </pre> * <p/> * To enable drag and drop for a tree grid, add the following: * <p/> * <p/> * * <pre> * new TreeGridDragSource<Data>(tg); * TreeGridDropTarget<Data> dt = new TreeGridDropTarget<Data>(tg); * dt.setFeedback(Feedback.BOTH); * </pre> * <p/> * To add reordering support to the drag and drop, include: * <p/> * * <pre> * dt.setAllowSelfAsSource(true); * </pre> * <p/> * * @param <M> the model type */ public class TreeGrid<M> extends Grid<M> implements HasBeforeCollapseItemHandlers<M>, HasCollapseItemHandlers<M>, HasBeforeExpandItemHandlers<M>, HasExpandItemHandlers<M> { public static class TreeGridNode<M> extends TreeNode<M> { protected TreeGridNode(String modelId, M m, String domId) { super(modelId, m, domId); } @Override public void clearElements() { super.clearElements(); setContainerElement(null); setElContainer(null); element = null; } } protected boolean filtering; protected TreeLoader<M> loader; protected Map<String, TreeNode<M>> nodes = new FastMap<TreeNode<M>>(); protected Map<String, TreeNode<M>> nodesByDomId = new FastMap<TreeNode<M>>(); protected StoreHandlers<M> storeHandler = new StoreHandlers<M>() { @Override public void onAdd(StoreAddEvent<M> event) { TreeGrid.this.onAdd(event); } @Override public void onClear(StoreClearEvent<M> event) { TreeGrid.this.onClear(event); } @Override public void onDataChange(StoreDataChangeEvent<M> event) { TreeGrid.this.onDataChange(event.getParent()); } @Override public void onFilter(StoreFilterEvent<M> event) { TreeGrid.this.onFilter(event); } @Override public void onRecordChange(StoreRecordChangeEvent<M> event) { TreeGrid.this.onRecordChange(event); } @Override public void onRemove(StoreRemoveEvent<M> event) { TreeGrid.this.onRemove((TreeStoreRemoveEvent<M>) event); } @Override public void onSort(StoreSortEvent<M> event) { TreeGrid.this.onSort(event); } @Override public void onUpdate(StoreUpdateEvent<M> event) { TreeGrid.this.onUpdate(event); } }; protected HandlerRegistration storeHandlerRegistration; protected TreeGridView<M> treeGridView; protected TreeStore<M> treeStore; private final GridAppearance appearance; private boolean autoLoad, autoExpand; private boolean caching = true; private boolean expandOnFilter = true; private IconProvider<M> iconProvider; private TreeStyle style = new TreeStyle(); private TreeAppearance treeAppearance; private ColumnConfig<M, ?> treeColumn; private boolean expandOnDoubleClick = true; /** * Creates a new tree grid. * * @param store the tree store * @param cm the column model * @param treeColumn the tree column */ public TreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn) { this(store, cm, treeColumn, GWT.<GridAppearance>create(GridAppearance.class)); } /** * Creates a new tree grid. * * @param store the tree store * @param cm the column model * @param treeColumn the tree column * @param appearance the grid appearance */ public TreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn, GridAppearance appearance) { this(store, cm, treeColumn, appearance, GWT.<TreeAppearance>create(TreeAppearance.class)); } /** * Creates a new tree grid. * * @param store the tree store * @param cm the column model * @param treeColumn the tree column * @param appearance the grid appearance * @param treeAppearance the tree appearance */ public TreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn, GridAppearance appearance, TreeAppearance treeAppearance) { this.appearance = appearance; this.treeAppearance = treeAppearance; disabledStyle = null; SafeHtmlBuilder builder = new SafeHtmlBuilder(); this.appearance.render(builder); setElement((Element) XDOM.create(builder.toSafeHtml())); getElement().makePositionable(); // Do not remove, this is being used in Grid.gss addStyleName("x-treegrid"); getElement().setTabIndex(0); getElement().setAttribute("hideFocus", "true"); this.cm = cm; setTreeColumn(treeColumn); this.treeStore = store; this.store = createListStore(); setSelectionModel(new TreeGridSelectionModel<M>()); disabledStyle = null; storeHandlerRegistration = treeStore.addStoreHandlers(storeHandler); setView(new TreeGridView<M>(appearance)); setAllowTextSelection(false); sinkCellEvents(); // use either long press or tap to select addGestureRecognizer(new LongPressOrTapGestureRecognizer() { @Override protected void onLongPress(TouchData touchData) { Event event = touchData.getLastNativeEvent().cast(); onMouseDown(event); onClick(event); super.onLongPress(touchData); } @Override protected void onEnd(List<TouchData> touches) { Event event = touches.get(0).getLastNativeEvent().cast(); onMouseDown(event); onClick(event); super.onEnd(touches); } }); } @Override public HandlerRegistration addBeforeCollapseHandler(BeforeCollapseItemHandler<M> handler) { return addHandler(handler, BeforeCollapseItemEvent.getType()); } @Override public HandlerRegistration addBeforeExpandHandler(BeforeExpandItemHandler<M> handler) { return addHandler(handler, BeforeExpandItemEvent.getType()); } @Override public HandlerRegistration addCollapseHandler(CollapseItemHandler<M> handler) { return addHandler(handler, CollapseItemEvent.getType()); } @Override public HandlerRegistration addExpandHandler(ExpandItemHandler<M> handler) { return addHandler(handler, ExpandItemEvent.getType()); } /** * Collapses all nodes. */ public void collapseAll() { for (M child : treeStore.getRootItems()) { setExpanded(child, false, true); } } /** * Expands all nodes. */ public void expandAll() { for (M child : treeStore.getRootItems()) { setExpanded(child, true, true); } } /** * Returns the tree node for the given target. * * @param target the target element * @return the tree node or null if no match */ public TreeNode<M> findNode(Element target) { Element row = (Element) getView().findRow(target); if (row != null) { XElement item = XElement.as(row).selectNode(treeAppearance.itemSelector()); if (item != null) { String id = item.getId(); TreeNode<M> node = nodesByDomId.get(id); return node; } } return null; } /** * Returns the grid appearance. * * @return the grid appearance */ public GridAppearance getAppearance() { return appearance; } /** * Returns the model icon provider. * * @return the icon provider */ public IconProvider<M> getIconProvider() { return iconProvider; } /** * Returns the tree style. * * @return the tree style */ public TreeStyle getStyle() { return style; } /** * Returns the tree appearance. * * @return the tree appearance */ public TreeAppearance getTreeAppearance() { return treeAppearance; } /** * Returns the column that represents the tree nodes. * * @return the tree column */ public ColumnConfig<M, ?> getTreeColumn() { return treeColumn; } /** * Returns the tree loader. * * @return the tree loader or null if not specified */ public TreeLoader<M> getTreeLoader() { return loader; } /** * 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<M> getTreeView() { return treeGridView; } /** * Returns true if auto expand is enabled. * * @return the auto expand state */ public boolean isAutoExpand() { return autoExpand; } /** * Returns true if auto load is enabled. * * @return the auto load state */ public boolean isAutoLoad() { return autoLoad; } /** * Returns true when a loader is queried for it's children each time a node is expanded. Only applies when using a * loader with the tree store. * * @return true if caching */ public boolean isCaching() { return caching; } /** * Returns true if the model is expanded. * * @param model the model * @return true if expanded */ public boolean isExpanded(M model) { TreeNode<M> node = findNode(model); return node != null && node.isExpanded(); } /** * Returns the expand on double click state. * * @return the expand on double click state */ public boolean isExpandOnDoubleClick() { return expandOnDoubleClick; } /** * Returns the if expand all and collapse all is enabled on filter changes. * * @return the expand all collapse all state */ public boolean isExpandOnFilter() { return expandOnFilter; } /** * 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) { return !hasChildren(model); } @Override public void reconfigure(ListStore<M> store, ColumnModel<M> cm) { throw new UnsupportedOperationException("Please call the other reconfigure method"); } public void reconfigure(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn) { if (isLoadMask()) { mask(DefaultMessages.getMessages().loadMask_msg()); } this.store.clear(); nodes.clear(); nodesByDomId.clear(); this.store = createListStore(); if (storeHandlerRegistration != null) { storeHandlerRegistration.removeHandler(); } treeStore = store; if (treeStore != null) { storeHandlerRegistration = treeStore.addStoreHandlers(storeHandler); } treeGridView.initData(this.store, cm); this.cm = cm; setTreeColumn(treeColumn); // rebind the sm setSelectionModel(sm); if (isViewReady()) { view.refresh(true); doInitialLoad(); } if (isLoadMask()) { unmask(); } } /** * Refreshes the data for the given model. * * @param model the model to be refreshed */ public void refresh(M model) { TreeNode<M> node = findNode(model); if (viewReady && node != null) { treeGridView.onIconStyleChange(node, calculateIconStyle(model)); treeGridView.onJointChange(node, calculateJoint(model)); } } /** * If set to true, all non leaf nodes will be expanded automatically (defaults to false). * * @param autoExpand the auto expand state to set. */ public void setAutoExpand(boolean autoExpand) { this.autoExpand = autoExpand; } /** * Sets whether all children should automatically be loaded recursively (defaults to false). Useful when the tree must * be fully populated when initially rendered. * * @param autoLoad true to auto load */ public void setAutoLoad(boolean autoLoad) { this.autoLoad = autoLoad; } /** * Sets whether the children should be cached after first being retrieved from the store (defaults to true). When * <code>false</code>, a load request will be made each time a node is expanded. * * @param caching the caching state */ public void setCaching(boolean caching) { this.caching = caching; } /** * 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) { if (expand) { // make parents visible List<M> list = new ArrayList<M>(); M p = model; while ((p = treeStore.getParent(p)) != null) { TreeNode<M> n = findNode(p); if (n == null || !n.isExpanded()) { list.add(p); } } for (int i = list.size() - 1; i >= 0; i--) { M item = list.get(i); setExpanded(item, expand, false); } } TreeNode<M> node = findNode(model); if (node == null) { assert !expand; return; } if (expand) { if (!isLeaf(model)) { // if we are loading, ignore it if (node.isLoading()) { return; } // if we have a loader and node is not loaded make // load request and exit method if (!node.isExpanded() && loader != null && (!node.isLoaded() || !caching) && !filtering) { treeStore.removeChildren(model); node.setExpand(true); node.setExpandDeep(deep); node.setLoading(true); treeGridView.onLoading(node); loader.loadChildren(model); return; } if (!node.isExpanded() && fireCancellableEvent(new BeforeExpandItemEvent<M>(model))) { node.setExpanded(true); if (!node.isChildrenRendered()) { renderChildren(model, false); node.setChildrenRendered(true); } // expand treeGridView.expand(node); fireEvent(new ExpandItemEvent<M>(model)); } if (deep) { setExpandChildren(model, true); } } } else { if (node.isExpanded() && fireCancellableEvent(new BeforeCollapseItemEvent<M>(model))) { node.setExpanded(false); // collapse treeGridView.collapse(node); fireEvent(new CollapseItemEvent<M>(model)); } if (deep) { setExpandChildren(model, false); } } } /** * Determines if the nodes should be expanded and collapsed on double clicks (defaults to {@code true}). Set to false * when using two clicks to edit inline editing. * * @param expandOnDoubleClick true to expand and collapse on double clicks */ public void setExpandOnDoubleClick(boolean expandOnDoubleClick) { this.expandOnDoubleClick = expandOnDoubleClick; } /** * Sets whether the tree should expand all and collapse all when filters are applied (defaults to true). * * @param expandOnFilter true to expand and collapse on filter changes */ public void setExpandOnFilter(boolean expandOnFilter) { this.expandOnFilter = expandOnFilter; } /** * Sets the tree's model icon provider which provides the icon style for each model. * * @param iconProvider the icon provider */ public void setIconProvider(IconProvider<M> iconProvider) { this.iconProvider = iconProvider; } /** * Sets the item's leaf state. The leaf state allows control of the expand icon before the children have been * realized. * * @param model the model * @param leaf the leaf state */ public void setLeaf(M model, boolean leaf) { TreeNode<M> t = findNode(model); if (t != null) { t.setLeaf(leaf); } } /** * Sets the tree loader. * * @param treeLoader the tree loader */ public void setTreeLoader(TreeLoader<M> treeLoader) { this.loader = treeLoader; } @Override public void setView(GridView<M> view) { assert view instanceof TreeGridView : "The view for a TreeGrid has to be an instance of TreeGridView"; super.setView(view); treeGridView = (TreeGridView<M>) view; } /** * Toggles the model's expand state. * * @param model the model */ public void toggle(M model) { TreeNode<M> node = findNode(model); if (node != null) { setExpanded(model, !node.isExpanded()); } } protected ImageResource calculateIconStyle(M model) { ImageResource style = null; if (iconProvider != null) { ImageResource iconStyle = iconProvider.getIcon(model); if (iconStyle != null) { return iconStyle; } } TreeStyle ts = getStyle(); if (!isLeaf(model)) { if (isExpanded(model)) { style = ts.getNodeOpenIcon() != null ? ts.getNodeOpenIcon() : treeAppearance.openNodeIcon(); } else { style = ts.getNodeCloseIcon() != null ? ts.getNodeCloseIcon() : treeAppearance.closeNodeIcon(); } } else { style = ts.getLeafIcon(); } return style; } protected Joint calculateJoint(M model) { if (model == null) { return Joint.NONE; } TreeNode<M> node = findNode(model); Joint joint = Joint.NONE; if (node == null) { return joint; } if (!isLeaf(model)) { boolean children = true; if (node.isExpanded()) { joint = children ? Joint.EXPANDED : Joint.NONE; } else { joint = children ? Joint.COLLAPSED : Joint.NONE; } } return joint; } protected ListStore<M> createListStore() { return new ListStore<M>(treeStore.getKeyProvider()) { @Override public Record getRecord(M model) { return treeStore.getRecord(model); } @Override public boolean hasRecord(M model) { return treeStore.hasRecord(model); } }; } /** * Returns the index in the liststore of the last child in the subtree starting at the given model. The given model * (or its children) do not need to already be in the list store, but all other items do. This makes this method * useful for finding where a model and its subtree belong. * * @param model the model used to find the last open child index. * @return returns the last open child index. */ protected int findLastOpenChildIndex(M model) { // make sure this node is visible assert treeStore.indexOf(model) != -1; // Find the next visible node parallel to or above this one M outer = model; M nextSibling = treeStore.getNextSibling(outer); while (nextSibling == null) { outer = treeStore.getParent(outer); // check if we are at the root if (outer == null) { break; } nextSibling = treeStore.getNextSibling(outer); } if (outer == null) { // hit the root, return the last element return store.size() - 1; } // otherwise, we've got the next element - get _its_ index in the liststore, and return that minus one return store.indexOf(nextSibling) - 1; } protected TreeNode<M> findNode(M m) { if (m == null) return null; return nodes.get(generateModelId(m)); } protected String generateModelId(M m) { return treeStore.getKeyProvider().getKey(m); } protected boolean hasChildren(M model) { TreeNode<M> node = findNode(model); if (loader != null && node != null && !node.isLoaded()) { return loader.hasChildren(node.getModel()); } if (node != null && (!node.isLeaf() || treeStore.hasChildren(node.getModel()))) { return true; } return false; } protected boolean isEventTargetJoint(Event event) { EventTarget eventTarget = event.getEventTarget(); if (Element.is(eventTarget)) { return getTreeAppearance().isJointElement((XElement) Element.as(eventTarget).cast()); } return false; } protected void onAdd(StoreAddEvent<M> se) { if (viewReady) { for (M child : se.getItems()) { register(child); } M p = treeStore.getParent(se.getItems().get(0)); final int index; if (p == null) { // adding to root if (se.getIndex() == 0) { // no need to adjust index, since we're adding to the top index = se.getIndex(); } else { // adjust index to add to the flattened tree index = findLastOpenChildIndex(se.getItems().get(se.getItems().size() - 1)) + 1; } } else { // adding to an existing node TreeNode<M> node = findNode(p); if (node == null) { // node's parent collapsed return; } if (!node.isExpanded()) { refresh(p); return; } if (se.getIndex() == 0) { // inserting at the top of this node, so adjust based on the parent's index index = store.indexOf(p) + 1; } else { // adjust the index to add to the local flattened tree index = findLastOpenChildIndex(se.getItems().get(se.getItems().size() - 1)) + 1; } refresh(p); } store.addAll(index, se.getItems()); } } @Override protected void onAfterRenderView() { super.onAfterRenderView(); doInitialLoad(); } protected void onClear(StoreClearEvent<M> event) { onDataChange(null); } @Override public void onBrowserEvent(Event ce) { if (ce.getTypeInt() == Event.ONCLICK) { if (isEventTargetJoint(ce)) { onClick(ce); return; } } super.onBrowserEvent(ce); } @Override protected void onClick(Event event) { if (isEventTargetJoint(event)) { EventTarget eventTarget = event.getEventTarget(); M m = store.get(getView().findRowIndex(Element.as(eventTarget))); if (m != null) { toggle(m); } } else { super.onClick(event); } } protected void onDataChange(M parent) { if (!viewReady) { return; } if (parent == null) { store.clear(); nodes.clear(); nodesByDomId.clear(); renderChildren(null, autoLoad); } else { TreeNode<M> n = findNode(parent); if (n != null) { if (n.isExpanded()) { // no need to collapse if we're not expanded treeGridView.collapse(n); n.setExpanded(false); } n.setLoaded(true); n.setLoading(false); renderChildren(parent, autoLoad); if (n.isExpand() && !isLeaf(parent)) { n.setExpand(false); boolean deep = n.isExpandDeep(); n.setExpandDeep(false); boolean c = caching; caching = true; setExpanded(parent, true, deep); caching = c; } else { refresh(parent); } } } } @Override protected void onDoubleClick(Event e) { super.onDoubleClick(e); if (expandOnDoubleClick) { if (Element.is(e.getEventTarget())) { int i = getView().findRowIndex(Element.as(e.getEventTarget())); M m = store.get(i); if (m != null) { toggle(m); } } } } protected void onFilter(StoreFilterEvent<M> se) { onDataChange(null); if (expandOnFilter && treeStore.isFiltered()) { expandAll(); } } protected void onRecordChange(StoreRecordChangeEvent<M> event) { store.update(event.getRecord().getModel()); } protected void onRemove(TreeStoreRemoveEvent<M> event) { if (viewReady) { unregister(event.getItem()); store.remove(event.getItem()); for (M child : event.getChildren()) { unregister(child); store.remove(child); } TreeNode<M> p = findNode(event.getParent()); if (p != null && p.isExpanded() && treeStore.getChildCount(p.getModel()) == 0) { setExpanded(p.getModel(), false); } else if (p != null && treeStore.getChildCount(p.getModel()) == 0) { refresh(event.getParent()); } } } protected void onSort(StoreSortEvent<M> event) { onDataChange(null); } protected void onUpdate(StoreUpdateEvent<M> se) { for (M m : se.getItems()) { // looking up by key instead of by index to allow for different model instance, but no equals() equality if (store.findModelWithKey(store.getKeyProvider().getKey(m)) != null) { store.update(m); } } } protected String register(M m) { String id = generateModelId(m); if (!nodes.containsKey(id)) { String domId = XDOM.getUniqueId(); TreeGridNode<M> node = new TreeGridNode<M>(id, m, domId); nodes.put(id, node); nodesByDomId.put(domId, node); } return id; } protected void renderChildren(M parent, boolean auto) { List<M> children = parent == null ? treeStore.getRootItems() : treeStore.getChildren(parent); for (M child : children) { register(child); } if (parent == null && children.size() > 0) { store.addAll(children); } for (M child : children) { if (autoExpand) { final M c = child; Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { setExpanded(c, true); } }); } else if (loader != null && autoLoad) { if (store.isFiltered() || (!auto)) { renderChildren(child, auto); } else { loader.loadChildren(child); } } } } protected void setTreeColumn(ColumnConfig<M, ?> treeColumn) { assert (treeColumn != null && cm.indexOf(treeColumn) != -1) : "treeColumn not found in ColumnModel"; this.treeColumn = treeColumn; } protected void unregister(M m) { TreeNode<M> node = findNode(m); if (node != null) { node.clearElements(); TreeNode<M> removed = nodes.remove(generateModelId(m)); assert removed != null; nodesByDomId.remove(removed.getDomId()); } } private void doInitialLoad() { if (treeStore.getRootItems().size() == 0 && loader != null) { loader.load(); } else { renderChildren(null, false); if (autoExpand) { expandAll(); } } } private void setExpandChildren(M m, boolean expand) { for (M child : treeStore.getChildren(m)) { setExpanded(child, expand, true); } } }