:
LZX is a whole new language, and the architecture of a typical Laszlo application is very different from the architecture of a typical web application. It's impossible for me to take you through all of Laszlo, and explain how it fits together in detail. But by walking through part of a simple application, I can give you a good feel for how the language holds together, and what it's capable of (see the sidebar Getting Started Quickly (on Windows)).
The application I'll use is called Oranges. It does the following:
This isn't a complicated application. But it does have three screens (the login screen, the failure screen, and the data screen), some screen transition logic, and some user-specific data. Which means it's a fine way to demonstrate some of Laszlo's capabilities.
We'll start by designing our login page. Login screens have several properties: they're centered in the screen, they ask for a name and password, and they can't be closed or circumvented. Here's the Flash generated by the LZX compiler for a simple login screen:
This is a very simple screen, but it already illustrates some fairly important points about Laszlo. For example, if you type in your name and then hit
Tab, you'll see an animation visually guide you to the next field. That's part of what Laszlo CTO David Temkin refers to as a cinematic user experience--users get visual cues and prompts that help them know what to do next.
Another important point is that you can click on the title bar and drag the window around. We've all become used to things like clicking and dragging in our desktop applications; an important part of rich internet applications is that they enable the same types of user interface actions in our web applications.
I also want to point out that I'm not showing you screenshots in this article; all of the examples in this article are actual applications compiled from LZX and embedded in this article using the
object tag. In fact, here's how I embedded the login screen above:
<object type="application/x-shockwave-Flash" data="example_one.swf" width="800" height="400"> <param name="movie" value="example_one.swf" /> </object>
The login screen above was pretty easy to write. In order to create it in in LZX, I simply used LZX's
modaldialog class and added some text fields. Here's the complete LZX program for the first login screen:
<canvas bgcolor="#335577"> <modaldialog name = "login_dialog" width="300" height = "200" title = "Please Login"> <form> <view width="200"> <reverselayout axis="x" spacing="10"/> <edittext></edittext> <text>Name:</text> </view> <view width="200"> <reverselayout axis="x" spacing="10"/> <edittext password="true"></edittext> <text>Password:</text> </view> <button text="Login" onclick="login()"/> </form> </modaldialog> <script> login_dialog.open(); function login() { // we'll implement this later } </script> </canvas>
The most important point about this code is that it's at least as readable as HTML. LZX is a very readable programming language, and it's designed to look as much like HTML as possible (the button even has an
onclick method). UI design in Laszlo is a lot like XHTML with a richer set of user interface controls and no cascading style sheets.
But even though the code is very readable, there's a lot going on in it. For example:
canvasnode. LZX programs are XML documents, and hence they have to have a single root node, which in LZX is the
canvasnode. The fact that LZX programs are well-formed XML has several obvious advantages. But it's also a little problematic, in that the entire LZX program executes inside a single canvas. You can have multiple "window" objects, but they're all constrained to live in one canvas (and, hence, inside of a single browser window).
mainfunction; instead, any "top-level" scripts (scripts that are direct children of the
canvasnode) are executed when the program loads.
formto hold several views. An LZX
Formis a lot like an HTML form, and is used in similar situations.
viewinside the
formused a
reverselayoutto organize its fields. Like Java, LZX uses layout managers to arrange components.
The next step in building Oranges is adding behavior to the login page. Remember that, as much as possible, we want the program to execute on the client side, without sending a large number of messages to the server. This means that instead of having the entire browser reload the page every time the application needs to show a new page, I'm going to define all of the pages inside of a single LZX application (which will be sent to the client as a single Flash movie) and write code to perform the page transitions myself.
I'm going to do this in stages. The first stage is simply adding event handlers to our form, and having it check whether the login is valid. As part of doing this, I'm going to turn both our login form and our login dialog into classes, and add methods to them. In the next version of Oranges, if you type secure as the password, you get one message. If you use any other password, something else happens.
Before I show you the code, though, here's the Flash generated by Laszlo for our new login panel:
The code for this version is very similar to the code for the first version. But, since we're exploring LZX, I've refactored it to take advantage of LZX's support for object-oriented programming. Rather than start by discussing the LZX object model, I'm going to show you the class definitions for the new login form and login panel, which I'm storing in a separate file named loginpanel.lzx. Here they are:
<library> <class name ="loginform" extends="form"> <view width="200" name="idview"> <reverselayout axis="x" spacing="10"/> <edittext name="name"></edittext> <text>Name:</text> </view> <view width="200" name="passwordview"> <reverselayout axis="x" spacing="10"/> <edittext name="password" password="true"></edittext> <text>Password:</text> </view> <button text="Login"> <method event="onclick"> var name = this.parent.idview.name.getText(); var password = this.parent.passwordview.password.getText(); login(name, password); </method> </button> <method name="clear"> idview.name.setText(""); passwordview.password.setText(""); </method> </class> <class name="logindialog" extends="modaldialog" width = "300" height = "200" title = "Please Login" x = "50" y = "50"> <simplelayout axis="y" spacing ="10"/> <text name="failureMessage" fgcolor="red" width="280"> </text> <loginform name="loginform"/> <method name="setMessage" args="message"> failureMessage.setText(message); </method> </class> </library>
In this code, instead of using
canvas as the top-level tag, I used the
library tag. The library contains two classes:
loginform and
logindialog. As before, LZX is very readable; most programmers can read the code and get a good idea of what's going on.
loginform is a subclass of
form that contains a pair of views (named
idview and
passwordview) and a
button that doesn't have a name, but does have an
onclick method.
The definition of
logindialog is equally transparent: it's a modal dialog that contains a text field for messages and a login form. It also encapsulates how messages are displayed by defining a
setMessages method.
As this example shows, defining classes in LZX is simply a matter of using the
class tag. Most of the things you'd expect to be true are true: classes can extend other classes (including classes provided by Laszlo Systems), can contain embedded components and methods, and so on.
However, what LZX means by "class" is subtly different from most programming languages. LZX uses a prototype-style object model, where instances are (basically) created as copies of the class definition. To see what that means, consider the following two instances of
logindialog:
<logindialog name = "standard_login_dialog" /> <logindialog name = "dysfunctional_login_dialog"> <method name="setMessage" args="message" /> </logindialog>
Both
login_dialog and
dysfunctional_login_dialog are instances of
logindialog (when we defined the
logindialog class, we implicitly defined the
logindialog tag as well). But
dysfunctional_login_dialog overrides the definition of
setMessage defined in
logindialog. When we override a method definition on an instance, we're not deleting the old method definition, or even changing the behavior of any other instance. We're simply changing the behavior of a single instance. This is something that's not possible in more traditional languages like Java.
Note also that we didn't name the button in
loginform. Logically, this is a lot like defining an instance variable with no name, and that's something that you can't normally do in an object-oriented language.
Another interesting aspect of LZX is illustrated by the login button in
loginform.
<button text="Login"> <method event="onclick"> var name = this.parent.idview.name.getText(); var password = this.parent.passwordview.password.getText(); login(name, password); </method> </button>
The
onclick function references a JavaScript function named
login that isn't defined in the library at all. This simply becomes a call to a global function, defined in a
<script> block inside of the main program. The presentation server will bind the call when it builds the Flash movie; until then, there's no way of knowing whether the function being referenced exists.
Now that we've defined our classes, it's time to look at the actual program. Here's the code:
<canvas bgcolor="#335577"> <!-- Most of our custom objects are defined in library files --> <include href="loginpanel.lzx"/> <!-- Define some visual objects --> <logindialog name = "login_dialog" /> <!-- Define the login function --> <script> <![CDATA[ function startApp() { login_dialog.open(); } function login(name, password) { if (invalidLogin(name, password)) { login_dialog.setMessage("Dude, That Was Both Lame <b>and</b> Bogus") } else { login_dialog.setMessage("You logged in. <b>Awesome!</b>"); } } function invalidLogin(name, password) { if (password=="secure") { return false; } return true; } ]]> </script> <!-- Let's get the party started --> <script> startApp(); </script> </canvas>
This is really straightforward code: we include the library, we create an instance of
logindialog, and we define the
login function it requires. But somehow, out of that, we've got fairly rich behavior, in a browser-based application, using an object-oriented programming language. That's pretty neat.
The next step in building Oranges is to add in a second screen, which tells the user how many oranges they've eaten. In order to do this, I'm going to take advantage of another one of LZX's features and use an XML dataset. In LZX, information is usually stored in datasets, and then accessed via XPath. In fact, datasets are important enough that there are two distinct ways to interact with them in LZX: either via
Datapaths or
Datapointers.
For starters, here's what an XML dataset looks like:
<dataset name="people"> <people> <person> <loginid>dsteinberg</loginid> <firstname>Daniel</firstname> <lastname>Steinberg</lastname> <numberoforanges>124</numberoforanges> <password>secure</password> </person> <person> <loginid>chromatic</loginid> <firstname>chromatic</firstname> <lastname>NA</lastname> <numberoforanges>10089</numberoforanges> <password>reallysecure</password> </person> </people> </dataset>
Strictly speaking, this is a local dataset; it contains all of its information, instead of simply containing a reference to the information. The structure is simple, though: you use the
dataset tag and then, within it, store data in XML format.
At this point, if you're an experienced DHTML programmer, you might be thinking, "Why aren't we using JavaScript arrays and variables to hold information? After all, we are building web applications, and in DHTML applications, client-side application state is usually stored in JavaScript variables."
The answer is that datasets are simpler, cleaner, and more standardized. Using datasets involves deciding on a representation. (In the example above,
loginid is stored in a subtag of
person instead of as an attribute. Why? Because that's the way it's done in the example.) But everything except the particular choice of representation is done in a standard way. This is in stark contrast to DHTML and JavaScript, where it seems that every web application does things slightly differently.
The information inside of an XML dataset is manipulated via XPath (note that while it doesn't support all of XPath, LZX supports a fairly large subset of XPath). While a complete discussion of XPath is well outside of the scope of this article, it's fairly easy to use basic XPath expressions by following a few simple rules.
:).
@stands for attribute.
text()means "give me the text associated to a node."
Thus, in the above dataset, we have:
people:person/loginid/text()returns
dsteinberg.
people:person[1]/loginid/text()returns
dsteinberg.
people:person[2]/loginid/text()returns
chromatic.
Given a dataset, there are two basic ways to display it using LZX. The first way is to use implicit binding. The idea here is that you attach part of a path to a view (or other user interface component) as an datapath. LZX is smart enough to iterate over all of the nodes matching the datapath, and repeatedly render the component, binding the contents of the component to a different node each time. Thus, for example, the following Flash movie:
Was generated by the following LZX.
<view name="table"> <simplelayout axis="y" spacing="10"/> <view datapath="people:/people/person"> <simplelayout axis="x" spacing ="10"/> <text datapath="loginid/text()" /> <text datapath="firstname/text()" /> <text datapath="lastname/text()" /> <text datapath="numberoforanges/text()" /> </view> </view>
The way to read this is that LZX iterates over the view once for each top-level node matching the partial path
people:/people/person. During each iteration, Laszlo inserts a number at the end of the partial datapath, and then the text nodes inside of the view complete the path. For example, on the first iteration, the view has the path
people:/people/person, the LZX runtime inserts a
[1], and then the first text node appends
loginid/text(). The net result? On the first iteration, the first text node displays the value of
people:/people/person[1]/loginid, which is
dsteinberg.
The above code is therefore, visually, the same as the following code.
<view name="table"> <simplelayout axis="y" spacing="10"/> <view> <simplelayout axis="x" spacing ="10"/> <text>dsteinberg</text> <text>Daniel</text> <text>Steinberg</text> <text>124</text> </view> <view> <simplelayout axis="x" spacing ="10"/> <text>chromatic</text> <text>chromatic</text> <text>NA</text> <text>10089</text> </view> </view>
The fun part about this, however, is that you can change the dataset (and you can tell the view to automatically update itself).
The second way to manipulate datasets is via datapointers. A datapointer is, more or less, an object corresponding to a partial XPath expression (in much the same way that a view has a partial path). You define them using the
datapointer tag, as in the following code snippet:
<datapointer xpath="people:/"> </datapointer>
In order to use datapointers, you usually define methods on them (since datapointers are instances of a class, you can add instance-specific methods, just like every other Laszlo object). Here, for example, is a very inefficient way to query the dataset defined above.
<datapointer name="database" xpath="people:/people" rerunxpath="true"> <method name="getNodeNumber" args="loginid"> <![CDATA[ for (i=0; i < this.getNodeCount(); i++) { var var nodeNumber = getNodeNumber(loginid); return (-1 != nodeNumber ); </method> <method name="getFirstName" args="loginid"> getNodeNumber(loginid); return this.xpathQuery("firstname/text()"); </method> <method name="getLastName" args="loginid"> getNodeNumber(loginid); return this.xpathQuery("lastname/text()"); </method> <method name="getNumberOfOranges" args="loginid"> getNodeNumber(loginid); return this.xpathQuery("numberoforanges/text()"); </method> <method name="getPassword" args="loginid"> getNodeNumber(loginid); return this.xpathQuery("password/text()"); </method> </datapointer>
This defines a datapointer and adds a few methods to it that are specific to the dataset it references. It's very inefficient, because it constantly resets the path of the data pointer in order to perform queries. (If we were worried about performance, or if the dataset were very large, we might wind up building secondary data structures and caching the results of commonly used XPath queries.)
Given our XML data set and our datapointer, we can now define our second screen and proceed to populate it. As usual, we start with the completed Flash. Here's the final version of the Oranges application.
With this version, if we enter "dsteinberg" with the password "secure," the screen changes entirely, to show us the number of oranges this particular user has eaten (and similarly for "chromatic" with password "reallysecure").
The code for this involves:
personalinformationwindowthat displays information about a person.
None of this is particularly hard at this point; the most complex thing involved is probably the JavaScript for handling logins. Here's that code:
<script> <![CDATA[ function startApp() { login_dialog.open(); } function login(name, password) { if (invalidLogin(name, password)) { login_dialog.setMessage("Dude, That Was Both Lame <b> and </b> Bogus") } else { successfulLogin(name); } } function invalidLogin(loginid, password) { if (database.isValidLoginId(loginid)) { return password != database.getPassword(loginid); } return true; } function successfulLogin(loginid) { login_dialog.close(); personal_information_window.display("ed", "zshau", "50"); var firstname = database.getFirstName(loginid); var lastname = database.getLastName(loginid); var numberoforanges = database.getNumberOfOranges(loginid); personal_information_window.display(firstname, lastname, numberoforanges); } ]]> </script>
All this does is tie our original
onclick, which calls
login to our new datapointer and XML dataset.
At this point, I've shown you an application that validates a login and then displays a second page. While it is, admittedly, a very simple application, it does illustrate many of the basic principles of LZX programming. The glaring exception is communicating with the server. No matter how rich and interactive our Flash movie is, it needs to occasionally fetch data from the server, and occasionally store data to the server.
In Laszlo, this is also done via XML datasets. Instead of actually inserting the data into the dataset directly, you can specify a URL and have Laszlo fetch the data while the application is running. Similarly, you can alter a dataset, and then send the altered data to the server (getting back a dataset as a return value).
In both cases, communication with the server is done by sending XML payloads over HTTP. In essence, Laszlo replaces the standard web application model, fetch the entire page in HTML, submit forms, and then get the entire page back, with get and put XML datasets that just contain the information that we need.. When you add in the fact that the server compresses the XML, this results in a remarkable savings in bandwidth.
The Laszlo manual contains three entire chapters on how to do this, and effectively writing a data-driven application is far too large a topic to include in this article. So I'll just close with a quick warning: done naively, using XML datasets instead of HTTP might not improve the user's perceived performance very much. The problem is: the application is still communicating with the server. Suppose, for example, that the login panel above was implemented as follows:
This sends less data over the wire (especially after compression), but it still makes as many server calls as a standard web application. Which means that, while it reduces screen flicker (no ugly "white screens" while the page is refreshing), and reduces the total amount of data transmitted, it doesn't significantly reduce application latency very much, unless the time it takes for the web browser to render the screen is significant.
At this point, you've seen enough of Laszlo, and learned enough about both Laszlo and rich internet application frameworks in general, to start wondering whether it's an appropriate technology for you to use. There are really two questions involved, though, and it's best to separate them:
If you've read this far, and you're reading this section, then there are pretty good odds that you're intrigued by rich internet applications. But you're probably also a little bit skeptical. After all, these days, most new applications are built as web applications. For the most part, that means applications are built on the HTTP model: a stateless client that gets its entire user interface sent (as an HTML document) from the server each time the user does anything. And there have been numerous unsuccessful attempts to replace the HTML/HTTP-based Web over the years. (Speaking as someone who once owned a copy of Late Night VRML with Java, I can personally attest to this.)
To date, all of them have failed to gain significant traction because the HTML/HTTP-based Web has some very real advantages. Among them are:
In short, HTML and HTTP are commonly used because they're amazingly useful. In order to find a better application development model, you need to have an application where the Web doesn't work so well. Typically, this sort of application will have the following positive characteristics:
and the following negative ones:
Does your application meet these criteria? It's hard for me to say. I will say that web-based email and calendaring are pretty clearly in the sweet spot for rich internet applications, and that weblogs probably aren't.
Once you decide that a rich internet application framework is appropriate for your project, the question then becomes, "Should you use Laszlo?" I'm very fond of Laszlo. It has some impressive features in the current product (the 2.2 version) and some of the features coming in the 3.0 version are very nice (chief among them will be the ability to simply deploy the Flash, without needing a presentation server).
On the other hand, Laszlo also has some significant limitations. Many of them come from the fact that Laszlo currently only deploys via Flash. Flash is a nice platform, and using Flash makes Laszlo very easy to deploy, but it also means that the client side has some performance limitations. At the end of the day, the Flash virtual machine was designed for displaying movies. It's not about numerical calculations or large-scale data manipulations and I'm pretty sure Laszlo clients won't handle a XML dataset with 20,000 rows very well.
For similar reasons, Laszlo applications are single-threaded and tend to be event-driven. If your client application needs the ability to perform background computations, it's just not going to fit comfortably into the LZX application unless you can offload the computations to a server process.
The other major caution I'd give you is that LZX involves a new style of programming, and a fairly new programming language. The decision to use Laszlo is inherently a decision to be on the edge. There are very few libraries or frameworks for Laszlo at this point in time, not many people know how to use it, and the bookshelves are bare. If you're not an early adopter, this is not the time to adopt Laszlo.
All in all: should you adopt Laszlo? Maybe. What's very clear is that you should definitely download it and think hard about how rich internet applications will change the Web.
William Grosso is the vice president of engineering for Echopass.