Unfortunately this commit made Tapestry to depend on Java 1.6. Class java.util.Deque is available starting from Java 1.6.
---------- Forwarded message ---------- From: <[email protected]> Date: Tue, Jun 14, 2011 at 2:40 AM Subject: 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... To: [email protected] 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 -- Best regards, Igor Drobiazko http://tapestry5.de
