The Source for Java Technology Collaboration
User: Password:



Hans Muller's Blog

April 2005 Archives


Data Binding in Laszlo - Lessons for JDNC

Posted by hansmuller on April 07, 2005 at 05:05 PM | Permalink | Comments (8)

For the past two months or so, I've been working with some of the JDNC developers on the databinding problem. After some false starts, the approach we've taken is to define "data aware" components and some special encapsulation classes for relational data. The overall goal is to make forms and master/detail applications relatively easy to build by automating most of the donkey work involved in interconnecting data sources with Swing GUI components and their attendant data and selection models. All in all not a terribly novel quest, however it's an important one and it's long overdue.

Data binding is an old problem and there are plenty of worthy examples to learn from. The support in ADO.NET and the newer version of that in Avalon certainly qualifies, although the complexity of Microsoft's solution should serve as a warning about the dangers of trading simplicity for generality. The JGoodies data binding system , which is based on the SmallTalk ValueModel idea, is another fine example. The Laszlo platform, which is popular among Flash aficionados and can boast some very beautiful demos, also supports basic data binding. I'd written a short review of Laszlo's databinding support recently and the JDNC developers with whom I'd been working suggested publishing it here.

In case you're already bored I'll provide the punchline first. We've observed that data binding systems that bury the natural syntax for specifying bindings with abstractions can be more trouble than they're worth. If I'm binding to JavaBeans then strings using the usual "bean.property" expression language notation are a nice way to specify bindings. Similarly, if you're binding to an XML document, then an XPath expression is a natural way to define a component's data source. Laszlo's data binding system is designed just for XML data and they employ an XPath subset for specifying bindings. Here's how.

What follows is based on a quick study of the data binding part of the Laszlo developer documentation, chapters 28-30 . The Laszlo designers had about as much flexibility as anyone could want, since they designed a new "language" (an XML schema) for defining applications that targeted Macromedia's Flash player.

Laszlo applications are defined by an XML document. Trivially simple GUIs survive the encoding and remain simple:

<canvas width="500" height="350">
    <text>Hello World</text>
</canvas>

Complex GUIs are probably more compact than a Java version however in my (biased) opinion, one ends up writing too much cryptic JavaScript/unix-shell style line noise like:

<tree datapath="*" text="$path{'@name'}" 
    isleaf="${this.datapath.xpathQuery('@type') == 'file'}"/>

The Laszlo platform includes a modest number of basic GUI controls; behavior is defined with JavaScript. There are a handful of very primitive layout managers however if the documentation is any indication, absolute layout is the norm. Platform look and feel fidelity is not a goal.

Laszlo supports XML data binding with XPath (a small subset) expressions. The XML data can be embedded in the application source code, which is good for examples, or it can be loaded from a URL. The binding system works nicely for simple things however once the world of "hello world" is left behind, Laszlo gets ugly in a hurry. There doesn't appear to be any support for master/detail applications with their pesky connections to selection. Apparently master/detail relationships must be defined at the level of writing "onClick" handlers. Updating a relational database is a similarly do-it-yourself operation.

That said, the basic XML data binding support is tidy enough, particularly if you're familiar with XPath syntax. To bind to an XML document you have to give it a name, and to do that you use the dataset tag:

<canvas>
    <dataset name="customerData">
        <customers>
	    <customer firstName="Fred" lastName="Mertz"/>
	    <customer firstName="Ethel" lastName="Mertz"/>
        </customers>
    </dataset>
    <simplelayout axis="x"/>
    <text datapath="customerData:/customers/customer[1]/@firstName"/>
    <text datapath="customerData:/customers/customer[2]/@firstName"/>
</canvas>

The dataset tag has a "src" attribute, like the HTML anchor or image tags, whose value is a URL. There's adequate support for programatically reconfiguring and and reloading a dataset and one can write handlers that get notified each time the dataset has been completely loaded.

