Strut This Way

* - *
|

Oliver Steele posted this excellent article on his blog about the differences between the architecture for a conventional server-side HTML application and that of a rich internet app. Oliver ends up espousing a Service-Oriented Architecture, which is a recieved idea about how to architect network-based applications. Oliver's architecture diagrams are beautiful, but I'm more of a learn-by-example type (Software Architect vs. Client Programmer?) so I wanted to use a practical example that shows how an existing webapp would be modified to incorporate Laszlo.

Most server-side developers who are new to Laszlo have a single, over-riding question, which has a few variants: "How does Laszlo work with my existing web application framework (usually Struts)?" You can see this again and again and again on the Laszlo forums. I don't feel strongly about Struts one way or another, so I don't wish to treat in detail the questions about whether Struts is the right framework for doing MVC in a webapp. The fact is that while many developers aren't using Struts, they have usually built some kind of framework for separating actions and views, because that relationship is so tortured in HTML. The swirl of issues usually boils down to these two or three questions:

  1. How do I embed a Laszlo app into a larger HTML-based application flow?
  2. and

  3. How do I get the data I have on the server into Laszlo?
  4. and sometimes

  5. How do I get some of that good ol' model/view separation within Laszlo?

The answer to these questions is the subject of this entry. I'd like to show in practical terms exactly how to adapt an existing web application to use Laszlo. I'm going to do all of this without using Struts (or, in fact, any MVC framework) but hopefully you'll see how this is relevant to a more complex example.

Let's start with a small webapp. This is my photoviewer, and it is composed of three extremely simple jsps. You can try the webapp here:



In this simple example, login.jsp presents a simple form, whose submission is forwarded to album.jsp. album.jsp then looks for a username passed to it, and if found, stores the username and the user's album in the session. If you look at the code, you'll note that the user identity is not authenticated, and that everyone gets the same album, but hopefully you'll get the idea. Once the album is retrieved, it is stored, along with the user identity, in the servlet session.

I should point out that this example does exactly what a framework like Struts prevents -- which is that login.jsp is hooked directly to album.jsp. We should have the action for the form on the login be more like /do/Login which then calls album.jsp on success -- but hopefully you see how this is relevant to a more complex example. I have worked around this problem by checking for null values passed to album.jsp in the username. If the value is null, the identity and album is retrieved from the session; if it is non-null, the authentication/data retreival code gets run. This allows me to use album.jsp to display the album whether I'm logging in for the first time or coming back to it from picture.jsp, but it's sloppy and fragile and all those J2EE MVC frameworks exist for good reason.

Anway, the picture album is represented in this code as a vector of HashMaps, but the particular implementation is unimportant. The assumption here is that you already have code which can pull database or other backend data into Java.

picture.jsp is similarly simple, and I've included it in the old app only to make a point about the new one. Once again, it retrieves the user name and album data from the session.

Now let's say that we want to employ Laszlo -- in this case to make better use of the extremely limited space for this little app. I'm not going to spend a lot of time on how the laszlo app is built, since I'm much more interested in how the laszlo app will work with the backend.

As a first step, let's leave everything else in tact, and turn the functionality in album.jsp into a laszlo app.

Something like this:



Not the most amazing demo, but at least you can immediately tell which parts are Laszlo. Let's take a second to break down the code.

I've had to rename all the files since all the links are written in to the jsps, but login1.jsp and picture1.jsp are the same, except that their links to the album are written as "album1.jsp".

Now, album1.jsp is simpler than album.jsp was -- its only responsibility is storing the username and album data in the session if it gets passed authentication information. Its other job is to embed the laszlo app, album1.lzx into the page. This is done by using Laszlo's lzEmbed call, which is a function that can write a Laszlo app into a page and is provided in a separate js file that is part of an LPS install.

In album1.jsp, the code that embeds the Laszlo control looks like this:

    <!-- include Laszlo's javascript library -->
    <script type="text/javascript" src="/lps-2.2.1/lps/includes/embed.js">
    </script>
    ...
    <script>
    <!-- Now call the lzEmbed function that's provided in the library-->
    lzEmbed({url: 'album1.lzx?lzt=swf', bgcolor: '#ffffff', width: '550', height: '300'});
    </script>

Again, I'm not going to dwell on the structure of the Laszlo app, which is trivial, but I want to focus on how the Laszlo gets data from the backend. This is done with albumxml.jsp.

This is the exciting part. albumxml.jsp is really simple, and it's just like a normal jsp. Let's take a look at how it works. First, there's this:

<%
    response.setContentType("text/xml");
%>

Which simple tells the requestor that the response will be XML and not HTML. Next, we retrieve the username from the session and send it as an attribute of the dataset:

<album user="<% out.print( session.getValue( "uname" ) ); %>">

Finally, we loop through the data in the album -- just as we did when we were rending for HTML -- but now emit XML, which is remarkably simpler:

    <%
        ArrayList album = ( ArrayList ) session.getValue( "album" );
        for ( int i= 0 ;i < album.size(); i++ ){
            String s = "<picture ";

            /*
            ...
            for ( properties in albumelements )
                s += properties +"='" + albumelements[ properties ] +"' "
                //like this prop='val'

            */

            s += "/>";
            //so s="<picture prop1='val1' ... propn='valn' />
            out.println( s );
        }
    %>

