You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Dirk Lattermann <dl...@alqualonde.de> on 2009/10/14 21:39:46 UTC

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: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


RE: T5: t:delegate and component recursion emulation

Posted by Dirk Lattermann <dl...@alqualonde.de>.
Am Donnerstag, den 15.10.2009, 11:32 +0100 schrieb Alfie Kirkpatrick:
> 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.

Hi Alfie!

Thank you for your thoughts and code. Meanwhile I think you are right,
using a template is not very helpful as it is not easily maintainable
anyway.

I found out what's the problem with the If component that "are only
evaluated once": the If component object is also re-used at every
recursion level, and their "test" parameter is cached, so they will
yield always the same result instead of recalculating it with a new test
value. As this will be the case for most standard components that could
be used to customize the tml, they are not usable in a situation like I
was trying to set up. So, one would have to rewrite all used components
to not cache their parameters which wouldn't be very practical, as it
will make a mess in the tml part.

I already had a tree component using a MarkupWriter that was nearly
identical to the code you sent (thanks again!), the only difference was
the move_next method wasn't recursive but iterative. I think I will make
the component produce <ul>/<li> elements with the values of their
"class" attributes configurable via a parameter. That should be enough
of configurability.

Regards, Dirk


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


RE: T5: t:delegate and component recursion emulation

Posted by Alfie Kirkpatrick <Al...@ioko.com>.
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:dlatt@alqualonde.de] 
Sent: 14 October 2009 20:40
To: users@tapestry.apache.org
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: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org