I've been meaning to tap this out for a while.

on a recent project (T5.1), i found tapestry's XHR handling/zone updating architecture lacking. I do hope there's a better way to achieve what i needed to which is why i'm writing this. if there is not i think there's a strong case to tidy the implementation up somewhat. it's certainly not that tapestry can't provide the functionality required, just that the API doesn't feel particularly well thought out in this one area. i'll explain...


     1. Using the zone parameter to flag an event as XHR

To specify that you want to perform an XHR request (EventLink, ActionLink, Form etc), you need to supply the zone parameter. The existence of this parameter is a flag that tells the component to use XHR. In some cases this may be useful (although I'm yet to find one). This strikes me as bad design since there is not necessarily a known One-to-One relationship between the event and the zone(s) updated.

Since all of my XHR event handlers return a MultiZoneUpdate, I ended up creating a dummy zone on every page and component and supplying that to every zone parameter. The dummy zone was always hidden and never actually updated. This hack made it easier to code and maintain my pages.

Obviously an improvement would be to add a 'xhr' parameter to the components (EventLink, Form etc) and for zone to be an optional parameter if xhr is true. You could even hard set xhr=true if zone != null for backwards compatibility.


     2. MultiZoneUpdate is a chain of MultiZoneUpdates

Once you're in the java implementation of your event handler, you can either return a Zone object, or a MultiZoneUpdate object. (if you've supplied a non-dummy zone you can just return a "renderer" which will be inserted into the zone). If you (like me) want to return a MultiZoneUpdate you need to:

  1. test exists = false therefore create new MultiZoneUpdate(zoneId,
     zone) and store as component variable
  2. test exists = false therefore add subsequent
     MultiZoneUpdate(zoneId, zone)s to the initial MultiZoneUpdate
  3. return the initial MultiZoneUpdate

Since sometimes due to method re-use etc a particular statement may or may not be the one which creates the initial MultiZoneUpdate, and some re-used methods would add updates which had already been added by another method. I found I needed to create a utility class - MultiZoneUpdateManager - to manage the MultiZoneUpdate chain building. This solved two issues: testing whether the initial MultiZoneUpdate was initialised and avoiding duplicate Zones within the chain. The process was simplified to:

  1. create new MultiZoneUpdateManager() update and store as component
     variable
  2. MultiZoneUpdateManager.add(zoneId, zone)
  3. return MultiZoneUpdateManager.getMultiZoneUpdate()

I would suggest the refactoring of MultiZoneUpdate so that it

  1. can be constructed without a zone update request
  2. keys updates by zoneId (no point performing multiple updates on a
     single zone)
  3. behaves as if 'null' was returned (from the event handler) if it
     is returned with no zone updates

I don't see any reason why this couldn't be backwards compatible.


     3. XHR requests should be easily callable from javascript

I quite often needed to initialise an XHR request-response from javascript. I ended up writing a function to facilitate this. It handles:

  1. the zone wiring (note the need for a dummy zone shows up here too)
  2. context parameters (unfortunately does not properly conform to
     encoding rules as per server side generated params)
  3. query strings (sometimes useful)
  4. url based session ids (for when cookies are disabled)

note that the url is usually generated at the server side via ComponentResources.createEventLink.

function multiZoneUpdate(url, params, zoneId)
{
    if (typeof(zoneId) == "undefined")
        zoneId = "dummyZone";
    var zoneObject = Tapestry.findZoneManagerForZone(zoneId);
    if (!zoneObject)
        throw "unknown zone: " + zoneId;

    if (!(params instanceof Array))
        params = [params];

    var qs = "";
    var qsInd = url.indexOf("?");
    if (qsInd != -1)
    {
        qs = url.substring(qsInd);
        url = url.substring(0, qsInd);
    }

    var jsId = "";
    var jsInd = url.indexOf(";");
    if (jsInd != -1)
    {
        jsId = url.substring(jsInd);
        url = url.substring(0, jsInd);
    }

    if (params != null)
        for (var p = 0; p < params.length; p++)
            url += "/" + params[p];

    zoneObject.updateFromURL(url + jsId + qs);
}

It would be preferable if tapestry could create the XHR request without a zoneObject (see point 1) and exposed a method to mimic the encoding of context parameters.


     Summary

For the most part, tapestry has been a breeze to work with, however It would be easier to continue developing complicated ajax applications with these few changes. I hope I can either be pointed in a better direction, or that these ideas are considered in future releases.

Cheers, Paul.

Reply via email to