You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2011/06/14 02:40:55 UTC
svn commit: r1135351 - in /tapestry/tapestry5/trunk/tapestry-core/src:
main/java/org/apache/tapestry5/corelib/components/
main/java/org/apache/tapestry5/internal/services/javascript/
main/java/org/apache/tapestry5/tree/ main/resources/org/apache/tapest...
Author: hlship
Date: Tue Jun 14 00:40:55 2011
New Revision: 1135351
URL: http://svn.apache.org/viewvc?rev=1135351&view=rev
Log:
Copy TapX Tree component into tapestry-core
Added:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeExpansionModel.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeModel.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeExpansionModel.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModel.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModelAdapter.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeNode.java
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Tree.tml
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branch.png
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branchend.png
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-sprites.png
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-vpipe.png
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.css
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.js
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FileSystemTreeModel.java
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/TreeDemo.java
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/TreeDemo.tml
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,294 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.corelib.components;
+
+import java.util.List;
+
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.Block;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.Link;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.Persist;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.dom.Element;
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Flow;
+import org.apache.tapestry5.func.Worker;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.runtime.RenderQueue;
+import org.apache.tapestry5.services.javascript.JavaScriptSupport;
+import org.apache.tapestry5.tree.DefaultTreeExpansionModel;
+import org.apache.tapestry5.tree.TreeExpansionModel;
+import org.apache.tapestry5.tree.TreeModel;
+import org.apache.tapestry5.tree.TreeNode;
+
+/**
+ * A component used to render a recursive tree structure, with expandable/collapsable nodes. The data that is displayed
+ * by the component is provided as a {@link TreeModel}. A secondary model, the {@link TreeExpansionModel}, is used
+ * to track which nodes have been expanded. The Tree component uses special tricks to support recursive rendering
+ * of the Tree as necessary.
+ *
+ * @since 5.3.0
+ */
+@SuppressWarnings(
+{ "rawtypes", "unchecked", "unused" })
+public class Tree
+{
+ /**
+ * The model that drives the tree, determining top level nodes and making revealing the overall structure of the
+ * tree.
+ */
+ @Parameter(required = true, autoconnect = true)
+ private TreeModel model;
+
+ /**
+ * Allows the container to specify additional CSS class names for the outer DIV element. The outer DIV
+ * always has the class name "t-tree-container"; the additional class names are typically used to apply
+ * a specific size and width to the component.
+ */
+ @Parameter(name = "class", defaultPrefix = BindingConstants.LITERAL)
+ private String className;
+
+ /**
+ * Optional parameter used to inform the container about what TreeNode is currently rendering; this
+ * is primarily used when the label parameter is bound.
+ */
+ @Property
+ @Parameter
+ private TreeNode node;
+
+ /**
+ * Used to control the Tree's expansion model. By default, a persistent field inside the Tree
+ * component stores a {@link DefaultTreeExpansionModel}. This parameter may be bound when more
+ * control over the implementation of the expansion model, or how it is stored, is
+ * required.
+ */
+ @Parameter(allowNull = false, value = "defaultTreeExpansionModel")
+ private TreeExpansionModel expansionModel;
+
+ /**
+ * Optional parameter used to inform the container about the value of the currently rendering TreeNode; this
+ * is often preferable to the TreeNode, and like the node parameter, is primarily used when the label parameter
+ * it bound.
+ */
+ @Parameter
+ private Object value;
+
+ /**
+ * A renderable (usually a {@link Block}) that can render the label for a tree node.
+ * This will be invoked after the {@link #value} parameter has been updated.
+ */
+ @Property
+ @Parameter(value = "block:defaultRenderTreeNodeLabel")
+ private RenderCommand label;
+
+ @Environmental
+ private JavaScriptSupport jss;
+
+ @Inject
+ private ComponentResources resources;
+
+ @Persist
+ private TreeExpansionModel defaultTreeExpansionModel;
+
+ private static RenderCommand RENDER_CLOSE_TAG = new RenderCommand()
+ {
+ public void render(MarkupWriter writer, RenderQueue queue)
+ {
+ writer.end();
+ };
+ };
+
+ private static RenderCommand RENDER_LABEL_SPAN = new RenderCommand()
+ {
+ public void render(MarkupWriter writer, RenderQueue queue)
+ {
+ writer.element("span", "class", "t-tree-label");
+ };
+ };
+
+ /**
+ * Renders a single node (which may be the last within its containing node).
+ * This is a mix of immediate rendering, and queuing up various Blocks and Render commands
+ * to do the rest. May recursively render child nodes of the active node.
+ *
+ * @param node
+ * to render
+ * @param isLast
+ * if true, add "t-last" attribute to the LI element
+ * @return command to render the node
+ */
+ private RenderCommand toRenderCommand(final TreeNode node, final boolean isLast)
+ {
+ return new RenderCommand()
+ {
+ public void render(MarkupWriter writer, RenderQueue queue)
+ {
+ // Inform the component's container about what value is being rendered
+ // (this may be necessary to generate the correct label for the node).
+ Tree.this.node = node;
+
+ value = node.getValue();
+
+ writer.element("li");
+
+ if (isLast)
+ writer.attributes("class", "t-last");
+
+ Element e = writer.element("span", "class", "t-tree-icon");
+
+ if (node.isLeaf())
+ e.addClassName("t-leaf-node");
+ else if (!node.getHasChildren())
+ e.addClassName("t-empty-node");
+
+ boolean hasChildren = !node.isLeaf() && node.getHasChildren();
+ boolean expanded = hasChildren && expansionModel.isExpanded(node);
+
+ if (hasChildren)
+ {
+ String clientId = jss.allocateClientId(resources);
+
+ e.attribute("id", clientId);
+
+ Link expandChildren = resources.createEventLink("expandChildren", node.getId());
+ Link markExpanded = resources.createEventLink("markExpanded", node.getId());
+ Link markCollapsed = resources.createEventLink("markCollapsed", node.getId());
+
+ JSONObject spec = new JSONObject("clientId", clientId,
+
+ "expandChildrenURL", expandChildren.toString(),
+
+ "markExpandedURL", markExpanded.toString(),
+
+ "markCollapsedURL", markCollapsed.toString());
+
+ if (expanded)
+ spec.put("expanded", true);
+
+ jss.addInitializerCall("treeNode", spec);
+ }
+
+ writer.end(); // span.tx-tree-icon
+
+ // From here on in, we're pushing things onto the queue. Remember that
+ // execution order is reversed from order commands are pushed.
+
+ queue.push(RENDER_CLOSE_TAG); // li
+
+ if (expanded)
+ {
+ queue.push(new RenderNodes(node.getChildren()));
+ }
+
+ queue.push(RENDER_CLOSE_TAG);
+ queue.push(label);
+ queue.push(RENDER_LABEL_SPAN);
+
+ }
+ };
+ }
+
+ /** Renders an <ul> element and renders each node recusively inside the element. */
+ private class RenderNodes implements RenderCommand
+ {
+ private final Flow<TreeNode> nodes;
+
+ public RenderNodes(List<TreeNode> nodes)
+ {
+ assert !nodes.isEmpty();
+
+ this.nodes = F.flow(nodes).reverse();
+ }
+
+ public void render(MarkupWriter writer, final RenderQueue queue)
+ {
+ writer.element("ul");
+ queue.push(RENDER_CLOSE_TAG);
+
+ queue.push(toRenderCommand(nodes.first(), true));
+
+ nodes.rest().each(new Worker<TreeNode>()
+ {
+ public void work(TreeNode element)
+ {
+ queue.push(toRenderCommand(element, false));
+ }
+ });
+ }
+ }
+
+ public String getContainerClass()
+ {
+ return className == null ? "t-tree-container" : "t-tree-container " + className;
+ }
+
+ Object onExpandChildren(String nodeId)
+ {
+ TreeNode container = model.getById(nodeId);
+
+ expansionModel.markExpanded(container);
+
+ return new RenderNodes(container.getChildren());
+ }
+
+ Object onMarkExpanded(String nodeId)
+ {
+ expansionModel.markExpanded(model.getById(nodeId));
+
+ return new JSONObject();
+ }
+
+ Object onMarkCollapsed(String nodeId)
+ {
+ expansionModel.markCollapsed(model.getById(nodeId));
+
+ return new JSONObject();
+ }
+
+ public TreeExpansionModel getDefaultTreeExpansionModel()
+ {
+ if (defaultTreeExpansionModel == null)
+ defaultTreeExpansionModel = new DefaultTreeExpansionModel();
+
+ return defaultTreeExpansionModel;
+ }
+
+ /**
+ * Returns the actual {@link TreeExpansionModel} in use for this Tree component,
+ * as per the expansionModel parameter. This is often, but not always, the same
+ * as {@link #getDefaultTreeExpansionModel()}.
+ */
+ public TreeExpansionModel getExpansionModel()
+ {
+ return expansionModel;
+ }
+
+ public Object getRenderRootNodes()
+ {
+ return new RenderNodes(model.getRootNodes());
+ }
+
+ /** Clears the tree's {@link TreeExpansionModel}. */
+ public void clearExpansions()
+ {
+ expansionModel.clear();
+ }
+}
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java?rev=1135351&r1=1135350&r2=1135351&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java Tue Jun 14 00:40:55 2011
@@ -69,17 +69,20 @@ public class CoreJavaScriptStack impleme
ROOT + "/t5-core.js",
ROOT + "/t5-arrays.js",
-
+
ROOT + "/t5-init.js",
-
+
ROOT + "/t5-pubsub.js",
- ROOT + "/tapestry.js" };
+ ROOT + "/tapestry.js",
+
+ ROOT + "/tree.js" };
// Because of changes to the logic of how stylesheets get incorporated, the default stylesheet
// was removed, the logic for it is now in TapestryModule.contributeMarkupRenderer().
- private static final String[] CORE_STYLESHEET = new String[0];
+ private static final String[] CORE_STYLESHEET = new String[]
+ { ROOT + "/tree.css" };
public CoreJavaScriptStack(@Symbol(SymbolConstants.PRODUCTION_MODE)
boolean productionMode,
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeExpansionModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeExpansionModel.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeExpansionModel.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeExpansionModel.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,64 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.tree;
+
+import java.util.Set;
+
+import org.apache.tapestry5.BaseOptimizedSessionPersistedObject;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+
+/**
+ * Manages a Set of String {@link TreeNode} ids.
+ *
+ * @param <T>
+ * @since 5.3.0
+ * @see TreeModel
+ */
+public class DefaultTreeExpansionModel<T> extends BaseOptimizedSessionPersistedObject implements TreeExpansionModel<T>
+{
+ private final Set<String> expandedIds = CollectionFactory.newSet();
+
+ public boolean isExpanded(TreeNode<T> node)
+ {
+ assert node != null;
+
+ return expandedIds.contains(node.getId());
+ }
+
+ public void markExpanded(TreeNode<T> node)
+ {
+ assert node != null;
+
+ if (expandedIds.add(node.getId()))
+ markDirty();
+ }
+
+ public void markCollapsed(TreeNode<T> node)
+ {
+ assert node != null;
+
+ if (expandedIds.remove(node.getId()))
+ markDirty();
+ }
+
+ public void clear()
+ {
+ if (!expandedIds.isEmpty())
+ {
+ expandedIds.clear();
+ markDirty();
+ }
+ }
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeModel.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeModel.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/DefaultTreeModel.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,189 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.tree;
+
+import java.util.Collections;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry5.ValueEncoder;
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+
+/**
+ * A default implementation of TreeModel that starts with a {@link ValueEncoder} (for the element to string conversion),
+ * a {@link TreeModelAdapter}, and a list of root nodes.
+ * <p>
+ * This implementation is <em>not</em> thread safe.
+ *
+ * @param <T>
+ * @since 5.3.0
+ */
+public class DefaultTreeModel<T> implements TreeModel<T>
+{
+ private final ValueEncoder<T> encoder;
+
+ private final TreeModelAdapter<T> adapter;
+
+ private final List<TreeNode<T>> roots;
+
+ private final Map<String, TreeNode<T>> cache = CollectionFactory.newMap();
+
+ private final Mapper<T, TreeNode<T>> toTreeNode = new Mapper<T, TreeNode<T>>()
+ {
+ public TreeNode<T> map(T value)
+ {
+ return new DefaultTreeNode(value);
+ };
+ };
+
+ private class DefaultTreeNode implements TreeNode<T>
+ {
+ private final T value;
+
+ private List<TreeNode<T>> children;
+
+ DefaultTreeNode(T value)
+ {
+ this.value = value;
+ }
+
+ public String getId()
+ {
+ return encoder.toClient(value);
+ }
+
+ public T getValue()
+ {
+ return value;
+ }
+
+ public boolean isLeaf()
+ {
+ return adapter.isLeaf(value);
+ }
+
+ public boolean getHasChildren()
+ {
+ return adapter.hasChildren(value);
+ }
+
+ public List<TreeNode<T>> getChildren()
+ {
+ if (children == null)
+ children = F.flow(adapter.getChildren(value)).map(toTreeNode).toList();
+
+ return children;
+ }
+
+ public String getLabel()
+ {
+ return adapter.getLabel(value);
+ }
+
+ }
+
+ /**
+ * Creates a new model starting from a single root element.
+ *
+ * @param encoder
+ * @param adapter
+ * @param root
+ */
+ public DefaultTreeModel(ValueEncoder<T> encoder, TreeModelAdapter<T> adapter, T root)
+ {
+ this(encoder, adapter, Collections.singletonList(root));
+ }
+
+ /**
+ * Standard constructor.
+ *
+ * @param encoder
+ * used to convert values to strings and vice-versa
+ * @param adapter
+ * adapts elements to the tree
+ * @param roots
+ * defines the root nodes of the model
+ */
+ public DefaultTreeModel(ValueEncoder<T> encoder, TreeModelAdapter<T> adapter, List<T> roots)
+ {
+ assert encoder != null;
+ assert adapter != null;
+ assert roots != null;
+ assert !roots.isEmpty();
+
+ this.encoder = encoder;
+ this.adapter = adapter;
+ this.roots = F.flow(roots).map(toTreeNode).toList();
+ }
+
+ public List<TreeNode<T>> getRootNodes()
+ {
+ return roots;
+ }
+
+ public TreeNode<T> getById(String id)
+ {
+ assert id != null;
+
+ TreeNode<T> result = findById(id);
+
+ if (result == null)
+ throw new IllegalArgumentException(String.format("Could not locate TreeNode '%s'.", id));
+
+ return result;
+ }
+
+ private TreeNode<T> findById(String id)
+ {
+ TreeNode<T> result = cache.get(id);
+
+ if (result != null)
+ return result;
+
+ Deque<TreeNode<T>> queue = new LinkedList<TreeNode<T>>(roots);
+
+ while (!queue.isEmpty())
+ {
+ TreeNode<T> node = queue.removeFirst();
+
+ String nodeId = node.getId();
+
+ cache.put(nodeId, node);
+
+ if (nodeId.equals(id))
+ return node;
+
+ if (!node.isLeaf() && node.getHasChildren())
+ {
+ for (TreeNode<T> child : node.getChildren())
+ {
+ queue.addFirst(child);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public TreeNode<T> find(T element)
+ {
+ return findById(encoder.toClient(element));
+ }
+
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeExpansionModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeExpansionModel.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeExpansionModel.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeExpansionModel.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,47 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.tree;
+
+import org.apache.tapestry5.corelib.components.Tree;
+
+/**
+ * Tracks which nodes of a {@link TreeModel} are currently expanded. The {@linkplain DefaultTreeExpansionModel default
+ * implementation} simply stores a set of {@linkplain TreeNode#getId() unique node
+ * ids} to identify expanded nodes. The expansion model is updated whenever folders are expanded or
+ * collapsed on the client side.
+ *
+ * @since 5.3.0
+ * @see Tree
+ */
+public interface TreeExpansionModel<T>
+{
+ /**
+ * Returns true if the node has been previously {@linkplain #markExpanded(TreeNode) expanded}.
+ *
+ * @param node
+ * node to check for expansion
+ * @return
+ */
+ boolean isExpanded(TreeNode<T> node);
+
+ /** Marks the node as expanded. */
+ void markExpanded(TreeNode<T> node);
+
+ /** Marks the node as collapsed (not expanded). */
+ void markCollapsed(TreeNode<T> node);
+
+ /** Marks all nodes as collapsed. */
+ void clear();
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModel.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModel.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModel.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,59 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.tree;
+
+import java.util.List;
+
+import javax.swing.tree.TreeSelectionModel;
+
+import org.apache.tapestry5.ValueEncoder;
+import org.apache.tapestry5.corelib.components.Tree;
+
+/**
+ * A model for tree-oriented data used by the {@link Tree} component. The default implemention, {@link DefaultTreeModel}
+ * uses a {@link ValueEncoder} and a {@link TreeModelAdapter} to supply the
+ * underlying information.
+ *
+ * @param <T>
+ * type of data in the tree
+ * @since 5.3.0
+ * @see TreeSelectionModel
+ */
+public interface TreeModel<T>
+{
+ /**
+ * Returns the node or nodes that are the top level of the tree.
+ */
+ List<TreeNode<T>> getRootNodes();
+
+ /**
+ * Locates a node in the tree by its unique id.
+ *
+ * @throws IllegalArgumentException
+ * if no such node exists
+ * @see TreeNode#getId()
+ */
+ TreeNode<T> getById(String id);
+
+ /**
+ * Recursively searches from the root nodes to find the tree node that matches
+ * the provided element.
+ *
+ * @param element
+ * to search for
+ * @return matching node, or null if not found
+ */
+ TreeNode<T> find(T element);
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModelAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModelAdapter.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModelAdapter.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeModelAdapter.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,53 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.tree;
+
+import java.util.List;
+
+/**
+ * Used with {@link DefaultTreeModel} to define how to extract labels and child nodes from a value.
+ *
+ * @since 5.3.0
+ */
+public interface TreeModelAdapter<T>
+{
+ /**
+ * Determines if the value is a leaf or a (potential) container of children.
+ *
+ * @see TreeNode#isLeaf()
+ */
+ boolean isLeaf(T value);
+
+ /**
+ * Returns true if the value has children (only invoked for non-leaf values).
+ *
+ * @see TreeNode#getHasChildren()
+ */
+ boolean hasChildren(T value);
+
+ /**
+ * Returns the children, in the order they should be presented to the client.
+ *
+ * @see TreeNode#getChildren()
+ */
+ List<T> getChildren(T value);
+
+ /**
+ * Returns a text label for the value, which may be presented to the client.
+ *
+ * @see TreeNode#getLabel()
+ */
+ String getLabel(T value);
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeNode.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeNode.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeNode.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/tree/TreeNode.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,72 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.tree;
+
+import java.util.List;
+
+/**
+ * A node within a {@link TreeModel}. In a {@link DefaultTreeModel}, most of the node's information
+ * comes via the {@link TreeModelAdapter}.
+ *
+ * @param <T>
+ * type of node
+ * @since 5.3.0
+ */
+public interface TreeNode<T>
+{
+ /**
+ * Returns a string Id for the node that uniquely identifies it.
+ *
+ * @return unique string identifying the node
+ * @see TreeModel#getById(String)
+ */
+ String getId();
+
+ /** Returns the value represented by this node. */
+ T getValue();
+
+ /**
+ * If true, then this node is a leaf node, which never has children (i.e., a file). If false, the node
+ * may have children (i.e., a folder).
+ *
+ * @return true for leaf nodes, false for folder nodes
+ * @see TreeModelAdapter#isLeaf(Object)
+ */
+ boolean isLeaf();
+
+ /**
+ * Returns true if this non-leaf node has child nodes. This will not be invoked for leaf nodes.
+ *
+ * @see TreeModelAdapter#hasChildren(Object)
+ */
+ boolean getHasChildren();
+
+ /**
+ * Returns the actual children of this non-leaf node, as additional nodes.
+ *
+ * @see TreeModelAdapter#getChildren(Object)
+ */
+ List<TreeNode<T>> getChildren();
+
+ // TODO: Some way to influence the rendered output (i.e., to display different icons based on
+ // file type).
+
+ /**
+ * Returns a textual label for the node. Not all UIs will make use of the label, but default UIs will.
+ *
+ * @see TreeModelAdapter#getLabel(Object)
+ */
+ public String getLabel();
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Tree.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Tree.tml?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Tree.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Tree.tml Tue Jun 14 00:40:55 2011
@@ -0,0 +1,11 @@
+<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
+
+ <div class="${containerClass}">
+ <t:delegate to="renderRootNodes"/>
+ </div>
+
+ <t:block id="defaultRenderTreeNodeLabel">
+ ${node.label}
+ </t:block>
+
+</t:container>
\ No newline at end of file
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branch.png
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branch.png?rev=1135351&view=auto
==============================================================================
Files tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branch.png (added) and tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branch.png Tue Jun 14 00:40:55 2011 differ
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branchend.png
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branchend.png?rev=1135351&view=auto
==============================================================================
Files tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branchend.png (added) and tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-branchend.png Tue Jun 14 00:40:55 2011 differ
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-sprites.png
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-sprites.png?rev=1135351&view=auto
==============================================================================
Files tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-sprites.png (added) and tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-sprites.png Tue Jun 14 00:40:55 2011 differ
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-vpipe.png
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-vpipe.png?rev=1135351&view=auto
==============================================================================
Files tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-vpipe.png (added) and tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree-vpipe.png Tue Jun 14 00:40:55 2011 differ
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.css
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.css?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.css (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.css Tue Jun 14 00:40:55 2011
@@ -0,0 +1,84 @@
+
+DIV.t-tree-container
+{
+ padding: 0;
+ margin: 0;
+}
+
+DIV.t-tree-container UL
+{
+ list-style-type: none;
+ background-image: url(tree-vpipe.png);
+ background-repeat: repeat-y;
+ margin: 0 0 0 12px;
+ padding: 0;
+}
+
+DIV.t-tree-container UL UL
+{
+ /* Line up the nested list's vertical bar under the element's folder icon. */
+ margin: 0 0 0 24px;
+}
+
+
+DIV.t-tree-container LI {
+ margin: 0;
+ padding: 0 0 0 16px;
+ background-image: url(tree-branch.png);
+ background-repeat: no-repeat;
+ line-height: 1.5;
+}
+
+DIV.t-tree-container LI.t-last
+{
+ background-color: white;
+ background-image: url(tree-branchend.png);
+}
+
+/* Assume its a collapsed, but expandable, tree node. Later CSS rules overwrite this. */
+
+SPAN.t-tree-icon {
+ display: inline-block;
+ width: 32px;
+ height: 16px;
+ cursor: pointer;
+ background-image: url(tree-sprites.png);
+ background-position: 0px 0px;
+}
+
+SPAN.t-tree-icon.t-leaf-node {
+ cursor: default;
+ background-position: -32px -16px;
+}
+
+SPAN.t-tree-icon.t-empty-node {
+ cursor: default;
+ background-position: -32px 0px !important;
+}
+
+SPAN.t-tree-expanded {
+ background-position: 0px -16px;
+}
+
+SPAN.t-ajax-wait {
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ background-image: url(ajax-loader.gif);
+}
+
+SPAN.t-tree-icon.t-expand {
+ width: 16px;
+ height: 16px;
+ background-image: url(expand.png);
+ display: inline-block;
+ cursor: pointer;
+}
+
+SPAN.t-tree-icon.t-collapse {
+ width: 16px;
+ height: 16px;
+ background-image: url(collapse.png);
+ display: inline-block;
+ cursor: pointer;
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.js?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.js (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tree.js Tue Jun 14 00:40:55 2011
@@ -0,0 +1,138 @@
+T5.extend(T5, {
+ Tree : {
+
+ /**
+ * Approximate time per pixel for the hide and reveal animations. The
+ * idea is to have small (few children) and large (many childen)
+ * animations operate at the same visible rate, even though they will
+ * take different amounts of time.
+ */
+ ANIMATION_RATE : .005,
+
+ /**
+ * Maximum animation time, in seconds. This is necessary for very large
+ * animations, otherwise its looks visually odd to see the child tree
+ * nodes whip down the screen.
+ */
+ MAX_ANIMATION_DURATION : .5,
+
+ /** Type of Scriptaculous effect to hide/show child nodes. */
+ TOGGLE_TYPE : 'blind',
+
+ /**
+ * Name of Scriptaculous effects queue to ensure that animations do not
+ * overwrite each other.
+ */
+ QUEUE_NAME : 't-tree-updates'
+ }
+});
+
+T5.extendInitializer(function() {
+
+ var cfg = T5.Tree;
+
+ function doAnimate(element) {
+ var sublist = $(element).up('li').down("ul");
+
+ var dim = sublist.getDimensions();
+
+ var duration = Math.min(dim.height * cfg.ANIMATION_RATE,
+ cfg.MAX_ANIMATION_DURATION)
+
+ new Effect.toggle(sublist, cfg.TOGGLE_TYPE, {
+ duration : duration,
+ queue : {
+ position : 'end',
+ scope : cfg.QUEUE_NAME
+ }
+ });
+ }
+
+ function animateRevealChildren(element) {
+ $(element).addClassName("t-tree-expanded");
+
+ doAnimate(element);
+ }
+
+ function animateHideChildren(element) {
+ $(element).removeClassName("t-tree-expanded");
+
+ doAnimate(element);
+ }
+
+ function initializer(spec) {
+ var loaded = spec.expanded;
+ var expanded = spec.expanded;
+ if (expanded) {
+ $(spec.clientId).addClassName("t-tree-expanded")
+ }
+ var loading = false;
+
+ function successHandler(reply) {
+ // Remove the Ajax load indicator
+ $(spec.clientId).update("");
+ $(spec.clientId).removeClassName("t-empty-node");
+
+ var response = reply.responseJSON;
+
+ Tapestry.loadScriptsInReply(response, function() {
+ var element = $(spec.clientId).up("li");
+ var label = element.down("span.t-tree-label");
+
+ label.insert({
+ after : response.content
+ });
+
+ // Hide the new sublist so that we can animate revealing it.
+ element.down("ul").hide();
+
+ animateRevealChildren(spec.clientId);
+
+ loading = false;
+ loaded = true;
+ expanded = true;
+ });
+
+ }
+
+ function doLoad() {
+ if (loading)
+ return;
+
+ loading = true;
+
+ $(spec.clientId).addClassName("t-empty-node");
+ $(spec.clientId).update("<span class='t-ajax-wait'/>");
+
+ Tapestry.ajaxRequest(spec.expandChildrenURL, successHandler);
+ }
+
+ $(spec.clientId).observe("click", function(event) {
+ event.stop();
+
+ if (!loaded) {
+
+ doLoad();
+
+ return;
+ }
+
+ // Children have been loaded, just a matter of toggling
+ // between showing or hiding the children.
+
+ var f = expanded ? animateHideChildren : animateRevealChildren;
+
+ f.call(null, spec.clientId);
+
+ var url = expanded ? spec.markCollapsedURL : spec.markExpandedURL;
+
+ Tapestry.ajaxRequest(url, {});
+
+ expanded = !expanded;
+ });
+ }
+
+ return {
+ treeNode : initializer
+ };
+});
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FileSystemTreeModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FileSystemTreeModel.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FileSystemTreeModel.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FileSystemTreeModel.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,75 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.integration.app1;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.tapestry5.ValueEncoder;
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.tree.DefaultTreeModel;
+import org.apache.tapestry5.tree.TreeModelAdapter;
+
+public class FileSystemTreeModel extends DefaultTreeModel<File>
+{
+ private static final ValueEncoder<File> ENCODER = new ValueEncoder<File>()
+ {
+
+ public String toClient(File value)
+ {
+ return value.getPath();
+ }
+
+ public File toValue(String clientValue)
+ {
+ return new File(clientValue);
+ }
+ };
+
+ private static final TreeModelAdapter<File> ADAPTER = new TreeModelAdapter<File>()
+ {
+
+ public boolean isLeaf(File value)
+ {
+ return value.isFile();
+ }
+
+ public boolean hasChildren(File value)
+ {
+ return value.list().length > 0;
+ }
+
+ public List<File> getChildren(File value)
+ {
+ return CollectionFactory.newList(value.listFiles());
+ }
+
+ public String getLabel(File value)
+ {
+ return value.getName();
+ }
+ };
+
+ public FileSystemTreeModel()
+ {
+ this(".");
+ }
+
+ public FileSystemTreeModel(String rootDir)
+ {
+ super(ENCODER, ADAPTER, F.flow(new File(rootDir).listFiles()).toList());
+ }
+}
Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java?rev=1135351&r1=1135350&r2=1135351&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java Tue Jun 14 00:40:55 2011
@@ -57,6 +57,8 @@ public class Index
private static final List<Item> ITEMS = CollectionFactory
.newList(
+ new Item("TreeDemo", "Tree Component Demo", "Demo of Tree Component"),
+
new Item("InvalidExpressionInDynamicTemplate", "Invalid Dynamic Expression",
"Invalid expression in a Dynamic Template"),
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/TreeDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/TreeDemo.java?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/TreeDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/TreeDemo.java Tue Jun 14 00:40:55 2011
@@ -0,0 +1,38 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.integration.app1.pages;
+
+import java.io.File;
+
+import org.apache.tapestry5.annotations.InjectComponent;
+import org.apache.tapestry5.corelib.components.Tree;
+import org.apache.tapestry5.integration.app1.FileSystemTreeModel;
+import org.apache.tapestry5.tree.TreeModel;
+
+public class TreeDemo
+{
+ @InjectComponent
+ private Tree fs;
+
+ public TreeModel<File> getFileSystemTreeModel()
+ {
+ return new FileSystemTreeModel();
+ }
+
+ void onActionFromClear()
+ {
+ fs.clearExpansions();
+ }
+}
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/TreeDemo.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/TreeDemo.tml?rev=1135351&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/TreeDemo.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/TreeDemo.tml Tue Jun 14 00:40:55 2011
@@ -0,0 +1,15 @@
+<t:border xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
+
+ <h1>Tree Demo</h1>
+
+ <t:tree t:id="fs" model="fileSystemTreeModel"/>
+
+ <p>
+ [
+ <t:pagelink page="treeDemo">Redraw</t:pagelink>
+ ]
+ [
+ <t:actionlink t:id="clear">clear expansions</t:actionlink>
+ ]
+ </p>
+</t:border>
\ No newline at end of file