I can't comment in detail on your solution but it looks a bit convoluted
in the TML for my liking. A tree should be a first class component and
in this case I am prepared to do a little more work in the component
class to make it simple and intuitive to use. I wrote an ajax tree a
while back but just put together a non-ajax tree which might give you
some ideas... hope it helps!
The Tree component requires a very simple implementation of TreeModel,
in a similar vein to SelectModel, etc in the tapestry core. This means
that the application can determine where the model actually lives, and
optimise accordingly.
The body of the component defines precisely how each node is rendered.
Most of the complexity is in managing the push/pop between begin render
and after render. Hopefully this is not too confusing, and I actually
think it's pretty succinct given what it's doing.
I just knocked this together and haven't tested it extensively but can't
see why it would exhibit the kind of problems you are seeing with your
solution.
Best regards, Alfie.
-------------- Sample page TML usage -------------
<t:tree model="model" level="level" item="item">
${level}: ${item.title}, ${item.other}
</t:tree>
-------------- Sample page class -----------------
public class Tree {
@Property
private int level;
@Property
private TreeNode item;
private TreeNode rootNode;
public Tree() {
rootNode=new TreeNode("root", "1")
.add(new TreeNode("child", "1.1"))
.add(new TreeNode("child", "1.2")
.add(new TreeNode("child", "1.2.1"))
.add(new TreeNode("child", "1.2.2"))
)
.add(new TreeNode("child", "1.3")
.add(new TreeNode("child", "1.3.1"))
.add(new TreeNode("child", "1.3.2"))
)
.add(new TreeNode("child", "1.4"));
}
public TreeModel<TreeNode> getModel() {
return new TreeModel<TreeNode>() {
public Collection<TreeNode> getChildren(TreeNode
node) {
return node.getChildren();
}
public TreeNode getRootNode() {
return rootNode;
}};
}
public class TreeNode {
private String title;
private String other;
private Collection<TreeNode> children=new
ArrayList<TreeNode>();
public TreeNode(String title, String other) {
this.title=title;
this.other=other;
}
public TreeNode add(TreeNode n) {
children.add(n);
return this;
}
public Collection<TreeNode> getChildren() {
return
Collections.unmodifiableCollection(children);
}
public String getTitle() {
return title;
}
public String getOther() {
return other;
}
}
}
-------------- Tree.java ----------------
public class Tree {
@Parameter(required=true)
private TreeModel<Object> model;
@Parameter
private int level;
@Parameter
private Object item;
private Stack<Iterator<Object>> iteratorStack;
@SetupRender
public boolean setup() {
iteratorStack=new Stack<Iterator<Object>>();
Object o=model.getRootNode();
if ( o == null ) {
// nothing in the model
return false;
}
item=o;
return true;
}
@BeginRender
public void begin(MarkupWriter writer) {
writer.element("div", "style", "margin-left:30px");
}
@AfterRender
public boolean after(MarkupWriter writer) {
Collection<Object> children = model.getChildren(item);
if ( children.size() == 0 ) {
// no children of this node
item=moveNext(writer);
return item == null;
} else {
level++;
iteratorStack.push(children.iterator());
item=iteratorStack.peek().next();
return false;
}
}
private Object moveNext(MarkupWriter writer) {
writer.end();
if ( iteratorStack.size() > 0 ) {
Iterator<Object> i = iteratorStack.peek();
if ( i.hasNext() ) {
return i.next();
}
level--;
iteratorStack.pop();
return moveNext(writer);
}
return null;
}
public interface TreeModel<T> {
public Collection<T> getChildren(T node);
public T getRootNode();
}
}
-----Original Message-----
From: Dirk Lattermann [mailto:[email protected]]
Sent: 14 October 2009 20:40
To: [email protected]
Subject: T5: t:delegate and component recursion emulation
Inspired by http://blog.bolkey.com/2009/06/tapestry-5-recursive-tree/, I
tried to develop a simple tree component that renders a tree of nodes as
nested ul/li lists. Always good to learn Tapestry behaviour.
I know that Tapestry doesn't support recursive components, but this can
be somewhat circumvented via the delegate mechanism. I wonder if this is
accidental or if this is supported and should work.
I proceeded as in the blog mentioned above, but used only one component
for all nodes for simplicity (no subclasses in dependance of the node
type). I have
t2tree.tml:
-----------------------
<?xml version="1.0" encoding="UTF-8" ?>
<ul xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
<li>root: ${currentNode.name}
<t:if test="currentNode.hasChildren()">
<t:delegate to="currentNodeBlock"/>
</t:if>
<t:block>
<t:t2node t:id="node" node="currentNode">
<li>${currentNode.name}
<t:if test="currentNode.hasChildren()">
<ul>
<t:delegate to="currentNodeBlock"/>
</ul>
</t:if>
</li>
</t:t2node>
</t:block>
</li></ul>
-----------------------
I have to use two components for this, because the t:block cannot
contain a t2tree, even if it is not rendered.
The block is used to hide the definition of <t2node t:id="node"> so that
t2tree's template doesn't render it. t2tree's property
"currentNodeBlock" returns this component (<t2node t:id="node">).
This creates a recursive structure in the stack backtrace, but the
component t2node instance is re-used at every level and must take care
to modify and restore its state on nesting and denesting.
t2node changes the node (t2tree's currentNode) in the beforeRenderBody
and afterRenderBody methods to iterate through the tree. I'm omitting
the details here, it works when I generate the <ul>-Tags with a
MarkupWriter. However, to separate the markup from the code, I'm trying
to omit generating the markup in the code, so the body of t2node
contains a t:if and the opening and closing <ul> tags.
As far as I have observed, this fails for the reason that this t:if is
only evaluated once (in effect, twice, apparently because it appears
twice in t2tree.tml).
This is the point where I'm asking myself if this is/should be supported
by tapestry after all, because the evaluation result of the t:ifs seem
to be cached somewhere. Is this intentional? Can it be switched off?
I can't see why it should be intentional, because normally, the template
is evaluated only once and it doesn't make sense to cache the result
then. On the other hand, in a t:loop component body, there can be t:ifs
that are evaluated at every iteration (if not @Cached).
I hope this issue is comprehensible. Can please someone knowledgable
explain why this doesn't work or tell me if it can be made to work or
that it should work?
Thanks much,
Dirk
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]