First implementation of TreeView as a widget, without depending on Tree.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7615 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/sample/tree/client/MyTreeViewModel.java b/bikeshed/src/com/google/gwt/sample/tree/client/MyTreeViewModel.java
index a5e025f..aff8791 100644
--- a/bikeshed/src/com/google/gwt/sample/tree/client/MyTreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/sample/tree/client/MyTreeViewModel.java
@@ -87,8 +87,6 @@
public <T> NodeInfo<?> getNodeInfo(T value, TreeNodeView<T> treeNodeView) {
if (value instanceof String) {
return getNodeInfoHelper((String) value);
- } else if (value instanceof Integer) {
- return getNodeInfoHelper((Integer) value);
}
// Unhandled type.
@@ -96,9 +94,8 @@
throw new IllegalArgumentException("Unsupported object type: " + type);
}
- @SuppressWarnings("unused")
- private NodeInfo<?> getNodeInfoHelper(final Integer value) {
- return null;
+ public boolean isLeaf(Object value) {
+ return value instanceof Integer;
}
private NodeInfo<?> getNodeInfoHelper(final String value) {
diff --git a/bikeshed/src/com/google/gwt/sample/tree/client/TreeNodeView.java b/bikeshed/src/com/google/gwt/sample/tree/client/TreeNodeView.java
index 4cc54fa..87f8576 100644
--- a/bikeshed/src/com/google/gwt/sample/tree/client/TreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/sample/tree/client/TreeNodeView.java
@@ -16,12 +16,9 @@
package com.google.gwt.sample.tree.client;
import com.google.gwt.cells.client.Cell;
-import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.list.shared.ListEvent;
import com.google.gwt.list.shared.ListHandler;
import com.google.gwt.list.shared.ListModel;
@@ -29,7 +26,6 @@
import com.google.gwt.list.shared.SizeChangeEvent;
import com.google.gwt.sample.tree.client.TreeViewModel.NodeInfo;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.TreeItem;
import java.util.ArrayList;
import java.util.List;
@@ -42,61 +38,9 @@
public class TreeNodeView<T> extends Composite {
/**
- * A {@link TreeItem} that fires value change events when the state changes.
+ * The element used in place of an image when a node has no children.
*/
- public static class ExtraTreeItem extends TreeItem implements
- HasValueChangeHandlers<Boolean> {
-
- private HandlerManager handlerManager = new HandlerManager(this);
-
- public ExtraTreeItem(String value) {
- super(value);
- }
-
- public HandlerRegistration addValueChangeHandler(
- ValueChangeHandler<Boolean> handler) {
- return handlerManager.addHandler(ValueChangeEvent.getType(), handler);
- }
-
- public void fireEvent(GwtEvent<?> event) {
- handlerManager.fireEvent(event);
- }
-
- @Override
- public void setState(boolean open, boolean fireEvents) {
- super.setState(open, fireEvents);
- if (open) {
- ValueChangeEvent.fire(this, true);
- } else {
- ValueChangeEvent.fire(this, false);
- }
- }
- }
-
- /**
- * The list registration for the list of children.
- */
- private ListRegistration listReg;
-
- /**
- * The TreeItem that displays this node.
- */
- private ExtraTreeItem treeItem;
-
- /**
- * This node's value.
- */
- private T value;
-
- /**
- * The parent {@link TreeNodeView}.
- */
- private TreeNodeView<?> parent;
-
- /**
- * The containing {@link TreeView}.
- */
- private TreeView tree;
+ private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
/**
* The children of this {@link TreeNodeView}.
@@ -104,29 +48,67 @@
private List<TreeNodeView<?>> children;
/**
- * The info about the child nodes.
+ * A reference to the element that contains the children.
+ */
+ private Element childContainer;
+
+ /**
+ * The list registration for the list of children.
+ */
+ private ListRegistration listReg;
+
+ /**
+ * The info about children of this node.
*/
private NodeInfo<?> nodeInfo;
/**
+ * Indicates whether or not we've loaded the node info.
+ */
+ private boolean nodeInfoLoaded;
+
+ /**
+ * Indicates whether or not this node is open.
+ */
+ private boolean open;
+
+ /**
+ * The parent {@link TreeNodeView}.
+ */
+ private TreeNodeView<?> parent;
+
+ /**
* The info about this node.
*/
private NodeInfo<T> parentNodeInfo;
/**
+ * The containing {@link TreeView}.
+ */
+ private TreeView tree;
+
+ /**
+ * This node's value.
+ */
+ private T value;
+
+ /**
* Construct a {@link TreeNodeView}.
*
- * @param value the value of this node
* @param tree the parent {@link TreeView}
- * @param treeItem this nodes view
+ * @param parent the parent {@link TreeNodeView}
+ * @param parentNodeInfo the {@link NodeInfo} of the parent
+ * @param elem the outer element of this {@link TreeNodeView}.
+ * @param value the value of this node
*/
- TreeNodeView(T value, final TreeView tree, final TreeNodeView<?> parent,
- ExtraTreeItem treeItem, NodeInfo<T> parentNodeInfo) {
+ TreeNodeView(final TreeView tree, final TreeNodeView<?> parent,
+ NodeInfo<T> parentNodeInfo, Element elem, T value) {
this.value = value;
this.tree = tree;
this.parent = parent;
- this.treeItem = treeItem;
+ // We pass in parentNodeInfo so we know that it is type T.
this.parentNodeInfo = parentNodeInfo;
+ setElement(elem);
}
/**
@@ -158,6 +140,15 @@
}
/**
+ * Check whether or not this {@link TreeNodeView} is open.
+ *
+ * @return true if open, false if closed
+ */
+ public boolean getState() {
+ return open;
+ }
+
+ /**
* Get the value contained in this node.
*
* @return the value of the node
@@ -166,90 +157,180 @@
return value;
}
- NodeInfo<?> getNodeInfo() {
- return nodeInfo;
+ /**
+ * Sets whether this item's children are displayed.
+ *
+ * @param open whether the item is open
+ */
+ public void setState(boolean open) {
+ setState(open, true);
+ }
+
+ /**
+ * Sets whether this item's children are displayed.
+ *
+ * @param open whether the item is open
+ * @param fireEvents <code>true</code> to allow open/close events to be
+ */
+ public void setState(boolean open, boolean fireEvents) {
+ // TODO(jlabanca) - allow people to add open/close handlers.
+
+ // Early out.
+ if (this.open == open) {
+ return;
+ }
+
+ this.open = open;
+ if (open) {
+ if (!nodeInfoLoaded) {
+ nodeInfo = tree.getTreeViewModel().getNodeInfo(value, this);
+ nodeInfoLoaded = true;
+ }
+ if (nodeInfo != null) {
+ onOpen(nodeInfo);
+ }
+ } else {
+ // Unregister the list handler.
+ if (listReg != null) {
+ listReg.removeHandler();
+ listReg = null;
+ }
+
+ // Remove the children.
+ childContainer.setInnerHTML("");
+ children.clear();
+ }
+
+ // Update the image.
+ updateImage();
+ }
+
+ /**
+ * Fire an event to the {@link Cell}.
+ *
+ * @param event the native event
+ */
+ void fireEventToCell(NativeEvent event) {
+ parentNodeInfo.onBrowserEvent(getCellParent(), value, event);
+ }
+
+ /**
+ * @return the element that contains the rendered cell
+ */
+ Element getCellParent() {
+ return getElement().getChild(1).cast();
+ }
+
+ /**
+ * @return the image element
+ */
+ Element getImageElement() {
+ return getElement().getFirstChildElement();
}
NodeInfo<T> getParentNodeInfo() {
return parentNodeInfo;
}
- TreeItem getTreeItem() {
- return treeItem;
- }
-
/**
- * Initialize the node info.
+ * Set the {@link Element} that will contain the children. Used by
+ * {@link TreeView}.
*
- * @param nodeInfo the {@link NodeInfo} that provides information about the
- * child values
+ * @param elem the child container element
*/
- void initNodeInfo(final NodeInfo<?> nodeInfo) {
- // Force a + icon if this node might have children.
- if (nodeInfo != null) {
- this.nodeInfo = nodeInfo;
- treeItem.addItem("loading...");
- treeItem.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
- public void onValueChange(ValueChangeEvent<Boolean> event) {
- if (event.getValue()) {
- onOpen(tree, nodeInfo);
- } else {
- onClose();
- }
- }
- });
- }
- }
-
- /**
- * Cleanup when the node is closed.
- */
- private void onClose() {
- if (listReg != null) {
- listReg.removeHandler();
- listReg = null;
- }
+ void initChildContainer(Element elem) {
+ assert this.childContainer == null : "childContainer already initialized.";
+ this.childContainer = elem;
}
/**
* Setup the node when it is opened.
*
- * @param tree the parent {@link TreeView}
* @param nodeInfo the {@link NodeInfo} that provides information about the
* child values
* @param <C> the child data type of the node.
*/
- private <C> void onOpen(final TreeView tree, final NodeInfo<C> nodeInfo) {
+ private <C> void onOpen(final NodeInfo<C> nodeInfo) {
+ // Get the node info.
ListModel<C> listModel = nodeInfo.getListModel();
listReg = listModel.addListHandler(new ListHandler<C>() {
public void onDataChanged(ListEvent<C> event) {
// TODO - handle event start and length
- treeItem.removeItems();
- // Add child tree items.
+ // Construct the child contents.
+ TreeViewModel model = tree.getTreeViewModel();
+ int imageWidth = tree.getImageWidth();
Cell<C> theCell = nodeInfo.getCell();
+ StringBuilder sb = new StringBuilder();
children = new ArrayList<TreeNodeView<?>>();
for (C childValue : event.getValues()) {
- // TODO(jlabanca): Use one StringBuilder.
- StringBuilder sb = new StringBuilder();
+ sb.append("<div style=\"position:relative;padding-left:");
+ sb.append(imageWidth);
+ sb.append("px;\">");
+ if (model.isLeaf(childValue)) {
+ sb.append(LEAF_IMAGE);
+ } else {
+ sb.append(tree.getClosedImageHtml());
+ }
+ sb.append("<div>");
theCell.render(childValue, sb);
- ExtraTreeItem child = new ExtraTreeItem(sb.toString());
- treeItem.addItem(child);
- children.add(tree.createChildView(childValue, TreeNodeView.this,
- child, nodeInfo));
+ sb.append("</div>");
+ sb.append("</div>");
+ }
+
+ // Replace contents of the child container.
+ if (childContainer == null) {
+ Element elem = getElement();
+ initChildContainer(Document.get().createDivElement());
+ elem.appendChild(childContainer);
+ }
+ childContainer.setInnerHTML(sb.toString());
+
+ // Create the child TreeNodeViews from the new elements.
+ children = new ArrayList<TreeNodeView<?>>();
+ Element childElem = childContainer.getFirstChildElement();
+ for (C childValue : event.getValues()) {
+ TreeNodeView<C> child = new TreeNodeView<C>(tree, TreeNodeView.this,
+ nodeInfo, childElem, childValue);
+ children.add(child);
+ childElem = childElem.getNextSiblingElement();
}
}
public void onSizeChanged(SizeChangeEvent event) {
- // TODO (jlabanca): Handle case when item is over.
+ if (children == null) {
+ return;
+ }
+
+ // Shrink the list based on the new size.
int size = event.getSize();
- treeItem.removeItems();
- if (size > 0) {
- // Add a placeholder to force a + icon.
- treeItem.addItem("loading...");
+ int currentSize = children.size();
+ for (int i = currentSize - 1; i >= size; i--) {
+ childContainer.getLastChild().removeFromParent();
+ children.remove(i);
}
}
});
listReg.setRangeOfInterest(0, 100);
}
+
+ /**
+ * Update the image based on the current state.
+ */
+ private void updateImage() {
+ // Early out if this is a root node.
+ if (getParentTreeNodeView() == null) {
+ return;
+ }
+
+ // Replace the image element with a new one.
+ String html = open ? tree.getOpenImageHtml() : tree.getClosedImageHtml();
+ if (nodeInfoLoaded && nodeInfo == null) {
+ html = LEAF_IMAGE;
+ }
+ Element tmp = Document.get().createDivElement();
+ tmp.setInnerHTML(html);
+ Element imageElem = tmp.getFirstChildElement();
+ getElement().replaceChild(imageElem, getImageElement());
+ }
}
diff --git a/bikeshed/src/com/google/gwt/sample/tree/client/TreeView.java b/bikeshed/src/com/google/gwt/sample/tree/client/TreeView.java
index 20c91d1..d31d99b 100644
--- a/bikeshed/src/com/google/gwt/sample/tree/client/TreeView.java
+++ b/bikeshed/src/com/google/gwt/sample/tree/client/TreeView.java
@@ -15,22 +15,47 @@
*/
package com.google.gwt.sample.tree.client;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.sample.tree.client.TreeNodeView.ExtraTreeItem;
-import com.google.gwt.sample.tree.client.TreeViewModel.NodeInfo;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.Tree;
-import com.google.gwt.user.client.ui.TreeItem;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.Widget;
import java.util.ArrayList;
/**
* A view of a tree.
*/
-public class TreeView extends Composite {
+public class TreeView extends Widget {
+
+ private static final Resources DEFAULT_RESOURCES = GWT.create(Resources.class);
+
+ /**
+ * A ClientBundle that provides images for this widget.
+ */
+ public interface Resources extends ClientBundle {
+
+ /**
+ * An image indicating a closed branch.
+ */
+ ImageResource treeClosed();
+
+ /**
+ * An image indicating an open branch.
+ */
+ ImageResource treeOpen();
+ }
+
+ /**
+ * The HTML used to generate the closed image.
+ */
+ private String closedImageHtml;
/**
* The {@link TreeViewModel} that backs the tree.
@@ -38,6 +63,16 @@
private TreeViewModel model;
/**
+ * The HTML used to generate the open image.
+ */
+ private String openImageHtml;
+
+ /**
+ * The Resources used by this tree.
+ */
+ private Resources resources;
+
+ /**
* The hidden root node in the tree.
*/
private TreeNodeView<?> rootNode;
@@ -51,56 +86,72 @@
*/
public <T> TreeView(TreeViewModel viewModel, T rootValue) {
this.model = viewModel;
+ this.resources = DEFAULT_RESOURCES;
+ setElement(Document.get().createDivElement());
+ getElement().getStyle().setPosition(Position.RELATIVE);
+ setStyleName("gwt-TreeView");
- // Initialize the widget.
- Tree tree = new Tree() {
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- switch (event.getTypeInt()) {
- case Event.ONMOUSEUP: {
- if ((DOM.eventGetCurrentTarget(event) == getElement())
- && (event.getButton() == Event.BUTTON_LEFT)) {
- elementClicked(DOM.eventGetTarget(event), event);
- }
- break;
- }
- }
- }
- };
- initWidget(tree);
- sinkEvents(Event.ONMOUSEUP);
-
- // Add the root item.
- ExtraTreeItem rootItem = new ExtraTreeItem("Dummy UI Root");
- tree.addItem(rootItem);
+ // Add event handlers.
+ sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP);
// Associate a view with the item.
- rootNode = createChildView(rootValue, null, rootItem, null);
+ rootNode = new TreeNodeView<T>(this, null, null, getElement(), rootValue);
+ rootNode.initChildContainer(getElement());
+ rootNode.setState(true);
}
public TreeViewModel getTreeViewModel() {
return model;
}
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ int eventType = DOM.eventGetType(event);
+ switch (eventType) {
+ case Event.ONMOUSEUP:
+ Element currentTarget = event.getCurrentEventTarget().cast();
+ if (currentTarget == getElement()) {
+ Element target = event.getEventTarget().cast();
+ elementClicked(target, event);
+ }
+ break;
+ }
+ }
+
/**
- * Create a child view for this tree.
- *
- * @param <C> the data type of the child
- * @param childValue the value of the child
- * @param parentTreeNodeView the parent {@link TreeNodeView}
- * @param childItem the DOM view of the child
- * @return a {@link TreeNodeView} for the child
+ * @return the HTML to render the closed image.
*/
- <C> TreeNodeView<C> createChildView(C childValue,
- TreeNodeView<?> parentTreeNodeView, ExtraTreeItem childItem,
- NodeInfo<C> parentNodeInfo) {
- TreeNodeView<C> childView = new TreeNodeView<C>(childValue, this,
- parentTreeNodeView, childItem, parentNodeInfo);
- NodeInfo<?> nodeInfo = model.getNodeInfo(childValue, childView);
- childView.initNodeInfo(nodeInfo);
- return childView;
+ String getClosedImageHtml() {
+ if (closedImageHtml == null) {
+ AbstractImagePrototype proto = AbstractImagePrototype.create(resources.treeClosed());
+ closedImageHtml = proto.getHTML().replace("style='",
+ "style='position:absolute;left:0px;top:0px;");
+ }
+ return closedImageHtml;
+ }
+
+ /**
+ * Get the width required for the images.
+ *
+ * @return the maximum width required for images.
+ */
+ int getImageWidth() {
+ return Math.max(resources.treeClosed().getWidth(),
+ resources.treeOpen().getWidth());
+ }
+
+ /**
+ * @return the HTML to render the open image.
+ */
+ String getOpenImageHtml() {
+ if (openImageHtml == null) {
+ AbstractImagePrototype proto = AbstractImagePrototype.create(resources.treeOpen());
+ openImageHtml = proto.getHTML().replace("style='",
+ "style='position:absolute;left:0px;top:0px;");
+ }
+ return openImageHtml;
}
/**
@@ -112,7 +163,7 @@
return;
}
- collectElementChain(chain, hRoot, DOM.getParent(hElem));
+ collectElementChain(chain, hRoot, hElem.getParentElement());
chain.add(hElem);
}
@@ -122,9 +173,11 @@
TreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode);
if (nodeView != null && nodeView != rootNode) {
- TreeItem item = nodeView.getTreeItem();
- if (DOM.isOrHasChild(item.getElement(), hElem)) {
- fireEvent(nodeView, event);
+ if (nodeView.getImageElement().isOrHasChild(hElem)) {
+ nodeView.setState(!nodeView.getState(), true);
+ return true;
+ } else if (nodeView.getCellParent().isOrHasChild(hElem)) {
+ nodeView.fireEventToCell(event);
return true;
}
}
@@ -141,7 +194,7 @@
Element hCurElem = chain.get(idx);
for (int i = 0, n = parent.getChildCount(); i < n; ++i) {
TreeNodeView<?> child = parent.getChild(i);
- if (child.getTreeItem().getElement() == hCurElem) {
+ if (child.getElement() == hCurElem) {
TreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child);
if (retItem == null) {
return child;
@@ -152,9 +205,4 @@
return findItemByChain(chain, idx + 1, parent);
}
-
- private <T> void fireEvent(TreeNodeView<T> nodeView, NativeEvent event) {
- nodeView.getParentNodeInfo().onBrowserEvent(
- nodeView.getTreeItem().getElement(), nodeView.getValue(), event);
- }
}
diff --git a/bikeshed/src/com/google/gwt/sample/tree/client/TreeViewModel.java b/bikeshed/src/com/google/gwt/sample/tree/client/TreeViewModel.java
index 20aacbe..d45a9e9 100644
--- a/bikeshed/src/com/google/gwt/sample/tree/client/TreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/sample/tree/client/TreeViewModel.java
@@ -109,4 +109,13 @@
* @return the {@link NodeInfo}
*/
<T> NodeInfo<?> getNodeInfo(T value, TreeNodeView<T> treeNodeView);
+
+ /**
+ * Check if the value is known to be a leaf node.
+ *
+ * @param value the value at the node
+ * @return true if the node is known to be a leaf node, false if not
+ */
+
+ boolean isLeaf(Object value);
}
diff --git a/bikeshed/src/com/google/gwt/sample/tree/client/treeClosed.gif b/bikeshed/src/com/google/gwt/sample/tree/client/treeClosed.gif
new file mode 100644
index 0000000..7bda586
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/tree/client/treeClosed.gif
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/tree/client/treeOpen.gif b/bikeshed/src/com/google/gwt/sample/tree/client/treeOpen.gif
new file mode 100644
index 0000000..0fcf791
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/tree/client/treeOpen.gif
Binary files differ