Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Thursday, April 28, 2005

An interesting idea: FieldLabel & ValidField

There's been some talk on the Tapestry developer list about radically reworking Tapestry's validation support (primarily, ValidField, FieldLabel and the helper classes ValidationDelegate and all the IValidator implementations). There's a lot of good reasons to do that ... the validation support in Tapestry is pretty good, but was written largely in 2001 and 2002, with only minor tweaks (such as client side validation) since then. Especially in Picasso, there's a lot of opportunity to improve it.

Until that happens (and based on the amount of movement on this subject, a complete overhaul is not likely to make it into the 4.0 release), people are still depending on the existing validation.

One of the most annoying, if relatively minor, problems with validation is the relationship between FieldLabel and ValidField when rendering, with decorations, inside a complex form (that is, with loops). There gets to be a nasty off-by-one problem, where the FieldLabel is out of sync with the ValidField and the wrong FieldLabel instance (within the enclosing loop) get decorated.

This is because the FieldLabel renders before the ValidField (in most Western language, left-to-right style layouts). I came up with a kind of kludge about how to approach this, where we gimic things to get the ValidField to render before the FieldLabel ... but then defer emitting that output into the response stream until the correct point.

What occurred to me this morning is that, with a modest amount of effort, we could bake this directly into the components That is, the FieldLabel could:

  • Obtain its field (i.e., a IFormComponent instance)
  • Create a nested IMarkupWriter
  • Have the field render into the nested writer
  • Store the writer as an IRequestCycle attribute, using a key that includes the form component's extendedId
  • Do it's own rendering

The ValidField (and all the other IFormComponent implementations) would have to:

  • See if there's a pre-renderered writer IRequestCycle attribute
  • If so: close the writer (which injects the buffered markup into the outer writer) and remove the attribute
  • If not: render normally

It's a bit of work, since it will require updating all the form control components. It's also a chance to get, at least, decoration of all form control components working.

In addition, I believe I'll change the Form component to provide a default IValidationDelegate even when its delegate parameter is not bound ... and change the code inside the various components to expect a non-null delegate from the form.

Finally, I've been thinking that an alternate way to define validators, using a binding prefix, would be useful, something like:

<binding name="validator" value="validator:string,required,minLength=3"/>

Friday, April 15, 2005

Tapestry Picasso will be 4.0

The finally tally for last week's vote is in. The next release of Tapestry, what's currently in CVS HEAD, will be Tapestry 4.0. So after all the talk of Tapestry 3.1, it's going to be a phantom. I hope to get a Tapestry 4.0-alpha-2 out soon as well (but need a stable HiveMind 1.1 alpha release first).

The numbering confusion is exactly why I'm pushing for the release naming experiment. What I've been calling "3.1" for so long is now going to be called, internally, "Picasso". This will give us the freedom to consistently discuss the code base before having to decide on a release number for it.

Other projects, such as Struts, work differently. After a release, they'll vote on its stability, and "grant" it final release status. So you might find that Struts 1.1.23 (or something) is the final release. I kind of like knowing that Tapestry 4.0 is the final, stable release for 4.0. Certainly, there'll be a 4.0.1 with bug fixes, and hopefully soon after that, a Tapestry Magritte (4.1) with more functionality. And having names is more fun than numbers.

Wednesday, April 13, 2005

Improved Listener Methods in Picasso

I'm always on the look out for ways to further simplify Tapestry development; that's one of my core principles. Simplicity. I personally love complexity ... on the inside, doing the magic that makes the end-developer's job that much easier.

One thing that's bugged people for a while concerns listener methods. These are public methods on your classes that can be invoked by forms or by the DirectLink component.

A common thing to do with DirectLink is to pass additional information in the URL. This information takes the form of listener parameters (which were confusingly called "service parameters" in 3.0 and earlier). Anyway, you can ask the request cycle for the parameters, and then extract and cast them to the right types. But that's not simple enough.

For example, suppose that the link encoded a String objectId and an integer index. The component in the template names the listener method, and the two parameters are passed into the DirectLink as an OGNL list expression:

<a jwcid="@DirectLink" listener="doClick" parameters="{ objectId, index }"> . . . </a>
This shows another recent recent improvement: the "listener:" prefix is assumed for the listener parameter, and "ognl:" is assumed for the parameters parameter. If that's not appropriate, you can still provide an explicit prefix. Kudos to Erik Hatcher for that idea.

In the Java class, the listener method might look like:

public void doClick(String objectId, int index)
{
  . . .
}

That's the innovation and simplification; the listener parameters are automatically converted into method parameters, saving you the trouble you'd have to do in release 3.0:

public void doClick(IRequestCycle cycle)
{
  Object[] parameters = cycle.getListenerParameters();
  String objectId = (String)parameters[0];
  int index = ((Integer)parameters[0]).intValue());
 
  . . .
}