One last important note: Laszlo apps play in a Flash control hosted by the browser. When the Flash player makes HTTP requests, it uses the browser's mechansim, meaning that it sends all of the browser's cookies. This is what allows us to write code that relies on session data even for requests that are made from the Laszlo app. The result is an XML document that contains only the information we need. Its output looks like this.

So, to recap, we've answered the first question in the above list, which was about how we embed a Laszlo app into an HTML application flow. I should point out that a lot of people who think mainly in terms of server-side applications (which is a lot of people) often try to do something like this:

    <!-- BAD EXAMPLE!!! -->
    <!-- put laszlo app with data into a jsp -->
    <canvas width="500">
    <%
        //psuedo code
        for pix in album
            //code that renders laszlo, as if it were a jsp rendering HTML:
            String s = "<view resource='http:" + pix.get( "url" );
            //etc.
            out.print s + "/>";

    %>
    <!-- rest of program -->
    ...
    </canvas>

Often people get struck trying to figure out how to embed the Laszlo code directly into an HTML page. Sometimes they even get as far as figuring out that the jsp that renders Laszlo code has to be separate, with its output handed to the LPS compiler somehow. If they do get this far, they're disappointed with the results. You want to write a Laszlo app that loads dynamic data in a dataset. The app itself should be static. This is not only because Laszlo's compiler is currently dreadfully slow -- this scheme would never work in a high volume production setting -- it's also because it's usually a bad idea to have non-trivial code that writes non-trivial code. (Of course that's essentially what most modern J2EE/DHTML apps do these days, but that's the example that proves the rule.)

Hopefully, my liberal use of <em> tags has gotten my point across about dynamic data, so that answers question number 2 above, which was: how does the Laszlo app get data from the backend? Hopefully that's pretty clear by now:
you can use all the goodies that you're used to in the J2EE environment, including the request object, the session, and any other code that you have, to render XML for consumption by the front end. Of course, there are more exotic options out there such as SOAP and XML-RPC, but custom XML over HTTP is very easy to do, and most production Laszlo apps use this approach.

The last question on the list was about how to add something like an MVC controller to a Laszlo app. You probably noted that the last example above did not provide the best user experience -- especially since the picture details were presented on a separate HTML page. Here is a final take on this app, with all of the functionality moved into Laszlo.



Again, not the most impressive application, but for once we're not really focussed on the presentation layer. Instead, let's take a look at how the app changed to accomodate its move completely into the world of Laszlo.

First of all, we changed login.jsp from a form renderer to a responder which takes authentication info and sets up the session with user information and user data. Now it just looks like this:

<%
    response.setContentType("text/xml");
    session.putValue( "uname", (String) request.getParameter( "uname" ));
    session.putValue( "album", album );
%>
<ok/>

This doesn't really do much except store the username and album data in the session, but of course you could imagine that it actually authenticates and tells the Laszlo app whether there was success or failure. For now, it unconditionally sends the success token, and that in turn triggers the request for the album data. You could imagine that a more complex application would do the login step and then make separate backend data requests during its run depending on user actions.

The rest of the backend is the same, with albumxml.jsp still responding with the XML data stored in the backend Java object that's kept in the session. The part of interest here is that we have added a controller to the Laszlo app to help it manage its various states. The controller looks like this:

    <node name="controller">
        <attribute name="state" value="0"/>
        ...

The main thing of note here is that the controller exposes a property called "state", which represents the mode of the app. The various pieces of the UI have constraints on their visible property that look like this:

    <!-- login -->
    <view align="center" valign="middle" visible="${controller.state == 0 }">
    ...
    <!-- album -->
    <view datapath="dsAlbum:/album" width="100%" y="20"
          visible="${controller.state == 1 }">
    ...
    <!-- details -->
    <view visible="${controller.state == 2 }">
    ...

The controller also has several methods that various UI elements talk to in order to change the application state. It stores a reference to the data for the selected picture which the user clicks a thumbnail. Here's the whole thing:

    <node name="controller">
        <attribute name="state" value="0"/>
        <attribute name="picdata" value="null"/>
        <method name="didAuth" args="response">
            dsAlbum.doRequest();
        </method>
        <method name="gotAlbum">
            this.setAttribute( "state" , 1 );
        </method>
        <method name="showDetailFor" args="d">
            this.setAttribute( 'picdata' , d);
            this.setAttribute( "state" , 2 );
        </method>
        <method name="goBackToAlbum">
            this.setAttribute( "state" , 1 );
        </method>
    </node>

You can see the whole program here.

This is a fairly primitive example, but hopefully it begins the to answer the question of how control can be centralized in a Laszlo app. It's worth noting that in general, if you have a Laszlo app with a lot of screens, you probably haven't spent enough time thinking about how you can adapt your user-interface design from HTML to the kind of universal canvas/shared space approach that you can take in a Laszlo application. Still, most apps have some modal sense of state, and that's best handled with a controller like the one in this small application.

|
* * *