Working on the Open Laszlo LZPIX multiple runtimes demo made me fall in love with data-binding in lzx. LZPIX is a flickr browser written in Open Laszlo, which can be compiled as a DHTML app or a Flash app. To share the data-binding joy, I wrote a little flickr browser (requires Flash Player 7 or later) and annotated the application code.
rubyonrails.org has a really cool screencast showing how they built a flickr viewer in five minutes with, of course, Ruby on Rails. That screencast inspired me to get a Rails environment set up on my mac, so I'm following suit with a similar Open Laszlo app in the hopes that I'll inspire more developers to give Open Laszlo a test run. I'm not trying to argue that Ruby on Rails is better or worse than Open Laszlo. Consider this article as "imitation as flattery" not "competitive analysis."
Open Laszlo is a great platform for mashups. (marketing-speak: Open Laszlo is a great platform for building rich internet applications using third-party REST web services.) OL has data binding and replication built in. The results of an xml query (a "dataset" in OL) can be mapped directly to elements in the OL scene graph (the "canvas" in OL). Attributes of elements in the OL scene graph can be mapped from attributes of the elements in the dataset. The data replication manager creates views to represent each result of a query to a REST api.
I've used a simple wrapper for the flickr api, written by Elliot Winard. For this article, I'll write from the perspective of an application developer using this existing api. libflickr defines a class, photo, which is a thumbnail view of an image in a result from a flickr query.
OL is tag-based and requires well-formed XML, so I start off my lzx document with a single root element, canvas, and include the flickr library:
<canvas title="Flickr on Open Laszlo">
<include href="incubator/libflickr.lzx" />
To access flickr, we need an api key. I define a global attribute with the value of my api key. Get your own from flickr
<attribute name="myapikey" value="xxxx_GET_YOUR_OWN_xxxx" type="string"/>
At first I just want to view some flickr data without worrying about how I got it, so I'll just use this little stub that kicks off a simple query when the canvas initializes. I call the method "getRecent" on the gFlickr node, which is global. I pass in my api key as an argument to the request.
<method event="oninit">
gFlickr.getRecent(canvas.myapikey);
</method>
Add a view for the search results, containing its own layout:
<view name="results"
x="10"
width="${parent.height-20}" >
<!-- The wrapping layout arranges the search results in a grid, and
starts a new row ("wraps" the contents) when the contents fill each row.
This gives grid-like behavior without scaling the subviews.
-->
<wrappinglayout axis="x" spacing="5" />
<!-- Data binding is so cool. This element says, "make an instance
of class coolphoto for the first ten nodes in the dataset "photods" with
"rsp" as grandparent and "photos" as parent.
-->
<photo datapath="photods:/rsp/photos/photo[1-24]" />
</view>
That's all it takes. The app now displays see a grid of photos recently added to flickr. (Here's the code so far)
Now let's enhance it. I want some more interesting searches, so I'll add a few buttons to control the search:
<view
x="10">
<!-- Within this view, put the subviews in a row. -->
<simplelayout axis="x" spacing="5" />
<!-- Label the search field -->
<text y="5">Search Flickr:</text>
<edittext name="tagQuery" text="lzpix">
<method event="onkeyup" args="kc">
if (kc == 13) gFlickr.searchWithTags(this.getValue(), canvas.myapikey);
</method>
</edittext>
<button onclick="gFlickr.searchWithTags(parent.tagQuery.getValue(), canvas.myapikey)">
go
</button>
<button onclick="gFlickr.getRecent(canvas.myapikey)">recent</button>
<button onclick="gFlickr.getInteresting(canvas.myapikey)">interesting</button>
</view>
I want to show a larger image when I click on a thumbnail, so I'll create a view for the larger image:
<view id="gPhotoDetails"
x="10" y="${parent.results.y + parent.results.height + 10}">
</view>
Nothing goes in that view yet, though, so I'll subclass the photo class, yielding the coolphoto class, and add an onclick handler. In the onclick handler, I'll set the source of the details view to the url to the medium sized image.
<handler name="onclick">
var s = this.getImageURL("_m");
gPhotoDetails.largeview.setSource( s );
</handler>
Now the app now has some search ui, and shows a larger image when you click on a thumbnail.(Here's the code so far)
Let's add some tasty effects. I'll add an animator to the coolphoto class, which fades in the photo from transparent to fully opaque.
<!-- This animator fades the image in by animating its opacity. -->
<animator name="appear_animator"
attribute="opacity"
to="1"
start="false"
duration="800"/>
We want that animator to start as soon as we've got the data for this photo, so we add an init handler:
<handler name="ondata" args="d">
<![CDATA[
// Start the fade-in animation
appear_animator.doStart();
]]></handler>
I want to show some information about the thumbnails when I mouse over them. I'll create a tiny view called gFloater to float in front of the photos, and animate the floater to just below the photo being mouse-over'd. To get the photos title and owner, I just run an XPath query on my data.
<view id="gFloater" width="160" bgcolor="#333333"
options="ignorelayout"
x="1000"> <!-- start off offscreen -->
<simplelayout axis="y" spacing="1" />
<text name="titlelabel" fgcolor="white" width="150">title</text>
<text name="ownerlabel" fgcolor="white" width="150">owner</text>
</view>
<method event="onmouseover">
gFloater.animate("x", this.x + 10, 500);
gFloater.animate("y", this.y + parent.y + this.height - 10, 500);
gFloater.titlelabel.setText( "t: " + this.datapath.xpathQuery('@title'));
gFloater.ownerlabel.setText( "o: " + this.datapath.xpathQuery('@ownername'));
</method>