The change is backwards compatible; there's a little search for the best matching method implementation, and the last thing checked is the Tapestry 3.0 style listener method (with the single IRequestCycle parameter).

I honestly can't remember where the idea for this came from; it seems so obvious in retrospect that you'd think I came up with up, but I think it was Erik Hatcher again. Oh, and the test suite is up to 960 tests, 277 seconds, and 82.4% code coverage.

Monday, April 11, 2005

HiveMind article on TheServerSide

James Carman's Introduction to HiveMind has appeared on TheServerSide.com today. James must have been working pretty hard, this final version of the article is much more in depth than the early version of the article I reviewed for him a couple of month's back.

I'm really psyched about the HiveMind community ... maybe even more so than the Tapestry community. Not to dig at the Tapestry or developer or user community, I just think that HiveMind is running truer to the ideals of an Apache open source project. I think that HiveMind started on the right foot, and has several key members who are contributing code, but also recognition (such as this article by James, who is a HiveMind committer). Perhaps its because HiveMind is a more well defined problem space than Tapestry, and started more clean and better organized. I hope to get the Tapestry project functioning as smoothly as the smaller HiveMind project some day.

Just finished reading the article; he covered a lot of ground quite nicely, but didn't even mention HiveMind's distinguishing characteristics: distributed configuration (that is, support for many individual modules) and configurations. Most of his examples could be done almost as easily in Spring (though I don't think Spring has a threaded service model). A follow-up article, perhaps?

Property Persistence on the Client

Over the weekend, in between NoFluffJustStuff sessions, I was busy working on adding the 'client' strategy for persistent page properties. In Tapestry 3.0 and earlier, there was a single strategy: persistent page properties are stored into the HttpSession.

Starting in Picasso (4.0), you can specify that a persistent property is stored on the client instead of in the session (on the server). It looks like this:

<property name="foo" persist="client"/>

Once this is in place, and once a change occurs to the foo property, all links and forms in the application will generate additional query parameters (or hidden fields) to track the value of foo. Actually, it uses a single query parameter per page to store all client persisted properties on that page.

It may still need some tweaking; already I can see that the URLs can get very long, very quickly. For example, after setting a couple of properties on a couple of pages of the Workbench:

http://localhost:8080/Palette.direct?component=%24Border.pageLink&session=T&sp=SChart&
state:Fields=H4sIAAAAAAAAAFvzloG1XI6BgYGRgUE8OSczNa8kLDEnMyWxJDM%2FzzUvMSknNaW4iEEwK7E
sUS8nMS9dzyk%2FPyc1Me%2BsQlHD1Tm%2F3jExMEYxsJYl5pSmVhQwAADTbn6TTwAAAA%3D%3D&state:Pale
tte=H4sIAAAAAAAAAFvzloG1nJuBgYGRgYGlOL%2BopLiIQSe%2FKF0vsSAxOSNVrySxILW4pKhSLzk%2Fr6Qo
M0mvIDEntaQkVS8YqNY3PyW1qyv4%2BsQ1f%2FczMTBUFDEoImlNzs%2FNzc8r1stJzEvXS80rzdVzBRI%2FbV
d5bjYMq2JiYPRhYM30S8xNLWEQ8slKLEvUB6nUDwZak5duXVFQwsDq4%2Bjk6gMAGprRD6MAAAA%3D

Given the practical limit of 4000 characters in a URL (don't ask me for references on that), this could be a problem.

The strategies are defined by a configuration point which is, of course, extensible. I expect to add more strategies. For example, a strategy that stores data on the client only until the there is a server-side session, then stores data on the server instead. Or, with some minor tweaks to the APIs, a strategy that store persistent property data, but only inside forms, not inside link URLs.

Drew Davidson has kludged into Tapestry 3.0 some other lifecycle options; he has pages that keep their state as long as the direct service is used, but when the page service is used (to leave a group of related pages), the persistent state for those pages is discarded.

I've been thinking of another option, where different versions of a page's state are stored on the server, and a token comes up in the request to indicate which state is used. This could go a long way towards automatically handling the browser-back button problem ... browser back to an earlier state, and continue working from that state. Actually, properly handling the browser-back button (and perhaps that duplicate form submit scenario) may take a mix of techniques.

Monday, April 04, 2005

JODE: Java decompiling plugin for Eclipse

One more tool for the standard toolkit: JODE Eclipse Plugin. By adding this plugin (and making one small configuration change), you will no longer see the "No source attachment" page when you navigate to a class provided by a JAR. Instead, it uses JODE to decompile the class back to source code and shows you that. It does a quite credible job, especially if the class was compiled with debugging symbols. This is usually more than sufficient for when I have a simple question about an API ... better than hunting around for the source on the web, or finding a source download to attach.