What’s DHTML Compilation Good For?

OpenLaszlo allows you to compile applications to SWF8, SWF9 or DHTML. Recently I’ve heard people ask, “why would you want to compile an application to DHTML?”

When you think about it, that’s not a silly question at all. Regardless of whether you compile an OpenLaszlo application to SWF or DHTML, you’ll have similar benefits and simliar constraints. On the positive side, coding complex and interactive user interfaces is straightforward. You’re using a language that was specificially designed for rich Internet applications (RIAs).

However, many of the problems people associate with SWF – inability to bookmark (what exactly?), lack of search engine exposure, back button integration (you want to try explaining what’s supposed to happen when you click back in a RIA), difficulty of integration with traditional HTML applications – affect DHTML-compiled OpenLaszlo applications too. Thankfully these days, most developers understand that a platform like OpenLaszlo isn’t supposed to replace HTML applications, but instead is intended for a specific type of application that’s difficult, if not impossible, to do with HTML. Think Gliffy.

So if DHTML doesn’t solve some of most long-standing “problems” of Flash, what does it offer? Certainly not media support; in fact SWF-compiled OpenLaszlo applications have more consistent support for PNG, as well as FLV, SWF, MP3.

Surely DHTML-compiled OpenLaszlo applications must offer some other benefits then? Well, they work in Webkit, which means they’ll run on the iPhone (and of course many upcoming mobile devices that use DHTML). That’s definitely an advantage, but the applications are still limited by the performance of the small CPU.

So now we get to where DHTML compilation really shines… Remember how I said that OpenLaszlo was good for some applications, and HTML was good for others? DHTML-compilation in OpenLaszlo allows you to run one or more HTML applications inside your OpenLaszlo application!

How can it do this? By using the <html> tag, you can embed another web property (e.g. a web site, HTML-based application) inside your OpenLaszlo application. Here’s an example that shows two HTML web sites embedded in the same OpenLaszlo application:

Screenshot of two HTML applications in one OpenLaszlo application
Screenshot of two HTML applications in one OpenLaszlo application

Unfortunately, I can’t simply embed this example into my blog, because it relies on the OpenLaszlo wrapper code. You have to click the screenshot above and then play with the windows.

You may be aware that SWF-compilation also supports the <html> tag. However, the problem with using the <html> tag with a SWF-compiled OpenLaszlo application is that the IFRAME which displays the HTML web site will always float above the SWF. That means you can’t really have a windowed application, unless you’re satisfied with only having one window in your entire application. And you can’t have two <html> tags, if they’re likely to ever overlap, since there’s no way to control which is on top at any one point.

As you can see, that’s not a problem for DHTML.

Download the source for this example.

Webtuner Application Added to Webtop

Vortex is an OpenLaszlo application built by Real Time Matrix. It’s essentially a customizable media browser – Real Time Matrix built a highly-configurable skeleton of the application, and brands it for various content providers. End-users can then embed the application into their own web pages, or into personal start pages (e.g. igoogle). The end-user gets fed various forms of content – streaming audio, video, images, news feeds, etc. – depending on the configuration of that particular Vortex. In exchange, they see discreet ads at the bottom of the Vortex application. You can see a variety of skinned Vortices at www.vortexme.com.

What’s new is that the Vortex has now been integrated into Laszlo Webtop. You can check it out at www.gowebtop.com. You do need an account for gowebtop.com, but it’s fairly quick and free.

Vortex Application in Laszlo Webtop
Vortex Application in Laszlo Webtop

What’s interesting to OpenLaszlo developers is:

  • The Vortex application is completely dynamically-skinned. The components in the Vortex can be enabled, disabled and re-ordered without recompiling the SWF. All resources in the application get loaded over HTTP when the application initializes. This performs surprisingly well.
  • You can plug an OpenLaszlo application into Webtop relatively easily. Webtop provides the notion of applications, as well as windowing/docking/undocking behavior. And, Webtop includes some applicaitons that people use the most (Mail, Contacts, Calendar).