Thanks to the "simplelayout" (like AWT flow) layout tag, this GUI just displays "Fred Ethel". The data binding for each text component is specified by the "datapath" attribute which names the dataset and, on the RHS of the ":", the XPath expression of the data that the component it bound to.

Note: if you're an XPath novice as I am, you might find the datapath expressions in the example a little bit confusing. They look like filesystem paths (and they're called "paths") but their semantics are subtly different. They're really patterns that match XML tag names and properties of XML elements and attributes. The latter are enclosed in square brackets, e.g. "[1]" means the element whose one-based index (relative to its siblings) is 1. So the XPath "/customers" means: all of the elements whose tag is "customers" and "/customers/customer[1]" selects the first child whose tag is "customer", and "/customers/customer[1].@firstName" selects its "firstName" attribute. It took me a while to stop trying to write "/customers[1]/@firstName". I'm over that now.

The Laszlo generic container class is called "view" and you can bind it to data and then use relative data binding paths on the components it contains. This example produces the same output as the previous one:

<canvas>
    <dataset name="customerData">
        <customers>
	    <customer firstName="Fred" lastName="Mertz"/>
	    <customer firstName="Ethel" lastName="Mertz"/>
        </customers>
    </dataset>
    <view datapath="customerData:/customers/">
      <simplelayout axis="x"/>
      <text datapath="customer[1]/@firstName"/>
      <text datapath="customer[2]/@firstName"/>
    </view>
</canvas>

Where Laszlo data binding gets interesting and ugly is in producing tables. There's no explicit table construct, you just bind a view to a list of elements and the GUI components contained by the view are "replicated", once for each list element.

<canvas>
    <dataset name="customerData">
        <customers>
	    <customer firstName="Fred" lastName="Mertz"/>
	    <customer firstName="Ethel" lastName="Mertz"/>
        </customers>
    </dataset>
    <simplelayout/>
    <view datapath="customerData:/customers/customer">
      <simplelayout axis="x"/>
      <text datapath="@firstName"/>
      <text datapath="@lastName"/>
    </view>
</canvas>

The XML above produces a GUI with four elements, like this:

Fred Mertz
Ethel Mertz

In other words, we're not binding firstName and lastName to columns in a special table component. What we've got is just short hand for creating one row of textfields for each customer element in the dataset. There is no selection support and watch out for large data sets. To deal with the latter you can try and configure the special "replication manager" which provides pools of components that can be reused for rows, and the "onclone" JavaScript callback that alerts you when a view has been replicated, and, well, yecch.

There are several other interesting features of the Laszlo databinding system that I don't think are worth diving into here. For example one can bind component attributes to XPath expressions (but be prepared to think hard about when those bindings are evaluated). There are "datapointers" which can be used to create bindings that get moved around with methods like datapointer.selectNext().

What's good about all of this is that, assuming you've got XML data, you can bind to that data using moderately intuitive XPath expressions. Returning to JDNC and given our canonical Customers, Orders, and Parts example, and a quick and dirty schema, you could write the bindings for the three tables (roughly) like this:

customersTable.setDataPath("/customers/customer");
ordersTable.setDataPath("/customers/customer[@selected]/order");
partsTable.setDataPath("/customers/customer[@selected]/order[@selected]/part");

<customers>
    <customer firstName="Fred" lastName="Mertz">
        <order id="o1">
	    <part id="p1"/>
	    <part id="p2"/>
        </order>
        <order id="o2">
	    <part id="p3"/>
        </order>
    </customer>
    <customer firstName="Ethel" lastName="Mertz"/>
        <order id="o3">
	    <part id="p4"/>
	    <part id="p5"/>
        </order>
    </customer>
</customers>

Here I've created a synthetic attribute called "selected" that's implicitly defined for all tags. If it's present then the corresponding XML DOM node is selected. The implication is that the binding engine that interpreted this binding would the customers JTable's selection model at run time. What's nice about this is that the developer gets to define the bindings using the same syntax as the (XML) data they're binding to.





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds