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.tree; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import com.google.gwt.cell.client.Cell; import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.cell.client.ValueUpdater; 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.dom.client.Style.Unit; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiConstructor; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.impl.FocusImpl; import com.sencha.gxt.core.client.ValueProvider; import com.sencha.gxt.core.client.dom.DomHelper; import com.sencha.gxt.core.client.dom.XDOM; import com.sencha.gxt.core.client.dom.XElement; import com.sencha.gxt.core.client.gestures.ScrollGestureRecognizer; import com.sencha.gxt.core.client.gestures.ScrollGestureRecognizer.ScrollDirection; import com.sencha.gxt.core.client.gestures.TapGestureRecognizer; import com.sencha.gxt.core.client.gestures.TouchData; import com.sencha.gxt.core.client.resources.CommonStyles; import com.sencha.gxt.core.client.util.DelayedTask; import com.sencha.gxt.core.client.util.Util; import com.sencha.gxt.core.shared.FastMap; import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration; import com.sencha.gxt.data.shared.IconProvider; import com.sencha.gxt.data.shared.ModelKeyProvider; import com.sencha.gxt.data.shared.PropertyAccess; import com.sencha.gxt.data.shared.Store; import com.sencha.gxt.data.shared.TreeStore; import com.sencha.gxt.data.shared.event.StoreAddEvent; import com.sencha.gxt.data.shared.event.StoreAddEvent.StoreAddHandler; import com.sencha.gxt.data.shared.event.StoreClearEvent; import com.sencha.gxt.data.shared.event.StoreClearEvent.StoreClearHandler; import com.sencha.gxt.data.shared.event.StoreDataChangeEvent; import com.sencha.gxt.data.shared.event.StoreDataChangeEvent.StoreDataChangeHandler; import com.sencha.gxt.data.shared.event.StoreFilterEvent; import com.sencha.gxt.data.shared.event.StoreFilterEvent.StoreFilterHandler; import com.sencha.gxt.data.shared.event.StoreRemoveEvent; import com.sencha.gxt.data.shared.event.StoreRemoveEvent.StoreRemoveHandler; import com.sencha.gxt.data.shared.event.StoreSortEvent; import com.sencha.gxt.data.shared.event.StoreSortEvent.StoreSortHandler; import com.sencha.gxt.data.shared.event.StoreUpdateEvent; import com.sencha.gxt.data.shared.event.StoreUpdateEvent.StoreUpdateHandler; import com.sencha.gxt.data.shared.event.TreeStoreRemoveEvent; import com.sencha.gxt.data.shared.loader.TreeLoader; import com.sencha.gxt.widget.core.client.CheckProvider; import com.sencha.gxt.widget.core.client.Component; import com.sencha.gxt.widget.core.client.event.BeforeCheckChangeEvent; import com.sencha.gxt.widget.core.client.event.BeforeCheckChangeEvent.BeforeCheckChangeHandler; import com.sencha.gxt.widget.core.client.event.BeforeCheckChangeEvent.HasBeforeCheckChangeHandlers; 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.CheckChangeEvent; import com.sencha.gxt.widget.core.client.event.CheckChangeEvent.CheckChangeHandler; import com.sencha.gxt.widget.core.client.event.CheckChangeEvent.HasCheckChangeHandlers; import com.sencha.gxt.widget.core.client.event.CheckChangedEvent; import com.sencha.gxt.widget.core.client.event.CheckChangedEvent.CheckChangedHandler; 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.event.XEvent; import com.sencha.gxt.widget.core.client.tree.TreeView.TreeViewRenderMode; /** * A {@link Tree} provides support for displaying hierarchical data. The tree gets its data from a {@link TreeStore}. * Each model in the store is rendered as an item in the tree. Any updates to the store are automatically pushed to the * tree. * <p/> * In GXT version 3, {@link ModelKeyProvider}s and {@link ValueProvider}s provide the interface between your data model, * the tree store and the tree. This enables a tree to work with data of any object type. The tree uses a value * provider, passed to the constructor, to get the value to display for each model in the tree. * <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/> * To customize the appearance of the item in the tree, provide a cell implementation using {@link Tree#setCell(Cell)}. * <p/> * The following code snippet illustrates the creation of a simple tree with local data for test purposes. For more * practical examples that show how to load data from remote sources, see the Async Tree and Async Json Tree examples in * the online Explorer demo. * </p> * * <pre>{@code // Generate the key provider and value provider for the Data class DataProperties dp = GWT.create(DataProperties.class); // Create the store that the contains the data to display in the tree TreeStore<Data> s = new TreeStore<Test.Data>(dp.key()); 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")); 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")); // Create the tree using the store and value provider for the name field Tree<Data, String> t = new Tree<Data, String>(s, dp.name()); // Add the tree to a container RootPanel.get().add(t); * }</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/> * * <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 check box support for a tree, add the following: * <p/> * * <pre> t.setCheckable(true); t.setCheckStyle(CheckCascade.CHILDREN); * </pre> * See {@link CheckCascade} for additional check box styles. * <p/> * To save and restore the expand / collapse state of tree items, add the following (must be after the tree is added to * the container). This works with both local and asynchronous loading of tree items. * <p/> * * <pre> t.setStateId("TreeCodeSnippetTest"); StateManager.get().setProvider(new CookieProvider("/", null, null, GXT.isSecure())); TreeStateHandler<Data> sh = new TreeStateHandler<Data>(t); sh.loadState(); * </pre> * <p/> * * @param <M> the model type * @param <C> the cell data type */ public class Tree<M, C> extends Component implements HasBeforeExpandItemHandlers<M>, HasExpandItemHandlers<M>, HasBeforeCollapseItemHandlers<M>, HasCollapseItemHandlers<M>, HasBeforeCheckChangeHandlers<M>, HasCheckChangeHandlers<M>, CheckProvider<M> { /** * Check cascade enum. */ public enum CheckCascade { /** * Checks cascade to all child nodes. */ CHILDREN, /** * Checks to not cascade. */ NONE, /** * Checks cascade to all parent nodes. */ PARENTS, /** * Tri-state check behavior. */ TRI } /** * Check nodes enum. */ public enum CheckNodes { /** * Check boxes for both leafs and parent nodes. */ BOTH, /** * Check boxes for only leaf nodes. */ LEAF, /** * Check boxes for only parent nodes. */ PARENT } public enum CheckState { CHECKED, PARTIAL, UNCHECKED } /** * Joint enum. */ public enum Joint { COLLAPSED(1), EXPANDED(2), NONE(0); private int value; private Joint(int value) { this.value = value; } public int value() { return value; } } public interface TreeAppearance { ImageResource closeNodeIcon(); String elementSelector(); XElement findIconElement(XElement target); XElement findJointElement(XElement target); XElement getCheckElement(XElement container); XElement getContainerElement(XElement node); XElement getIconElement(XElement container); XElement getJointElement(XElement container); XElement getTextElement(XElement container); boolean isCheckElement(XElement target); boolean isJointElement(XElement target); String itemSelector(); ImageResource loadingIcon(); XElement onCheckChange(XElement node, XElement checkElement, boolean checkable, CheckState state); void onDropOver(XElement node, boolean over); void onHover(XElement node, boolean over); XElement onJointChange(XElement node, XElement jointElement, Joint joint, TreeStyle ts); void onSelect(XElement node, boolean select); ImageResource openNodeIcon(); void render(SafeHtmlBuilder sb); void renderContainer(SafeHtmlBuilder sb); void renderNode(SafeHtmlBuilder sb, String id, SafeHtml html, TreeStyle style, ImageResource icon, boolean checkable, CheckState checked, Joint joint, int level, TreeViewRenderMode renderMode); String textSelector(); } /** * Maintains the internal state of nodes contained in the tree. Should not need to be referenced in typical usage. */ public static class TreeNode<M> { protected CheckState checked = CheckState.UNCHECKED; protected Element element; protected final String modelId; protected final String domId; protected boolean leaf = true; private Element checkElement; private boolean childrenRendered; private Element containerElement; private Element elContainer; private boolean expand; private boolean expandDeep; private boolean expanded; private Element iconElement; private Element jointElement; private boolean loaded; private boolean loading; private M model; private Element textElement; protected TreeNode(String modelId, M m, String domId) { this.modelId = modelId; this.domId = domId; this.model = m; } public void clearElements() { jointElement = null; checkElement = null; iconElement = null; textElement = null; } public Element getCheckElement() { return checkElement; } public Element getContainerElement() { return containerElement; } public String getDomId() { return domId; } /** * Gets the root element of the tree node that this object represents. May return null if the tree hasn't looked up * the element yet, or if the node isn't rendered yet. Typically * {@link TreeView#getElement(com.sencha.gxt.widget.core.client.tree.Tree.TreeNode)} should be used instead. * * @see TreeView#getElement(com.sencha.gxt.widget.core.client.tree.Tree.TreeNode) * @return an element if it is rendered and cached, null otherwise */ public Element getElement() { return element; } public Element getElementContainer() { return elContainer; } public Element getIconElement() { return iconElement; } public Element getJointElement() { return jointElement; } public M getModel() { return model; } public String getModelId() { return modelId; } public Element getTextElement() { return textElement; } public boolean isChildrenRendered() { return childrenRendered; } public boolean isExpand() { return expand; } public boolean isExpandDeep() { return expandDeep; } public boolean isExpanded() { return expanded; } public boolean isLeaf() { return leaf; } public boolean isLoaded() { return loaded; } public boolean isLoading() { return loading; } public void setCheckElement(Element checkElement) { this.checkElement = checkElement; } public void setChildrenRendered(boolean childrenRendered) { this.childrenRendered = childrenRendered; } public void setContainerElement(Element containerElement) { this.containerElement = containerElement; } public void setElContainer(Element elContainer) { this.elContainer = elContainer; } /** * Sets the element to be used as the root of this node. * * @param element the element to cache as the root of this tree node */ public void setElement(Element element) { this.element = element; } public void setExpand(boolean expand) { this.expand = expand; } public void setExpandDeep(boolean expandDeep) { this.expandDeep = expandDeep; } public void setExpanded(boolean expanded) { this.expanded = expanded; } public void setIconElement(Element iconElement) { this.iconElement = iconElement; } public void setJointElement(Element jointElement) { this.jointElement = jointElement; } public void setLeaf(boolean leaf) { this.leaf = leaf; } public void setLoaded(boolean loaded) { this.loaded = loaded; } public void setLoading(boolean loading) { this.loading = loading; } public void setTextElement(Element textElement) { this.textElement = textElement; } } private class Handler implements StoreAddHandler<M>, StoreClearHandler<M>, StoreDataChangeHandler<M>, StoreFilterHandler<M>, StoreRemoveHandler<M>, StoreUpdateHandler<M>, StoreSortHandler<M> { @Override public void onAdd(StoreAddEvent<M> event) { Tree.this.onAdd(event); } @Override public void onClear(StoreClearEvent<M> event) { Tree.this.onClear(event); } @Override public void onDataChange(StoreDataChangeEvent<M> event) { Tree.this.onDataChanged(event); } @Override public void onFilter(StoreFilterEvent<M> event) { Tree.this.onFilter(event); } @Override public void onRemove(StoreRemoveEvent<M> event) { Tree.this.onRemove(event); } @Override public void onSort(StoreSortEvent<M> event) { Tree.this.onSort(event); } @Override public void onUpdate(StoreUpdateEvent<M> event) { Tree.this.onUpdate(event); } } private final TreeAppearance appearance; protected boolean filtering; protected XElement focusEl; protected final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); protected boolean focusConstrainScheduled = false; protected TreeLoader<M> loader; protected Map<String, TreeNode<M>> nodes = new FastMap<TreeNode<M>>(); protected Map<String, TreeNode<M>> nodesByDom = new FastMap<TreeNode<M>>(); protected TreeSelectionModel<M> sm; protected TreeStore<M> store; final protected ValueProvider<? super M, C> valueProvider; protected TreeView<M> view = new TreeView<M>(); protected Element rootContainer; private boolean autoExpand; private boolean autoLoad; private boolean autoSelect; private boolean bufferRender = false; private boolean caching = true; private boolean cascade = true; private Cell<C> cell; private boolean checkable; private CheckNodes checkNodes = CheckNodes.BOTH; private CheckCascade checkStyle = CheckCascade.PARENTS; private boolean expandOnFilter = true; private GroupingHandlerRegistration storeHandlers = new GroupingHandlerRegistration(); private IconProvider<M> iconProvider; private TreeStyle style = new TreeStyle(); private boolean trackMouseOver = true; private DelayedTask updateTask, cleanTask; /** * Creates a new tree panel. * * @param store the tree store */ @UiConstructor public Tree(TreeStore<M> store, ValueProvider<? super M, C> valueProvider) { this(store, valueProvider, (TreeAppearance) GWT.create(TreeAppearance.class)); } public Tree(TreeStore<M> store, ValueProvider<? super M, C> valueProvider, TreeAppearance appearance) { this.appearance = appearance; this.valueProvider = valueProvider; SafeHtmlBuilder builder = new SafeHtmlBuilder(); this.appearance.render(builder); setElement((Element) XDOM.create(builder.toSafeHtml())); ensureFocusElement(); setStore(store); setSelectionModel(new TreeSelectionModel<M>()); view.bind(this); addGestureRecognizer(new TapGestureRecognizer() { @Override protected void onTap(TouchData touchData) { Event event = touchData.getLastNativeEvent().cast(); onClick(event); getSelectionModel().onMouseDown(event); getSelectionModel().onMouseClick(event); super.onTap(touchData); } }); addGestureRecognizer(new ScrollGestureRecognizer(getElement(), ScrollDirection.BOTH)); } @Override public HandlerRegistration addBeforeCheckChangeHandler(BeforeCheckChangeHandler<M> handler) { return addHandler(handler, BeforeCheckChangeEvent.getType()); } @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 addCheckChangedHandler(CheckChangedHandler<M> handler) { return addHandler(handler, CheckChangedEvent.getType()); } @Override public HandlerRegistration addCheckChangeHandler(CheckChangeHandler<M> handler) { return addHandler(handler, CheckChangeEvent.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 : store.getRootItems()) { setExpanded(child, false, true); } } /** * Expands all nodes. */ public void expandAll() { for (M child : store.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 elem = target.<XElement>cast().findParentElement(appearance.itemSelector(), 10); if (elem != null) { TreeNode<M> node = nodesByDom.get(elem.getId()); return node; } return null; } /** * Returns the tree node for the given model. * * @param model the model * @return the tree node or null if no match */ public TreeNode<M> findNode(M model) { if (store == null || model == null) return null; return nodes.get(generateModelId(model)); } @Override public void focus() { focusImpl.focus(focusEl); } /** * Returns the tree appearance. * * @return the tree appearance */ public TreeAppearance getAppearance() { return appearance; } /** * Return the tree's cell. * * @return the cell */ public Cell<C> getCell() { return cell; } /** * Returns the models checked state. * * @param model the model * @return the check state */ public CheckState getChecked(M model) { TreeNode<M> node = findNode(model); if (node != null && isCheckable(node)) { return node.checked; } return CheckState.UNCHECKED; } /** * Returns the current checked selection. Only items that have been rendered will be returned in this list. Set * {@link #setAutoLoad(boolean)} to {@code true} to fully render the tree to receive all checked items in the tree. * * @return the checked selection */ @Override public List<M> getCheckedSelection() { List<M> checked = new ArrayList<M>(); for (TreeNode<M> n : nodes.values()) { if (n.checked == CheckState.CHECKED) { checked.add(n.getModel()); } } return checked; } /** * Returns the child nodes value which determines what node types have a check box. Only applies when check boxes have * been enabled ( {@link #setCheckable(boolean)}. * * @return the child nodes value */ public CheckNodes getCheckNodes() { return checkNodes; } /** * The check cascade style value which determines if check box changes cascade to parent and children. * * @return the check cascade style */ public CheckCascade getCheckStyle() { return checkStyle; } /** * Returns the model icon provider. * * @return the icon provider */ public IconProvider<M> getIconProvider() { return iconProvider; } /** * Returns the tree's selection model. * * @return the selection model */ public TreeSelectionModel<M> getSelectionModel() { return sm; } /** * Returns the tree's store. * * @return the store */ public TreeStore<M> getStore() { return store; } /** * Returns the tree style. * * @return the tree style */ public TreeStyle getStyle() { return style; } /** * Returns the tree's view. * * @return the view */ public TreeView<M> getView() { return view; } /** * Returns {@code true} if auto expand is enabled. * * @return the auto expand state */ public boolean isAutoExpand() { return autoExpand; } /** * Returns {@code true} if auto load is enabled. * * @return the auto load state */ public boolean isAutoLoad() { return autoLoad; } /** * Returns {@code true} if select on load is enabled. * * @return the auto select state */ public boolean isAutoSelect() { return autoSelect; } /** * Returns {@code true} if buffered rendering is enabled. * * @return true for buffered rendering */ public boolean isBufferedRender() { return bufferRender; } /** * Returns {@code 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 {@code true} if check boxes are enabled. * * @return the check box state */ public boolean isCheckable() { return checkable; } @Override public boolean isChecked(M model) { TreeNode<M> node = findNode(model); if (node != null && isCheckable(node)) { return node.checked == CheckState.CHECKED; } return false; } /** * Returns {@code 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.expanded; } /** * 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 {@code 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); } /** * Returns {@code true} if nodes are highlighted on mouse over. * * @return true if enabled */ public boolean isTrackMouseOver() { return trackMouseOver; } @Override public void onBrowserEvent(Event event) { if (cell != null) { CellWidgetImplHelper.onBrowserEvent(this, event); } super.onBrowserEvent(event); switch (event.getTypeInt()) { case Event.ONCLICK: onClick(event); break; case Event.ONDBLCLICK: onDoubleClick(event); break; case Event.ONSCROLL: onScroll(event); break; case Event.ONFOCUS: onFocus(event); break; } view.onEvent(event); handleEventForCell(event); } public void refresh(M model) { if (isOrWasAttached()) { TreeNode<M> node = findNode(model); if (node != null && view.getElement(node) != null) { view.onIconStyleChange(node, calculateIconStyle(model)); view.onJointChange(node, calculateJoint(model)); view.onTextChange(node, getCellContent(model)); boolean checkable = isCheckable(node); if (checkable) { setChecked(node.getModel(), node.checked); } } } } /** * Scrolls the tree to ensure the given model is visible. * * @param model the model to scroll into view */ public void scrollIntoView(M model) { TreeNode<M> node = findNode(model); if (node != null) { XElement con = (XElement) node.getElementContainer(); if (con != null) { con.scrollIntoView(getElement(), false); focusEl.setXY(con.getXY()); } } } /** * If set to {@code true}, all non leaf nodes will be expanded automatically (defaults to {@code 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 {@code true} to auto load */ public void setAutoLoad(boolean autoLoad) { this.autoLoad = autoLoad; } /** * True to select the first model after the store's data changes (defaults to {@code false}). * * @param autoSelect {@code true} to auto select */ public void setAutoSelect(boolean autoSelect) { this.autoSelect = autoSelect; } /** * True to only render tree nodes that are in view (defaults to {@code false}). Set this to {@code true} when dealing * with very large trees. * * @param bufferRender {@code true} to buffer render */ public void setBufferedRender(boolean bufferRender) { this.bufferRender = bufferRender; } /** * Sets whether the children should be cached after first being retrieved from the store (defaults to {@code true}). * When {@code false}, 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 tree's cell. * * @param cell the cell */ public void setCell(Cell<C> cell) { this.cell = cell; if (cell != null) { CellWidgetImplHelper.sinkEvents(this, cell.getConsumedEvents()); } } /** * Sets whether check boxes are used in the tree (defaults to {@code false}). * * @param checkable {@code true} for check boxes */ public void setCheckable(boolean checkable) { this.checkable = checkable; } /** * Sets the check state of the item. The checked state will only be set for nodes that have been rendered, * {@link #setAutoLoad(boolean)} can be used to render all children. * * @param item the item * @param checked the check state */ public void setChecked(M item, CheckState checked) { if (!checkable) return; TreeNode<M> node = findNode(item); if (node != null) { if (node.checked == checked) { return; } boolean leaf = isLeaf(item); if ((!leaf && checkNodes == CheckNodes.LEAF) || (leaf && checkNodes == CheckNodes.PARENT)) { return; } if (fireCancellableEvent(new BeforeCheckChangeEvent<M>(node.getModel(), node.checked))) { node.checked = checked; view.onCheckChange(node, checkable, checked); fireEvent(new CheckChangeEvent<M>(item, node.checked)); fireEvent(new CheckChangedEvent<M>(getCheckedSelection())); if (cascade) { onCheckCascade(item, checked); } } } } @Override public void setCheckedSelection(List<M> selection) { for (M m : store.getAll()) { setChecked(m, selection != null && selection.contains(m) ? CheckState.CHECKED : CheckState.UNCHECKED); } } /** * Sets which tree items will display a check box (defaults to BOTH). * <p> * Valid values are: * <ul> * <li>BOTH - both nodes and leafs</li> * <li>PARENT - only nodes with children</li> * <li>LEAF - only leafs</li> * </ul> * * @param checkNodes the child nodes value */ public void setCheckNodes(CheckNodes checkNodes) { this.checkNodes = checkNodes; if (isOrWasAttached()) { for (M m : store.getAll()) { refresh(m); } } } /** * Sets the cascading behavior for check tree (defaults to PARENTS). When using CHILDREN, it is important to note that * the cascade will only be applied to rendered nodes. {@link #setAutoLoad(boolean)} can be used to fully render the * tree on render. * <p> * Valid values are: * <ul> * <li>NONE - no cascading</li> * <li>PARENTS - cascade to parents</li> * <li>CHILDREN - cascade to children</li> * </ul> * * @param checkStyle the child style */ public void setCheckStyle(CheckCascade checkStyle) { this.checkStyle = checkStyle; } /** * Sets the item's expand state. * * @param model the model * @param expand {@code 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 {@code true} to expand * @param deep {@code true} to expand all children recursively */ public void setExpanded(M model, boolean expand, boolean deep) { if (expand) { // make item visible by expanding parents List<M> list = new ArrayList<M>(); M p = model; while ((p = store.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 (!isAttached()) { node.setExpand(expand); return; } if (expand) { onExpand(model, node, deep); } else { onCollapse(model, node, deep); } } /** * Sets whether the tree should expand all and collapse all when filters are applied (defaults to {@code true}). * * @param expandOnFilter {@code 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); refresh(model); } } /** * Sets the tree loader. * * @param loader the loader */ public void setLoader(TreeLoader<M> loader) { this.loader = loader; } /** * Sets the tree's selection model. * * @param sm the selection model */ public void setSelectionModel(TreeSelectionModel<M> sm) { if (this.sm != null) { this.sm.bindTree(null); } this.sm = sm; if (sm != null) { sm.bindTree(this); } } /** * Assigns a new store to the tree. May be null, in which case the tree must not be attached to the dom. All selection * is lost when this takes place and items are re-rendered. * * @param store the new store to bind to, or null to detach from the store */ public void setStore(TreeStore<M> store) { if (this.store != null) { storeHandlers.removeHandler(); if (isOrWasAttached()) { clear(); } } this.store = store; if (this.store != null) { Handler handler = new Handler(); storeHandlers.add(store.addStoreAddHandler(handler)); storeHandlers.add(store.addStoreUpdateHandler(handler)); storeHandlers.add(store.addStoreRemoveHandler(handler)); storeHandlers.add(store.addStoreDataChangeHandler(handler)); storeHandlers.add(store.addStoreClearHandler(handler)); storeHandlers.add(store.addStoreFilterHandler(handler)); storeHandlers.add(store.addStoreSortHandler(handler)); if (getSelectionModel() != null) { getSelectionModel().bind(store); } if (isOrWasAttached()) { renderChildren(null); if (autoSelect) { getSelectionModel().select(0, false); } } } else { // no store, make sure we aren't attached if (isAttached()) { throw new IllegalStateException( "Tree must have a store when attached, either detach or pass in a non-null store"); } } } /** * Sets the tree style. * * @param style the tree style */ public void setStyle(TreeStyle style) { this.style = style; } /** * True to highlight nodes when the mouse is over (defaults to {@code true}). * * @param trackMouseOver {@code true} to highlight nodes on mouse over */ public void setTrackMouseOver(boolean trackMouseOver) { this.trackMouseOver = trackMouseOver; } /** * Sets the tree's view. Only needs to be called when customizing the tree's presentation. * * @param view the view */ public void setView(TreeView<M> view) { this.view = view; view.bind(this); } /** * Toggles the model's expand state. If the model is not visible, does nothing. * * @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() : appearance.openNodeIcon(); } else { style = ts.getNodeCloseIcon() != null ? ts.getNodeCloseIcon() : appearance.closeNodeIcon(); } } else { style = ts.getLeafIcon(); } return style; } protected Joint calculateJoint(M model) { if (model == null) { return Joint.NONE; } TreeNode<M> node = findNode(model); return isLeaf(model) ? Joint.NONE : node.isExpanded() ? Joint.EXPANDED : Joint.COLLAPSED; } protected boolean cellConsumesEventType(Cell<?> cell, String eventType) { if (cell == null) { return false; } Set<String> consumedEvents = cell.getConsumedEvents(); return consumedEvents != null && consumedEvents.contains(eventType); } protected void clean() { if (cleanTask == null) { cleanTask = new DelayedTask() { @Override public void onExecute() { doClean(); } }; } cleanTask.delay(view.getCleanDelay()); } protected void cleanCollapsed(M parent) { List<M> list = store.getAllChildren(parent); for (M m : list) { TreeNode<M> node = findNode(m); if (node != null && node.element != null) { cleanNode(node); } } } protected void cleanNode(TreeNode<M> node) { if (node != null && node.element != null) { node.clearElements(); view.getElement(node).getFirstChildElement().<XElement>cast().removeChildren(); } } protected void clear() { if (isOrWasAttached()) { getContainer(null).<XElement>cast().removeChildren(); nodes.clear(); nodesByDom.clear(); if (isAttached()) { moveFocus(getContainer(null)); } } } protected void moveFocus(Element selectedElem) { if (selectedElem == null) return; constrainFocusElement(); } protected void constrainFocusElement() { if (!focusConstrainScheduled) { focusConstrainScheduled = true; Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { focusConstrainScheduled = false; int scrollLeft = getElement().getScrollLeft(); int scrollTop = getElement().getScrollTop(); int left = getElement().getWidth(true) / 2 + scrollLeft; int top = getElement().getHeight(true) / 2 + scrollTop; focusEl.setLeftTop(left, top); } }); } } protected void doClean() { int count = getVisibleRowCount(); if (count > 0) { List<M> rows = getChildModel(store.getRootItems(), true); int[] vr = getVisibleRows(rows, count); vr[0] -= view.getCacheSize(); vr[1] += view.getCacheSize(); int i = 0; // if first is less than 0, all rows have been rendered // so lets clean the end... if (vr[0] <= 0) { i = vr[1] + 1; } for (int len = rows.size(); i < len; i++) { // if current row is outside of first and last and // has content, clean the node if (i < vr[0] || i > vr[1]) { cleanNode(findNode(rows.get(i))); } } } } protected void doUpdate() { int count = getVisibleRowCount(); if (count > 0) { List<M> rootItems = store.getRootItems(); List<M> visible = getChildModel(rootItems, true); int[] vr = getVisibleRows(visible, count); for (int i = vr[0]; i <= vr[1]; i++) { if (!isRowRendered(i, visible)) { M parent = store.getParent(visible.get(i)); SafeHtml html = renderChild(parent, visible.get(i), store.getDepth(parent), TreeViewRenderMode.BUFFER_BODY); view.getElement(findNode(visible.get(i))).getFirstChildElement().setInnerSafeHtml(html); } } clean(); } } protected void findChildren(M parent, List<M> list, boolean onlyVisible) { for (M child : store.getChildren(parent)) { list.add(child); if (!onlyVisible || findNode(child).isExpanded()) { findChildren(child, list, onlyVisible); } } } protected void fireEventToCell(Event event, String eventType, Element cellParent, final M m, Context context) { if (cell != null && cellConsumesEventType(cell, eventType)) { C value = getValue(m); cell.onBrowserEvent(context, cellParent, value, event, new ValueUpdater<C>() { @Override public void update(C value) { Tree.this.getStore().getRecord(m).addChange(valueProvider, value); } }); } } protected String generateModelId(M m) { return store.getKeyProvider().getKey(m); } protected SafeHtml getCellContent(M model) { C value = getValue(model); SafeHtmlBuilder sb = new SafeHtmlBuilder(); if (cell == null) { String text = null; if (value != null) { text = value.toString(); } sb.append(Util.isEmptyString(text) ? Util.NBSP_SAFE_HTML : SafeHtmlUtils.fromString(text)); } else { Context context = new Context(store.indexOf(model), 0, store.getKeyProvider().getKey(model)); cell.render(context, value, sb); } return sb.toSafeHtml(); } protected List<M> getChildModel(List<M> l, boolean onlyVisible) { List<M> list = new ArrayList<M>(); for (M m : l) { list.add(m); if (!onlyVisible || findNode(m).isExpanded()) { findChildren(m, list, onlyVisible); } } return list; } protected Element getContainer(M model) { if (model == null) { return rootContainer; } TreeNode<M> node = findNode(model); if (node != null) { return view.getContainer(node); } return null; } protected Element getRootContainer() { // TODO we should be asking appearance for the root container element rather // than assuming its the last child. XElement root = getElement(); if (root.getFirstChildElement() != null && root.getFirstChildElement().getTagName().equals("TABLE")) { root = root.selectNode("td"); } return root; } protected C getValue(M m) { C value; if (store.hasRecord(m)) { value = store.getRecord(m).getValue(valueProvider); } else { value = valueProvider.getValue(m); } return value; } protected int getVisibleRowCount() { int rh = view.getCalculatedRowHeight(); int visibleHeight = getElement().getHeight(true); return (int) ((visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh)); } protected int[] getVisibleRows(List<M> visible, int count) { int sc = getElement().getScrollTop(); int start = (int) (sc == 0 ? 0 : Math.floor(sc / view.getCalculatedRowHeight()) - 1); int first = Math.max(start, 0); int last = Math.min(start + count + 2, visible.size() - 1); return new int[] { first, last }; } protected void handleEventForCell(Event event) { // Get the event target. EventTarget eventTarget = event.getEventTarget(); if (cell == null || !Element.is(eventTarget)) { return; } final Element target = event.getEventTarget().cast(); TreeNode<M> node = findNode(target); if (node == null) { return; } M value = node.getModel(); Element cellParent = getView().getTextElement(node); if (value != null && cellParent != null) { Context context = new Context(store.indexOf(value), 0, getStore().getKeyProvider().getKey(value)); fireEventToCell(event, event.getType(), cellParent, value, context); } } protected boolean hasChildren(M model) { TreeNode<M> node = findNode(model); if (loader != null && !node.isLoaded()) { return loader.hasChildren(node.getModel()); } if (!node.leaf || store.hasChildren(node.getModel())) { return true; } return false; } protected boolean isCheckable(TreeNode<M> node) { boolean leaf = isLeaf(node.getModel()); boolean check = checkable; switch (checkNodes) { case LEAF: if (!leaf) { check = false; } break; case PARENT: if (leaf) { check = false; } default: // empty } return check; } protected boolean isRowRendered(int i, List<M> visible) { Element e = view.getElement(findNode(visible.get(i))); return e != null && e.getFirstChild().hasChildNodes(); } @Override protected void notifyShow() { super.notifyShow(); update(); } protected void onAdd(StoreAddEvent<M> event) { for (M child : event.getItems()) { register(child); } if (isOrWasAttached()) { M parent = store.getParent(event.getItems().get(0)); TreeNode<M> pn = findNode(parent); if (parent == null || (pn != null && pn.isChildrenRendered())) { SafeHtmlBuilder sb = new SafeHtmlBuilder(); int parentDepth = parent == null ? 0 : store.getDepth(parent); for (M child : event.getItems()) { TreeViewRenderMode mode = !bufferRender ? TreeViewRenderMode.ALL : TreeViewRenderMode.BUFFER_WRAP; sb.append(renderChild(parent, child, parentDepth, mode)); } int index = event.getIndex(); int parentChildCount = parent == null ? store.getRootCount() : store.getChildCount(parent); if (index == 0) { DomHelper.insertFirst(getContainer(parent), sb.toSafeHtml()); } else if (index == parentChildCount - event.getItems().size()) { DomHelper.insertHtml("beforeEnd", getContainer(parent), sb.toSafeHtml()); } else { DomHelper.insertBefore((Element) getContainer(parent).getChild(index).cast(), sb.toSafeHtml()); } } refresh(parent); update(); } } protected void onAfterFirstAttach() { SafeHtmlBuilder builder = new SafeHtmlBuilder(); this.appearance.renderContainer(builder); // TODO we should be asking appearance for the root container element rather // than assuming its the last child. rootContainer = getRootContainer(); rootContainer.setInnerSafeHtml(builder.toSafeHtml()); getElement().show(); getElement().getStyle().setProperty("overflow", "auto"); if (store.getRootItems().size() == 0 && loader != null) { loader.load(); } else { renderChildren(null); if (autoSelect) { getSelectionModel().select(0, false); } } // JAWS does not work when disabling text selection setAllowTextSelection(false); sinkEvents(Event.ONSCROLL | Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS); } @Override protected void onAttach() { super.onAttach(); if (store == null) { throw new IllegalStateException("Cannot attach a tree without a store"); } update(); } protected void onCheckCascade(M model, CheckState checked) { switch (getCheckStyle()) { case PARENTS: if (checked == CheckState.CHECKED) { M p = store.getParent(model); while (p != null) { setChecked(p, CheckState.CHECKED); p = store.getParent(p); } } else { for (M child : store.getChildren(model)) { setChecked(child, CheckState.UNCHECKED); } } break; case CHILDREN: for (M child : store.getChildren(model)) { setChecked(child, checked); } break; case TRI: onTriCheckCascade(model, checked); break; case NONE: // do nothing break; } } protected void onCheckClick(Event event, TreeNode<M> node) { event.stopPropagation(); event.preventDefault(); setChecked(node.getModel(), (node.checked == CheckState.CHECKED || node.checked == CheckState.PARTIAL) ? CheckState.UNCHECKED : CheckState.CHECKED); } protected void onClear(StoreClearEvent<M> event) { clear(); } protected void onClick(Event event) { XEvent e = event.<XEvent>cast(); TreeNode<M> node = findNode(event.getEventTarget().<Element>cast()); if (node != null) { Element jointEl = view.getJointElement(node); if (jointEl != null && e.within(jointEl)) { toggle((M) node.getModel()); // necessary to prevent synthetic mouse events in case of touch event.preventDefault(); event.stopPropagation(); } Element checkEl = view.getCheckElement(node); if (checkable && checkEl != null && isEnabled() && e.within(checkEl)) { onCheckClick(event, node); } } focusEl.setXY(event.getClientX(), event.getClientY()); focus(); } protected void onCollapse(M model, TreeNode<M> node, boolean deep) { if (node.isExpanded() && fireCancellableEvent(new BeforeCollapseItemEvent<M>(model))) { node.setExpanded(false); // collapse view.collapse(node); List<M> l = new ArrayList<M>(); l.add(node.getModel()); update(); if (bufferRender) { cleanCollapsed(node.getModel()); } moveFocus(node.getElement()); fireEvent(new CollapseItemEvent<M>(model)); } if (deep) { setExpandChildren(model, false); } } protected void onDataChanged(StoreDataChangeEvent<M> event) { redraw(event.getParent()); } protected void onDoubleClick(Event event) { TreeNode<M> node = findNode(event.getEventTarget().<Element>cast()); if (node != null) { setExpanded(node.getModel(), !node.isExpanded()); } } protected void onExpand(M model, TreeNode<M> node, boolean deep) { if (!isLeaf(node.getModel())) { // 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) { store.removeChildren(model); node.setExpand(true); node.setExpandDeep(deep); node.setLoading(true); view.onLoading(node); loader.loadChildren(model); return; } if (!node.isExpanded() && fireCancellableEvent(new BeforeExpandItemEvent<M>(model))) { node.setExpanded(true); if (!node.isChildrenRendered()) { renderChildren(model); node.setChildrenRendered(true); } // expand view.expand(node); update(); fireEvent(new ExpandItemEvent<M>(model)); } if (deep) { setExpandChildren(model, true); } } } protected void onFilter(StoreFilterEvent<M> se) { if (isOrWasAttached()) { filtering = store.isEnableFilters(); clear(); renderChildren(null); if (expandOnFilter && store.isFiltered()) { expandAll(); } update(); } } protected void onRemove(StoreRemoveEvent<M> se) { TreeNode<M> node = findNode(se.getItem()); if (node != null) { if (view.getElement(node) != null) { // directly call node.getElement() since it won't be null node.getElement().removeFromParent(); } unregister(se.getItem()); TreeStoreRemoveEvent<M> remove = (TreeStoreRemoveEvent<M>) se; for (M child : remove.getChildren()) { unregister(child); } M itemParent = remove.getParent(); TreeNode<M> p = findNode(itemParent); if (p != null && p.isExpanded() && store.getChildCount(p.getModel()) == 0) { setExpanded(p.getModel(), false); } else if (p != null && store.getChildCount(p.getModel()) == 0) { refresh(itemParent); } moveFocus(node.getElement()); } } protected void onResize(int width, int height) { super.onResize(width, height); update(); constrainFocusElement(); } protected void onScroll(Event event) { update(); constrainFocusElement(); } protected void onSort(StoreSortEvent<M> se) { redraw(null); } protected void onTriCheckCascade(M model, CheckState checked) { if (checked == CheckState.CHECKED) { List<M> children = store.getAllChildren(model); cascade = false; for (M child : children) { TreeNode<M> n = findNode(child); if (n != null) { setChecked(child, checked); } } M parent = store.getParent(model); while (parent != null) { boolean allChildrenChecked = true; for (M child : store.getAllChildren(parent)) { TreeNode<M> n = findNode(child); if (n != null) { if (!isChecked(child)) { allChildrenChecked = false; } } } if (!allChildrenChecked) { setChecked(parent, CheckState.PARTIAL); } else { setChecked(parent, CheckState.CHECKED); } parent = store.getParent(parent); } cascade = true; } else if (checked == CheckState.UNCHECKED) { List<M> children = store.getAllChildren(model); cascade = false; for (M child : children) { setChecked(child, checked); } M parent = store.getParent(model); while (parent != null) { boolean anyChildChecked = false; for (M child : store.getAllChildren(parent)) { if (isChecked(child)) { anyChildChecked = true; } } if (anyChildChecked) { setChecked(parent, CheckState.PARTIAL); } else { setChecked(parent, CheckState.UNCHECKED); } parent = store.getParent(parent); } cascade = true; } } protected void onUpdate(StoreUpdateEvent<M> event) { for (M item : event.getItems()) { TreeNode<M> node = findNode(item); if (node != null) { if (node.model != item) { node.model = item; } refresh(item); } } } /** * Completely redraws the children of the given parent (or all items if parent is null), throwing away details like * currently expanded nodes, etc. Not designed to be used to update nodes, look into {@link #refresh(Object)} or * {@link Store#update(Object)}. * * @param parent the parent of the items to redraw */ protected void redraw(M parent) { if (!isOrWasAttached()) { return; } if (parent == null) { clear(); renderChildren(null); if (autoSelect) { M m = store.getChild(0); if (m != null) { List<M> sel = new ArrayList<M>(); sel.add(m); getSelectionModel().setSelection(sel); } } } else { TreeNode<M> n = findNode(parent); n.setLoaded(true); n.setLoading(false); if (n.isChildrenRendered()) { getContainer(parent).setInnerSafeHtml(SafeHtmlUtils.EMPTY_SAFE_HTML); } renderChildren(parent); if (n.isExpand() && !isLeaf(n.getModel())) { n.setExpand(false); boolean c = caching; caching = true; boolean deep = n.isExpandDeep(); n.setExpandDeep(false); setExpanded(parent, true, deep); caching = c; } else { refresh(parent); } } } protected String register(M m) { String id = generateModelId(m); if (nodes.containsKey(id)) { return nodes.get(id).getDomId(); } else { String domId = XDOM.getUniqueId(); TreeNode<M> node = new TreeNode<M>(id, m, domId); nodes.put(id, node); nodesByDom.put(domId, node); return domId; } } protected SafeHtml renderChild(M parent, M child, int depth, TreeViewRenderMode renderMode) { String domId = register(child); TreeNode<M> node = findNode(child); return view.getTemplate(child, domId, getCellContent(child), calculateIconStyle(child), isCheckable(node), node.checked, calculateJoint(child), depth, renderMode); } protected void renderChildren(M parent) { SafeHtmlBuilder markup = new SafeHtmlBuilder(); int depth = store.getDepth(parent); List<M> children = parent == null ? store.getRootItems() : store.getChildren(parent); if (children.size() == 0) { return; } for (M child : children) { register(child); } for (int i = 0; i < children.size(); i++) { TreeViewRenderMode mode = !bufferRender ? TreeViewRenderMode.ALL : TreeViewRenderMode.BUFFER_WRAP; markup.append(renderChild(parent, children.get(i), depth, mode)); } Element container = getContainer(parent); container.setInnerSafeHtml(markup.toSafeHtml()); for (int i = 0; i < children.size(); i++) { M child = children.get(i); TreeNode<M> node = findNode(child); if (autoExpand) { setExpanded(child, true); } else if (node.isExpand() && !isLeaf(node.getModel())) { node.setExpand(false); setExpanded(child, true); } else if (loader != null) { if (autoLoad) { if (store.isFiltered()) { renderChildren(child); } else { if (loader.hasChildren(child)) { loader.loadChildren(child); } } } } else if (autoLoad) { renderChildren(child); } } TreeNode<M> n = findNode(parent); if (n != null) { if (n.checked == CheckState.CHECKED) { switch (checkStyle) { case TRI: cascade = false; for (M child : store.getChildren(parent)) { setChecked(child, CheckState.CHECKED); } cascade = true; break; case CHILDREN: onCheckCascade(n.getModel(), n.checked); break; default: // empty } } n.setChildrenRendered(true); } if (parent == null) { ensureFocusElement(); } update(); } protected void unregister(M m) { if (m != null) { TreeNode<M> n = nodes.remove(generateModelId(m)); if (n != null) { nodesByDom.remove(n.getDomId()); n.clearElements(); } } } protected void update() { if (!bufferRender) { return; } if (updateTask == null) { updateTask = new DelayedTask() { @Override public void onExecute() { doUpdate(); } }; } updateTask.delay(view.getScrollDelay()); } private void ensureFocusElement() { if (focusEl != null) { focusEl.removeFromParent(); } focusEl = (XElement) getElement().appendChild(focusImpl.createFocusable()); focusEl.addClassName(CommonStyles.get().noFocusOutline()); if (focusEl.hasChildNodes()) { focusEl.getFirstChildElement().addClassName(CommonStyles.get().noFocusOutline()); com.google.gwt.dom.client.Style focusElStyle = focusEl.getFirstChildElement().getStyle(); focusElStyle.setBorderWidth(0, Unit.PX); focusElStyle.setFontSize(1, Unit.PX); focusElStyle.setPropertyPx("lineHeight", 1); } focusEl.setLeft(0); focusEl.setTop(0); focusEl.makePositionable(true); focusEl.addEventsSunk(Event.FOCUSEVENTS); } private void setExpandChildren(M m, boolean expand) { for (M child : store.getChildren(m)) { setExpanded(child, expand, true); } } }