Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for 
change notification.

The following page has been changed by GregWoolsey:
http://wiki.apache.org/tapestry/RecursiveComponents

New page:
A component whose template calls itself again causes a stack overflow (due to 
an infinite loop) when the Tapestry parser encounters it.  This is well 
documented many places.  A couple of pages talk about a workaround using Block 
and Render``Block, but they have a couple of problems.  This page contains a 
complete example I have working with Tapestry 4.1 combining ideas from the two 
main examples found elsewhere, but with one major addition to work around a 
side effect they don't mention.

== Inspirational Examples ==

1. [http://www.behindthesite.com/blog/C1931765677/E923478269/index.html]
This example shows the basic idea, but isn't really contained and reusable.

2. [http://blogs.encodo.ch/news/view_article.php?id=15]
This has an elegant, simple component definition containing both a starting 
Render``Block and the Block it renders that then contains another Render``Block 
to render itself.  This is the primary example I based my solution on.

== Problems, Caveats, and Shortcomings ==
Example 1 has the major drawback of requiring inclusion of the block(s) that 
define the body of the recursive component in the body of each page that uses 
it - this isn't really a reusable component design.  Attempts to put the block 
in a dummy page referenced by the component still caused stack overflows for me.

Example 2 is cleaner, but not a working example - their code has typos, and 
doesn't mention a very important caveat.  This example uses a component level 
property to hold the child object For component iteration value.  This appears 
fine, but the For component does (or doesn't do) something not documented - it 
doesn't remember and reset the state of the property used for the value and 
index bindings before and after iterating over the source values.  This means 
that any recursive Block body contents that reference this component level 
property after the recursive For component has used it for it's value binding 
will see not the value of the property in the expected scope.

This is quite confusing, and took me a while to figure out.  The problem 
relates to how OGNL evaluates references and the fact that the sample needs a 
component property to store the child For value object passed to the recursive 
RenderBlock call.  Each level of Block rendering looks for it's input data via 
`getProperty()`, which is defined in terms of the For component value binding, 
which is set to the component property.  There is only one instance of the 
component property for the whole recursive rendering, and the default behavior 
of the For component results in that property remaining set to the last value 
found in the last iteration of the lowest level of recursion once the recursive 
calls start to unwind.  

Thus components in the Block body after the recursive For component do not see 
the Block input parameter for that block's original call, but the last value 
the underlying component property was set to by the last iteration of the 
lowest recursion.

= Solution Code =
This example is for a basic Java``Bean called `MessageNode` containing a 
Collection of `MessageNode` objects (`getChildren()`) and a Collection of 
String messages (`getMessages()`).  Rendering is as nested unordered lists, 
with messages as list item elements.  Messages are rendered after children, 
which is where we encounter the `For` component value problem.

==== Template ====
{{{
<span jwcid="$content$">
        <span jwcid="@RenderBlock" block="ognl:components.treeBlock" 
node="ognl:root"/>

        <span jwcid="[EMAIL PROTECTED]">
                <span jwcid="forChildren">
                        <span jwcid="@RenderBlock" 
block="ognl:components.treeBlock" node="ognl:child" />
                </span>
                <ul jwcid="forMessages">
                        <li jwcid="messageItem">Inserted message text</li>
                </ul>
        </span>
</span>
}}}
==== JWC Spec ====
{{{
<component-specification class="MessageTree" allow-body="no" 
allow-informal-parameters="no">
        <description>
                This renders a tree of MessageNode objects as nested lists
        </description>

        <parameter name="root" required="yes">
                <description>
                        Root MessageNode object to render
                </description>
        </parameter>
        
        <property name="message"/>
        <property name="child"/>
        
        <component id="forChildren" type="For">
                <binding name="renderTag" value="false"/>
                <binding name="source" value="blockNode.children"/>
                <binding name="value" value="child"/>
        </component>
        
        <component id="forMessages" type="For">
                <binding name="renderTag" value="true"/>
                <binding name="source" value="blockNode.messages"/>
                <binding name="value" value="message"/>
        </component>

        <component id="messageItem" type="Insert">
                <binding name="renderTag" value="true"/>
                <binding name="value" value="message.toString()"/>
        </component>
        
</component-specification>
}}}

==== Component Java Class ====
{{{
public abstract class MessageTree extends BaseComponent {
        // root parameter from spec
        public abstract MessageNode getRoot();

        // This needs to be in the class so OGNL evaluation when parsing the 
spec doesn't cause an infinite loop!
        public MessageNode getBlockNode() {
                return (MessageNode) ((Block) 
getComponent("treeBlock")).getParameter("node");
        }
}
}}}

== Analysis ==
This example looks pretty simple and straight forward at first, but when 
executed, what you get is the messages for the last leaf node on each branch of 
the object tree rendered as the messages for each parent node of that leaf.  
This is because of the aforementioned problem with the `For` component.

== Solution: A custom ForContext Component ==
To solve the problem, we need a component that acts like the standard `For` 
component but has the added behavior of remembering it's value and index 
initial state and reseting those after rendering it's iterations.  This is 
similar to what the `Block` component does with it's `invoker` property, and 
why the `Block` component can be used recursively as we are doing here.

The custom code is quite simple - just copy the For.jwc spec file (changing the 
class of course), and create a component class like this:
{{{
public abstract class ForContext extends ForBean {
        /**
         * Render the same as the standard ForBean, but remember initial index 
and value binding values if any,
         * and reset them when done rendering.
         * 
         * @see 
org.apache.tapestry.components.ForBean#renderComponent(org.apache.tapestry.IMarkupWriter,
         *      org.apache.tapestry.IRequestCycle)
         */
        @Override
        protected void renderComponent(IMarkupWriter writer, IRequestCycle 
cycle) {
                Object value = null;
                int index = 0;

                IBinding valueBinding = getBinding("value");
                if (valueBinding != null) value = valueBinding.getObject();

                IBinding indexBinding = getBinding("index");
                if (indexBinding != null) index = ((Integer) 
indexBinding.getObject()).intValue();

                try {
                        super.renderComponent(writer, cycle);

                } finally {
                        if (indexBinding != null) indexBinding.setObject(new 
Integer(index));

                        if (valueBinding != null) valueBinding.setObject(value);

                }
        }
}
}}}

== Conclusion ==
Now we can replace the `For` components in the component spec above with 
`ForContext`, leaving everything else the same.  When we render a tree of 
messages, we now get the proper message lists at every level, because the new 
component remembered and reset the state of the property used for the value and 
index bindings (if any) when it was done.

I think this is really a bug with the `ForBean` class, but changing that would 
probably break something that was depending on it's current behavior, expecting 
to see the component or page property still referencing the last collection 
value after the `ForBean` was done rendering.

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to