Hi Avi!
First I've to say I really like tapestry 5 and its huge improvements
over T4. Most of all the live class reloading!
At the moment I am trying to do an integration of T5 with extjs. Till
now it seems to go quite good.
I can render ext components over to the client use informal parameters
on them and use the inheritance of components to do in a very lean way.
Some days ago I started with the AJAX part. So first thing I needed
was the use of tapestry event handlers to add client side event.
Things are going quite well, now I am at the point where I add an
@OnEvent annotation or have an event listener method signature
(on....) the event handler is added on the fly to the client side
component.
But now I am at the point where I am fiddling arround with updates to
the client side. For me the easiest thing would be to get the output
of a specific rendered component, which is nothing different than some
json objects that
I can integrate into my return value (which is again a json object).
With this approach. Several components could get updated, added or any
other operation executed on the client. This would be all I need for
ajax (as far as i can consider now).
Has someone an idea if this is already possible and how this could get
achieved, or if the approach is not useful at all?
Kind regards,
Robert
Am 07.01.2009 um 03:05 schrieb Avi Cherry:
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: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org