[Prev] Thread [Next] |
[Prev] Date [Next]
T5's Ajax/Zone implementation has substantial limitations
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
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
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
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
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
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
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
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
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
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
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]