Although Webtop does offer a service that proxies and optimizes data transactions between the client and data stores, you don’t have to use it; you can just compile an OpenLaszlo application that uses datasets in the normal way into Webtop, and run it alongside the existing applications.

Google Maps in OpenLaszlo

Recently, I wrote about Rmenuz, a site that embedded Google’s Flash maps in OpenLaszlo. The Google Flash Maps are written in ActionScript 3 (AS3), which requires Flash Player 9 or above. That means you wouldn’t be able to use them in OpenLaszlo 4.1 or below. I figured that with OpenLaszlo 4.2b3, which supports compiling to SWF9, it should be possible to embed an AS3 component. With some much-appreciated help from Henry, I got the following application working:

I wanted an example that:

  • Embedded Google maps in a clean, developer-friendly way.
  • Was full-featured (allowed a user to search for an address, and add a marker there).
  • Passed information (such as where to add a marker) from the OpenLaszlo application to the map component.
  • Passed information (such as the address of the marker that a user clicked) from the map to an object in the OpenLaszlo namespace.

Note that the Google Maps geocoding service is Flash-specific. Even though it passes XML back to the client, you need to use Google's Flash-based APIs to call it. I didn't want to do this; I wanted my example to perform the search in OpenLaszlo, and pass instructions to the map component. So I used Yahoo Maps' excellent geocoding service.

Here are the instructions on how to do this:

  1. Download OpenLaszlo 4.2b3 or later, and install.
  2. Get a Google Maps API key (free).
  3. Download the Google Maps API for Flash SDK, from Google's web site. I used version 1.7.
  4. Find the WEB-INF directory in your OpenLaszlo install, and create a directory called flexlib inside that. i.e. WEB-INF/flexlib/
  5. Unzip the Google Maps SDK, and locate the two SWC files in the lib directory. Take the Flash one (mine was called map_1_7a.swc) and copy it into WEB-INF/flexlib/.
  6. In your OpenLaszlo my-apps folder, create your lzx file (e.g. main.lzx), and write your application.
  7. When you compile the app via the browser, make sure that you remember to compile to SWF9. When you request the LZX file, append ?lzr=swf9 to the request. (e.g. main.lzx?lzr=swf9).

Here's my complete source:

