This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

Recursive Components in Tapestry

Description

<n>This article was originally published on the <a href="http://blogs.encodo.ch/news/view_article.php?id=6"><b>Encodo Blogs</b></a>. Browse on over to see more!</n> <hr> Given a recursive object structure in memory, what's the best---and most efficient---way to render it with Tapestry? First, let's define a tiny Java class that we'll use for our example: <code> public class DataObject { private String name; private List<dataobject> subObjects = new ArrayList<dataobject>(); public String getName() { return name; } public List<dataobject> getSubObjects() { return subObjects; } } </code> Imagine an application has built a whole tree of <c>DataObjects</c> and wants to display them in a page. Since the page doesn't know how many objects---or nesting levels---there are, it can't be defined statically. This sounds like the perfect place to use a Tapestry component. Since each component must know about its context object (the <c>DataObject</c>), there must be an instance of the component for each object. This sounds like the perfect place to use <i>recursion</i>. Let's take a crack at defining the template for a component named "DataObjectTree", which has a single property, <c>context</c>, which passes in the object to render<fn>: <code> Context Name
</code> If it was that easy, you probably wouldn't be reading this article, as it wouldn't have been written. However, the Tapestry template parser is going to have extreme difficulties parsing this self-referential template. This value causes a stack overflow: <code>
<span class="error">@DataObjectTree</span>" context="ognl:DataObject"/> </code> That's a shame, but, with help from the blog entry, <a href="http://www.behindthesite.com/blog/C1931765677/E923478269/index.html">Recursive Tapestry Components</a>, it's possible to solve this problem with very little code (though the recursive solution would <i>still</i> be nicer). <h>Use <c>Blocks</c> to fool Tapestry</h> Tapestry has two components, <c>Block</c> and <c>RenberBlock</c>. <c>Block</c> defines a "floating" piece of template that is not rendered where it is defined, but is rather rendered in a particular place---or places---in a template by a <c>RenderBlock</c>. The trick boils down to this: replace the recursive call in the component definition with a call to render a block defined in the page. That is, replace the offending line above with the line below: <code>
<span class="highlight">jwcid="@RenderBlock" block="ognl:Page.Components.DataObjectBlock"</span>/> </code> The <c>DataObjectBlock</c>, in turn, is defined in the page template and includes a <c>DataObjectTree</c> component. <code>
<span class="caution">???</span>"/>
</code> As shown above, there is a slight problem, as we need to pass a context to the nested <c>DataObjectTree</c> component. Since we are once again in the page template, we can't refer to any properties defined in the component. Therefore, the iterator object, <c>DataObject</c>, used in the recursive (and non-functional) example above, is not available. We'll have to access it some other way. The other way turns out to be by passing it from the <c>RenderBlock</c> to the <c>Block</c>. A <c>RenderBlock</c> component accepts and stores all properties, so we'll pass it the context in a "value" property (use any name you like). <code>
<span class="highlight">value="ognl:DataObject"</span>/> </code> How can the <c>DataObjectTree</c> retrieve this property? It needs access to the <c>RenderBlock</c> that included its parent <c>Block</c>. That is, with a reference to its surrounding block, it can obtain a reference to the <c>RenderBlock</c> and retrieve the value from it. The code below shows how to declare the <c>Block</c> in the page template. <code>
<span class="highlight">block="ognl:Page.Components.DataObjectBlock"</span>/>
</code> Tapestry will now give each instance of <c>DataObjectTree</c> a reference to the <c>Block</c> instance that encloses it. In order to complete this solution, you'll have to write a Java class for the <c>DataObjectTree</c> component itself. The component template refers to a <c>Context</c>, which represents the <c>DataObject</c> to display. When displayed from the block, this property is not directly set, so we will have to define code to retrieve it from the appropriate place.<fn> <code> public abstract class DataObjectTree extends BaseComponent { public abstract Block getBlock(); public abstract DataObject getContext(); public DataObject getBlockContext() { if (getBlock() != null) { return (DataObject) getBlock().getParameter("value"); } return getContext(); } } </code> If the component's <c>block</c> property is set, then the component was instantiated from within a block. In that case, the context is retrieved from the block's parameters (all properties from the initiating <c>RenderBlock</c> are automatically passed to the block as parameters). Otherwise, use the context set by the <c>Context</c> property of the component. Below is the completed template for the component: <code> <span class="highlight">BlockContext</span>.Name">Context Name
<span class="highlight">BlockContext</span>.SubObjects.size() > 0">
<span class="highlight">BlockContext</span>.SubObjects" value="ognl:DataObject">
</code> As discussed above, the template now uses the <c>BlockContext</c> instead of the context directly, so it uses the correct <c>DataObject</c>. The page, on the other hand, uses the <c>DataObjectTree</c> component twice, once from the <c>DataObjectBlock</c> (as shown above) and once from the main template, as highlighted below. <code> <span class="highlight">
</span>
</code> Note how the instance in the main template gets a <c>Context</c> representing the root of the tree and defined in the page itself. Though not as simple as the intuitive, recursive solution, the "Tapestry Way" doesn't end up using too much code, though it did take a little while to figure out. <h>It's <i>still</i> too complicated!</h> It's kind of a shame that the page template has to not only use the component, but declare the block that it uses to render its nodes. Optimally, this part would also go into the component ... but that takes us right back to the recursion problem we started with. Also, it's kind of a shame that a separate component is <i>required</i>. Is there any way around around these two warts? Using the <c>DataObjectTree</c> recursively is not possible, but <c>Blocks</c> can seemingly be nested as much as needed. In fact, all that seems to be missing is a way to cleanly access the "value" passed to the <c>Block</c> by the <c>RenderBlock</c>. Leaving the definition within a separate component (for now), we can redefine the component HTML as follows: <code>
Context Name
</code> The entirety of the component's rendering is contained within a <c>Block</c>, which accesses the current context through the <c>BlockContext</c>. The component's main content is simply a <c>RenderBlock</c> that renders that block for its <c>Context</c> by passing it as the "value" for the block. Since the block is now defined within the component, it is accessed using <c>Components.DataObjectBlock</c> rather than <c>Page.Components.DataObjectBlock</c>. The only remaining magic is to implement <c>BlockContext</c> in the component's Java class. The component retrieves the current instance of the "Node" component out of its component map and returns whatever was set in the "value" parameter. The page uses the <c>getContext()</c> property to pass in the initial context. <code> public abstract class DataObjectTree extends BaseComponent { public abstract DataObject getContext(); public Object getBlockContext() { return <span class="highlight">((Block) getComponents().get("Node")).getParameter("value");</span> } } </code> Now <i>that's</i> clean! In fact, it's almost the same as the original recursive solution, but uses the <c>RenderBlock/Block</c> trick to get around Tapestry's limitation. Though this example is now still defined in a component, you can just move the code and HTML into a page definition. Since the page already has its own context, you only need to copy in the <c>getBlockContext()</c> method. The HTML can be copied directly and voila! A clean solution to recursive structures in Tapestry without defining any new components or using messy kludges. <h>Optimizing with <c>@InvokeListener</c></h> Since the <c>BlockContext</c> is called many times from the component---real-world implementations will also likely need more such properties---, we'd like to set up all necessary data when starting the <c>DataRowBlock</c>. To do this, use the <c>@InvokeListener</c> from the HTML to call a function on the page instance. <code> public abstract class DataObjectTree extends BaseComponent { <span class="highlight">private Object blockContext;</span> public abstract DataObject getContext(); public Object getBlockContext() { return <span class="highlight">blockContext</span>; } <span class="highlight">public void updateBlockContext() { blockContext = ((Block) getComponents().get("Node")).getParameter("value"); }</span> } </code> From the HTML template, simply call this function at the beginning of the <c>DataObjectBlock</c>: <code>
<span class="highlight"></span> ...
</code> This is a good pattern to follow to avoid having getters that are too computationally expensive. <hr> <ft>For the non Tapestry-savvy, here are a few tips: <ul> <c>ognl</c> indicates that a <a href="http://www.ognl.org/">Object-Graph Navigation Language</a> expression is coming -- it's the mechanism Tapestry uses to script objects from a template. This language understands get/set and can read properties. The @ sign indicates a component type: <c>For</c> is a loop, <c>If</c> is a conditional and <c>Insert</c> adds text to the template. Text before the @ sign is an explicitly named component, which can be referred to by this name elsewhere in the template. </ul> For more information, visit <a href="http://tapestry.apache.org/">Tapestry's home page</a>. </ft> <ft>Again, for Tapestry novices, properties that are declared <c>abstract</c> are automatically given getters and setters and wired up by Tapestry in a dynamically generated descendent (that's why the class is <c>abstract</c> as well.</ft> <n>Using Java 1.5 and Tapestry 4.0.2</n>