/**
* Sencha GXT 3.1.0-beta - Sencha for GWT
* Copyright(c) 2007-2014, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
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.GXT;
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.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 text, 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();
if ((GXT.isIE6() || GXT.isIE7())) {
getElement().makePositionable();
}
setStore(store);
setSelectionModel(new TreeSelectionModel<M>());
view.bind(this);
}
@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() {
getContainer(null).<XElement> cast().removeChildren();
nodes.clear();
nodesByDom.clear();
if (isAttached()) {
moveFocus(getContainer(null));
}
}
protected void moveFocus(Element selectedElem) {
if (selectedElem == null) return;
int containerLeft = getAbsoluteLeft();
int containerTop = getAbsoluteTop();
int left = selectedElem.getAbsoluteLeft() - containerLeft;
int top = selectedElem.getAbsoluteTop() - containerTop;
int width = selectedElem.getOffsetWidth();
int height = selectedElem.getOffsetHeight();
if (width == 0 || height == 0) {
focusEl.setLeftTop(0, 0);
return;
}
focusEl.setLeftTop(left, top);
}
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, update the innerHTML to nothing
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().setInnerHTML(html.asString());
}
}
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) ? SafeHtmlUtils.fromTrustedString(" ") : 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().asString());
} else if (index == parentChildCount - event.getItems().size()) {
DomHelper.insertHtml("beforeEnd", getContainer(parent), sb.toSafeHtml().asString());
} else {
DomHelper.insertBefore((Element) getContainer(parent).getChild(index).cast(), sb.toSafeHtml().asString());
}
}
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.setInnerHTML(builder.toSafeHtml().asString());
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());
}
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).setInnerHTML("");
}
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.setInnerHTML(markup.toSafeHtml().asString());
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);
}
}
}