Why I Like Spry

I like Spry. Why? Simple - they got their data model right.

Not that I’ve done a lot of AJAX work, but from what I’ve seen of other frameworks, it seems that the general focus has been on creating cool UI components. While this is not at all a bad thing, the result is that AJAX data models are not nearly as mature as they could be.

The programming models for most of these frameworks are typically transactional - user performs action on UI, javascipt makes asynchronous call to web server, and registers a callback function to handle the results.

Spry does things quite differently. You start by defining Spry datasets to asynchronously retrieve the data you need to populate on your page, like so:

var dsCategories
= new Spry.Data.XMLDataSet("shoppingcart.cfc?method=getCategories", "categories/category");
var dsItems
= new Spry.Data.XMLDataSet("shoppingcart.cfc?method=getItems&categoryid={dsCategories::categoryid}", "items/item");
var dsDetails
= new Spry.Data.XMLDataSet("shoppingcart.cfc?method=getItemDetails&itemid={dsItems::itemid}", "details/item");

The example above is from a simple shopping cart application I wrote to get a feel for Spry. Note the CFC calls - for those who don’t know, you can invoke CFC methods that have the ‘access=remote’ attribute specified via HTTP calls. Unfortunately, the WDDX XML format that ColdFusion outputs by default for these kinds of calls is not flat enough for Spry to process, so you’ll have to create your own XML dialect and return that from your CFCs - actually, it might even just be easier to create a CFM that outputs the XML.

The Spry datasets load XML returned from the specified URLs, parse it down to the level of the provided XPath expression, and then extract data from that level of the document into a tabular format. Sounds a bit complex? The Spry team has you covered - go take a look at the Spry Dataset and Dynamic Region Overview for a good introduction.

The really interesting part of the dataset definitions, though, is that you can create what virtually amounts to foreign keys between the datasets. The first dataset, dsCategories, loads a set of categories for items that are available in the store. The second dataset, dsItems, loads the set of items for the currently selected category, using the expression {dsCategories::categoryid}. The third dataset, dsDetails loads the details of the currently selected item, using the expression {dsItems::itemid}. You can change the selected entry in a dataset (category or item in this case), and Spry will figure out what datasets depend on the change and reload them automatically - cool, huh?

So I hear you thinking to yourself, datasets, blah, blah, I need to get some data on my UI! Spry introduces the concept of dynamic regions to help you do just that. For example:

<div id="ItemList" spry:detailregion="dsCategories dsItems" class="SpryHiddenRegion" >
<div spry:state="ready" spry:repeat="dsItems">
<div class="ItemColumn" onclick="dsItems.setCurrentRow('{dsItems::ds_RowID}');">
{dsItems::name}
</div>
</div>
</div>

This fragment of code shows how Spry will allow you to loop over the dsItems dataset to display the list of items for the selected category. Note that the outermost div has a spry:detailregion attribute, making it a Spry dynamic region, which points to the datasets, dsCategories and dsItems, that this dynamic region depends on. What does this mean? When a new category is selected in the dsCategories dataset by calling dsCategories.setCurrentRow(’{dsCategories::ds_RowID}’) on click of a category in the category list, the ItemList region is reloaded, since it declares a dependency on the dsCategories dataset. Moreover, the dsItems dataset is also reloaded, since it has a dependency on dsCategories as well, so the list of items that gets displayed is for the selected category.

This is what makes me like Spry so much - you can define dependencies between datasets and display regions, and then not worry about AJAX at all. The programming model is no longer transactional - once the datasets and regions are defined, code deals only with ensuring that the dataset state is maintained properly, typically by grabbing events such as onClick to set the current item in a dataset. There’s none of the conventional worry about setting up a response handler for each event, writing code in that handler to parse the response from the server, and then populating the response elements into the HTML DOM tree.

There is, however, one place where this model breaks down for Spry. If you need to send data to the server, rather than retrieve data from the server, you’re going to have to go the usual route and write callbacks. For example, in order to add an item to the shopping cart, I had to write the following bit of Javascipt:

Spry.Utils.loadURL("post",
"shoppingcart.cfc?method=addItemToCart&name=" + name + "&price=" + price + "&itemid=" + id + "&quantity=" + quantity,
true, null);

Not so cool - I would have much preferred it if Spry allowed me to add a row to the shopping cart dataset and register a listener there which would take care of exactly this kind of interaction. Client-side AJAX datasets with full CRUD capabilities, yum! ;-)
From the Spry forums, I see that it is the stated intention of the Spry team to have Spry play well with just about any other AJAX framework - more wholesome goodness, use your Spry datasets and dynamic regions to wrap UI widgets from OpenRico, MochiKit, Dojo, or whatever suits your fancy. The current version does not, alas, work over Prototype-based frameworks, such as OpenRico, but that should be resolved with the next release. For those who want Prototype support now, check out Big Doug’s Spry Patches.

Spry is still an early beta - I, for one, am looking forward to seeing what this framework looks like when it gets to final release.