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