In the recent "which two items..." thread, I saw a few requests for "fixing" the rewind process. Here are my thoughts on the topic.
There are a number of aspects of form rewinding (or "replaying", as I like to call it) that I think confuse people. One big issue, I think, is the fact that form rendering, both for display and for replay (rewind), is handled in a sequential process. Despite the fact that Tapestry is component-based, the rendering process feels very script-like to me, since the state of the page and component properties is continually changing as the render takes place, especially when looping is involved. By itself this isn't an issue, but when you couple this with the following two aspects, things get confusing: The first aspect is that, during the replay (rewind) of the page, the component tree is rebuilt dynamically from business objects stored in various places. If the contents of the business objects are not exactly the same during replay as during the initial rendering, and different form components are built, then Tapestry gets mad. Second, listeners fire _during_ the render instead of at the end of rendering. Other frameworks seem to fire all of their listeners at the end of rendering. This mid-render fire of listeners has some benefits and some drawbacks. Because the page and component properties are constantly changing during the loop, the listener fires at the point in time when the page and component properties are in a specific state. The listener can call the property getters to determine the state of the loop. If we wait until the end of the rendering process, this state will be lost. Let's give this benefit the name "listener-knows-page-state". One huge drawback, though, is that the listener can't make changes to anything that would affect components lower in the page. For example, if your "delete" button's listener deletes the current row from the list, then your iterator will break and the page crashes. So, you're stuck with a big if-else-if statement in the form's listener, which is neither pretty nor clean, and defeats the purpose of putting listeners on the submit buttons. So that's the problem... what are possible solutions? To deal with the first aspect, we need client-persistent page and component properties. Fortunately, this is coming in Tapestry 4.0. :-) An alternative would be to save the component tree state in the page (as opposed to saving the state of the business objects that determine the component state). However, this alone wouldn't work, because the component properties are basically always bound to business objects, so the business objects had better be in their original state, or things won't work as expected. In other words, client-persistent properties are basically the only way to solve that problem. I agree with whoever suggested that encryption be part of the out-of-box client-persistent properties for them to be fully robust. Maybe they even could be encrypted by default. (Note that right now (with 3.0.x) we can fake client-persistent properties by using hidden fields and DataSqueezers, but it is too easy to make mistakes with this solution, and they require more work than they should.) To deal with the second issue, I would suggest enhancing the listeners: 1) The biggest enhancement would be to allow delayed-firing of listeners. When I put a listener on a button, I want to be able to specify that the listener fires after replay (rewind) is complete. A listener should be allowed to be configured for immediate-fire or delayed-fire. I think that in the future, the default should be delayed-fire, but for now it would have to be immediate-fire for backwards compatibility. During rendering, listeners scheduled for firing would be pushed into a FIFO queue and then pulled out and fired (in order) after rendering is complete. I think we might need at least three possibilities: a) fire immediately; b) fire after parent component renders (for components stored within other components); and c) fire after page renders.* 2) Allow parameters to be passed to listeners. If we delay the firing of listeners, we lose the "listener-knows-page-state" benefit mentioned earlier. If we allow parameters to be passed to listeners, we can restore most of this benefit, even when using delayed-firing listeners. When the component renders, the current state of the listener's parameter values would be stored in a List, and these would be retrieved later (after rendering) when the listener actually fires. Of course, the contents of those objects could have changed during the rendering, but I think you'd find that, in practice, this generally won't be a problem. Plus, there might be ways to identify parameters that are likely to be altered and issue a warning. Listener parameters could be configured in two ways: a) ActionLink-style where page replay (rewind) is necessary, and the values are calculated during replay; or b) DirectLink-style where replay is not necessary the values during the initial render are embedded in the page as URL or form parameters.** 3) The usage of listener and their parameters should be consistent across all components and services. This could consolidate ActionLink and DirectLink so that they're the same, but they use two different parameter-passing configurations. 4) As a side note, I would like to see more flexibility for listeners in general. For example, I'd love to put an "onChange" listener directly onto the PropertySelection component instead of using the submitOnChange flag and then putting a messy if-else-if into my form's listener. I'd love to see many additional listener triggers on various components, with the possibility of multiple listeners attached to a single component. (*Note: I'm not totally sure, but I think that with listener parameters, the immediate-firing listeners would probably be unnecessary. Comments?) (**Note 2: I think that both kinds of parameter-passing configurations are necessary... I personally think that ActionLink, or something like it, still has its place, even if it must be used with caution.) Another possible enhancement would be to allow the application to try to handle stale page submission in a graceful way. Maybe the page could have a method that would handle stale page errors (by implementing an interface). The business logic in the page might be able to determine what the user is trying to do and tell the framework how to gracefully continue processing without issuing an error. Or, the business logic might be able to direct the user to a nice page with a nice context-specific error message. Once we find some common patterns for graceful handling of stale forms, these patterns could be built into the framework. Thoughts? (Especially about delayed-firing listeners and listener parameters.) -Nathan --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