<canvas proxied="false" width="468" height="530">

    <!-- antunkarlovac.com key -->
    <attribute name="gmapsKey"
               value="ABQIAAAAN0JyO4tW04-1OKNW7bg9gxSPySWqAfkZkuZG2U8jr6yyIuV3XBSrEn410_O9d9QPJh3dbWV85Qad8w" type="string" />

    <alert name="errorDialog" width="400" />

    <method name="handleMarkerClick" args="address">
        addressWin.writeAddress(address);
    </method>

    <script when="immediate"><!&#91;CDATA&#91;
        class FlashMapOL {
            #passthrough (toplevel: true) {
                import com.google.maps.*;
                import com.google.maps.controls.*;
                import com.google.maps.overlays.*;


                import flash.geom.*;
            }#

            var map:Map;

            function createMap() {
                map = new Map();
                map.addEventListener(MapEvent.MAP_READY, onMapReady);
                map.key = canvas.gmapsKey;
                map.setSize(new Point(450, 300));
                return map;
            }

            function onMapReady(event:MapEvent):void {
                map.addControl(new ZoomControl());
                map.addControl(new PositionControl());
                map.addControl(new MapTypeControl());
            }

            function addMarker(lat:Number, lon:Number, address:String):void {
                map.clearOverlays();
                var latLon:LatLng = new LatLng(lat, lon);
                var marker:Marker = new Marker(latLon);
                marker.addEventListener(MapMouseEvent.CLICK, function(e:MapMouseEvent):void {
                    canvas.handleMarkerClick(address);
                    map.openInfoWindow(latLon, new InfoWindowOptions({titleHTML: "<b>Search Result</b>", contentHTML: address}));
                });
                map.addOverlay(marker);
            }

            function centerAndZoom(lat:Number, lon:Number):void {
                var latLon:LatLng = new LatLng(lat, lon);
                this.map.setCenter(latLon, 14, MapType.NORMAL_MAP_TYPE);
            }

        }

        lz.mapFactory = new FlashMapOL();
        
        lz.map = lz.mapFactory.createMap();
    &#93;&#93;>
    </script>

    <dataset name="geoCode_ds"
             src="http://local.yahooapis.com/MapsService/V1/geocode?" type="http" request="false"/>

    <datapointer name="result_dp" 
                 xpath="geoCode_ds:/ResultSet/Result&#91;1&#93;" />

    <handler name="ondata" reference="geoCode_ds"><!&#91;CDATA&#91;
        var lat;
        var lon;
        var root_dp = geoCode_ds.getPointer();
        root_dp.selectChild();
        if (root_dp.getNodeName() != "Error") {
            lat = result_dp.xpathQuery("Latitude/text()") * 1;
            lon = result_dp.xpathQuery("Longitude/text()") * 1;
            var warn = result_dp.getNodeAttribute("warning");
            if (warn != undefined) {
                // Pass along any warnings (e.g. couldn't find address,
                // centering on city.
                errorDialog.setAttribute("text", warn);
                errorDialog.open();
            }
            // Get full address as a string
            var address = result_dp.xpathQuery("Address/text()")
                        + ",<br />" + result_dp.xpathQuery("City/text()")
                        + ", " + result_dp.xpathQuery("State/text()")
                        + " " + result_dp.xpathQuery("Zip/text()")
                        + "<br />" + result_dp.xpathQuery("Country/text()");
            lz.mapFactory.centerAndZoom(lat,lon);
            lz.mapFactory.addMarker(lat, lon, address);
        } else {
            // Failed to find address
            var msg = root_dp.xpathQuery("Message/text()");
            errorDialog.setAttribute("text", msg);
            errorDialog.open();
        }
    &#93;&#93;>
    </handler>

    <window name="mapWin" title="Map" width="468" height="369" 
            allowdrag="false">
        <passthrough>
            import flash.display.*;
        </passthrough>
        <handler name="oninit">
            var sprite:Sprite = this.content.sprite;
            sprite.addChildAt(lz.map, sprite.numChildren);
        </handler>

        <view name="buttons" bgcolor="0xeaeaea" width="100%"
              valign="bottom">
            <simplelayout axis="x" spacing="10" />
            <text valign="middle">Address Search:</text>
            <edittext name="addressInput" width="260" 
                      text="1221 Mariposa Street, San Francisco, CA" />
            <button>Search
                <handler name="onclick"><!&#91;CDATA&#91;
                    var qs = "appid=YD-9G7bey8_JXxQP6rxl.fBFGgCdNjoDMACQA--" 
                           + "&amp;amp;location=" + parent.addressInput.text 
                    geoCode_ds.setQueryString(qs);
                    geoCode_ds.doRequest();
                &#93;&#93;>
                </handler>
            </button>
        </view>
        
    </window>

    <window name="addressWin" title="Address" width="468" height="150" 
            y="379" allowdrag="false">
        <method name="writeAddress" args="address">
            this.txt.setAttribute("text", address);
        </method>

        <simplelayout axis="y" spacing="10" />

        <text width="100%" multiline="true" fontstyle="italic">
            Click the search button to search for an address. When you click
            one of the markers, the full address will be displayed in the
            OpenLaszlo text field below:
        </text>

        <!-- Address display area -->
        <text name="txt" width="100%" multiline="true" fontsize="14">
        </text>
    </window>

</canvas>

Note a few important points in the code:

  • There's AS3 code directly inside the script block in the canvas. You need to use the when="immediate" option in the script tag for this.
  • There's an OpenLaszlo compiler instruction that allows you to include libraries from the WEB-INF/flexlib/ directory:
    #passthrough (toplevel: true) {
    ...
    }#
  • There's another OpenLaszlo compiler instruction in the view where you want to instantiate the map (there's a new passthrough tag, and the map must be added procedurally - sprite.addChildAt().

Here's the complete source to my app, as a zip.