[Prev] Thread [Next]  |  [Prev] Date [Next]

T5's Ajax/Zone implementation has substantial limitations Avi Cherry Tue Jan 06 19:00:23 2009

First off, I want to say that I'm a huge supporter, advocate (and long time user) of Tapestry, particularly T5.

Secondly, I'd like to say that I hope this can be the start of a rational discussion about the merits and limitations of the current 'Zone' approach as well as possible ways to improve the model.

Finally, I'm hoping that Howard, if available, would be able to comment on some of this so that the current design's goals could be more apparent as well as future plans for Ajax support.

So, the approach that T5 has now for implementing ajax requests and zones is as follows:

A XHR is made to the server, triggering an event. The event optionally returns a reference to a component that gets rendered (more or less) in isolation, turned into a JSON message and returned to the client. On the client, the contents of the zone specified by the form or link is replaced by the 'content' portion of the JSON reply payload. If no component is returned, the target zone is emptied.

So broken down, this approach has the following characteristics:

1) The content returned to client can be dynamically chosen by the event handler. 2) The target 'zone' for any form/link is static, so any link or form can only ever update the same zone, regardless of the results of the event. I believe the zone a link is bound to -can- be changed on the client, but only -before- a request is made, not as the result of an event. It cannot be changed based on the results of an event on the server.
3) Only a single zone can be updated in any one request
4) A component being rendered within a zone can easily be in an inconsistent state, since surrounding components may or may not have been rendered, depending on where the 'zone' is placed. 5) Relying on the state of a component while it's handling an event is essentially impossible unless its state at rendering is serialized. This, I believe, can only be done if the component is inside of a Form. I believe this is an inherent limitation of Tapestry's event handling and not specific to the Ajax support, but I still believe that this is a severe 'leak' in the abstraction that Tapestry attempts to provide.

Characteristic #1 seems okay to me, but -nearly- all of the time, I have found that I want a component to re-render in place, rather than to replace the content inside of the zone with a different component. The remaining characteristics each provide particular roadblocks when attempting to to use AJAX in otherwise common situations.

Characteristic #2 is a problem. Going back to #1, I would nearly always find it more useful to be able to specify in the event handler "where" on the page I want components to re-render themselves, rather than "what" component I want to insert into the position specified by the zone.

A combination of #1 and #2 together provide a rather different behavior of event handlers depending on if they're XHR or not. Making them behave exactly the same is clearly not possible, but I see there as being a fairly major discrepancy between the two: When a page handling a standard request event wants to merely refresh the page, it can either return null or have a method with a void return value. A component that handles an event can additionally achieve a content refresh by returning null or void from the event handler method. The component does NOT have to have any knowledge of the page that it is inside in order to re-fresh the contents of that page. When a page handling an XHR request wants to refresh a zone, the event handler must explicitly return the particular component or zone that will be refreshed. In addition, this must match the zone bound to the form or the link in order to achieve a refresh. Even worse, if an event handler on a -component- wishes to achieve a refresh of a zone within a request the component is involved with, there is effectively no way to do this unless that component itself was the source of the event or otherwise explicitly binding the zone -again- to the component.

If you wish to see an example of how complicated it can be to implement a solution to a seemingly simple problem of an in-place component refresh when there are multiple collaborating components, one need only look at Tapestry's built-in Grid component: When inPlace is flagged true, on the grid, the following code artifacts must be implemented:
1) Grid must synthesize a zone to contain itself
2) Grid must pass this zone to the GridPager it contains
3) GridPager has to explicitly 'link' the Links that it generates to the zone passed by Grid 4) GridPager must trigger a secondary event during handling of the 'action' events 5) Grid must handle this secondary event, but since it's not the primary event (I assume this is the reason?), it must explicitly call into the componentEventResultProcessor in order to re-render itself into the JSON result.

If the return value of an event handler behaved in such a way that on an XHR request, the component that was returned was automatically refreshed, the implementation would be more like: 1) GridPager implements an onAction event handler that changes the current page and returns false to indicate the event may not have been completely handled. This handler behaves identically for a non XHR request. 2) Grid implements an onAction event handler as well. It would return itself, or perhaps null, to indicate that it should re-render in place. Again, this handler behaves identically for a non XHR request.

Characteristic #3 is a rather large limitation. Take, for example, a page that's laid out such that there is a 'status' area in the 'layout' component that would display some status messages pushed to it by the service/business layer and then in the mail body of the page, an active area that has links/form that can perform actions. There is no simple way to update both the status area and the active area on the page. I believe a mechanism that allows for multiple parts of the page to be updated in a single request would be extremely valuable in making the Ajax support more useful. I've been able to partially work-around this by getting ajax requests to cascade: The first ajax reply includes a script in the JSON payload that triggers a 2nd ajax link/form.

Characteristic #4 is really, really tricky... I believe that parts of this had to be overcome to allow for Forms to work at ALL when they have zones inside of them. The only complete solution that I can think of is to do a complete render of the page and cherry-pick portions of the DOM to send back to the client. This is obviously far from ideal, but seems to be an inherent limitation of any system that uses nested components.

Characteristic #5 I don't think I really want to get into right now, since I honestly have a large gap of knowledge as to what is going on here.

So, my extremely rough-draft proposal would be as follows:

Get rid of the concept of zones, since I don't believe they're necessary in my model. Components that update during an XHR request will always refresh themselves in-place. A component that terminates an event (by returning anything other than false) will refresh refresh itself when the return value is void or null or will trigger a refresh of any component explicitly returned. This is rather similar to the behavior of non-Ajax event handlers now There should be some mechanism for allowing the refresh of multiple components. This could either be done entirely by the event handler by returning a Collection of components, or with an injected Tapestry service that allows an event handler to add components to a queue.

If you wanted to then achieve the -effect- of the current ability to place an arbitrary component chosen by the event handler into a zone (say with a tab view, for example), you could achieve it in exactly the same way that you achieve it in a non-ajax page, with delegates and blocks, where you simply re-render the delegate which will then render a block from a property.

What I haven't though about much is what happens if a component that you want to refresh is inside of a loop, but I believe in the current implementation you have to use the special-purpose AjaxFormLoop inside of a Form to be able to make use of any ajax inside of a loop.

I'm really, really hoping that I'll get some feedback on these ideas and to hear other people's experience using T5's current Ajax implementation.

Ideas?  Feedback?

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