Our Feature Presentation

* - *
|

Laszlo has a bunch of nifty databinding features. A lot of them are fairly high-level and quite powerful, but I've noticed (and experienced) that there are a lot of cases that they don't quite cover. It's been interesting to be involved in designing APIs and then to see how they are used in the wild. Well designed parts of the API not only flourish -- they become support for increasing levels of elegant and useful abstraction.

I've written about some of these successes and failures before, but with some additional perspective, I've realized that a well designed API is a like a good novel. In a good book, the story that you read is the tip of an iceberg that sits atop a carefully thought-through milieu of characters and situations. Even if you, as a reader, never untangle the various threads of the plot backstory you sense its solidity; the characters take on weight that extends beyond their actions in the plot.

A good API has that same feeling of solidity. It may present a fairly high level of abstraction, but as a user of it, you sense its cohesion and its representation of something integral and fundamental. A bad API, like a bad novel, feels like a trick: key information is withheld and simple relationships are hard to deduce.

The Laszlo data replication stuff is more like the latter. The entry points for sorting are hard to understand, and developers are forever confused about how to go back and get the view which represents a piece of data.

In our painfully slow but ever-striving process of self-improvement, we introduced a small method in OpenLaszlo 3.0 that gets developers closer to the truth of the matter: data replication is essentially a controller which iterates over a list. Before OL3, this list could only be treated as an abstraction: the set of nodes that is selected using an XPath query.

It turns out that there was a perfect entry point for this already in the LFC, and all that we needed to do was expose it in the right way. Of course, that last bit was non-trivial, and we had been stewing on the right design for a few releases. Here's an example of the basic replication feature, as it can be used to represent a simple and arbitrary list of vegetables:

<canvas proxied="false">
    <dataset name="dsVeggies" src="vegetables.xml"/>

    <window width="300" height="300">
        <view>
            <text width="300">
                <datapath xpath="dsVeggies:/vegetables/*/text()"
                          sortpath="text()"
                          replication="lazy"/>
            </text>
        </view>
        <scrollbar/>
    </window>
</canvas>

Classic question: how do I add sorting to this? Ok well it's not so bad, you just have to define a sortpath, which is an xpath expression that returns a key that you can sort on.

        <datapath xpath="dsVeggies:/vegetables/*/text()"
                  sortpath="text()"
                  replication="lazy"/>

And we get an app that looks like this: (Source)


Of course, it's a little strange that we have to repeat the text() operator, but let's just assume that we can live with that for now. Ok, works well for a very simple case, but it's hard to imagine exactly where that sort got applied. Is the original list now sorted? (No.) If I add an item to the original list, does it just get inserted or does the whole list have to be re-sorted? (Sadly, the latter.)

It quickly gets harder, though, when we decide that we want to sort on two keys.Like any reasonable person, I want to put the icky vegetables last -- they're marked as such with an icky="true" attribute. This can be accomplished by using a sortpath of "." and defining a sort function, but these APIs are a little confusing. Wouldn't it be better if we could just come up with the list of nodes we want to represent and then hand it to a replication manager?

Well the good news is that now we can. The setNodes API allows for this. First we grab the list of nodes, and then we sort them however we like, using ECMA's built-in Array.sort method.

<canvas proxied="false" height="200">
    <dataset name="dsVeggies" src="vegetables.xml"/>

    <script>
        <![CDATA[
        function sortEm( a, b ){
            var aicky = a.getAttr( 'icky' );
            var bicky = b.getAttr( 'icky' );
            if ( aicky == bicky ){
                return a.getFirstChild().data < b.getFirstChild().data ?
                       -1 : 1;
            } else if ( aicky == "true" ){
                return 1;
            }
            return -1;
        }
        ]]>
    </script>

    <method event="oninit">
        var list = dsVeggies.getFirstChild().childNodes.concat();
        list.sort( sortEm );
        gReplicated.datapath.setNodes( list );
    </method>

    <window title="Fruits and Vegetables" width="300" height="200">
        <view>
            <text id="gReplicated" width="300">
                <datapath xpath="text()" replication="lazy"/>
            </text>
        </view>
        <scrollbar/>
    </window>
</canvas>

There we go. Lima beans at the bottom, where they belong.

The other nice thing about this API is that now we have direct control over the list. We can add a method to our list manager that lets us remove an element in the list without having to muck with the data. As a side benefit, these types of operations can be much more efficient than using the xpath operators and complicating the client-side data with additional markup about how it's being displayed. (Source)

<canvas proxied="false" height="200">
   ....
    <node name="gListMan">
        <attribute name="list"/>

        <method name="reset" event="oninit">
            this.list = dsVeggies.getFirstChild().childNodes;
            list.sort( sortEm );
            this.updateList();
        </method>

        <method name="removeElement" args="el">
            for ( var i in list ){
                if ( list[ i ] == el ){
                    list.splice( i , 1 );
                    this.updateList();
                    return;
                }
            }
        </method>

        <method name="updateList">
            gReplicated.datapath.setNodes( list );
        </method>

    </node>
    ....
            <text id="gReplicated" width="300" ...
                   onclick="gListMan.removeElement( datapath.p )">
                <datapath xpath="text()" replication="lazy"/>
            </text>
            ....
    <button onclick="gListMan.reset()" x="320">Reset

Now by clicking on elements in the list, the user can remove them, all without altering the original data. Hopefully, it's clear from this example how this feature could be used to implement things like a quick search filter.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Copy the characters (respecting upper/lower case) from the image.
|
* * *