OpenLaszlo 4.2 adds SWF9 Support

In case you missed it among the holiday cheer, OpenLaszlo 4.2 was officially released last week. Personally, I’ve been looking forward to this release for a while, since it adds support for SWF9. SWF9 is bytecode that’s optimized for the Flash Player 9 runtime. Flash developers may know it as ActionScript 3 (AS3). The exciting thing for OpenLaszlo developers is that SWF9 is vastly faster than SWF8. And since Flash Player 9 is widely-deployed, you can start using it as the default runtime for new applications.

SWF9 support may not sound like a huge deal to many people – it’s just a matter of keeping up-to date with the newer version of Flash, isn’t it? Well, not exactly. The difference between SWF8 bytecode and SWF9 bytecode was significant. So big, in fact, that Adobe included two completely separate runtimes in Flash Player 9: one for running the new SWF9 bytecode, and a separate one for running SWF8-and-below bytecode. In other words, the SWF9 capability is a pretty big deal.

Just how much of a performance gain does the new runtime offer? A few months ago, while writing a post about optimizing data for Lzx (OpenLaszlo Performance Tip: Attributes, not Nodes), I found that the then-beta SWF9 runtime in OpenLaszlo was as much as 3.7 times faster than the SWF8 one, at loading and parsing XML data. At the time, much of the functionality was still missing from the 4.2 branch, so data loading was the only behavior I could test.

With 4.2 available, I wrote a simple test that measured how long it took to instantiate 100 windows:

<canvas>

    <text id="output" fontsize="36" />

    <class name="mywindow" extends="window" width="300" height="200">
    </class>


    <handler name="oninit"><!&#91;CDATA&#91;
        var dStart = new Date();
        var j:Number = 0;
        var xpos:Number;
        var ypos:Number;

        for (j=0; j<100; j++) {
            xpos = Math.max(150, Math.random() * canvas.width);
            ypos = Math.max(50, Math.random() * canvas.height);
            new lz.mywindow(canvas, {x: xpos, y: ypos});
        }
        var dEnd = new Date();
        var timeTaken = dEnd.getTime() - dStart.getTime();
        output.setAttribute("text", timeTaken);
        &#93;&#93;>
    </handler>
    
</canvas>

The results for creating 100 windows in OpenLaszlo 4.2.0 are:

  • SWF8: 6,042ms
  • SWF9: 1,073ms

That’s 5.6 times faster – even more dramatic than the results from the data loading test!

Surely all OpenLaszlo developers will now rush to recompile their applications to SWF9; all it takes is appending ?lzt=swf9 to the request, doesn’t it? Unfortunately, it’s not that simple. Moving from OpenLaszlo 4.0 to 4.1 involved a lot of language changes, specifically in anticipation of SWF9. I wrote about this a while back. Then, moving from OpenLaszlo 4.1 to 4.2, there were a few more changes, mainly because icompatibilities were found after 4.1 had been released, and in-depth development had started on supporting the new runtime. As an example, the <script> tag shouldn’t be used in SWF9. It actually partly works, but causes confusing errors. Better replace it with <handler name=”oninit”>.

Happily there are some resources and tools for developers needing to migrate their applications. There’s an OpenLaszlo wiki page on Runtime Differences, which should be your first stop. It describes how to use the Perl migration script that (in theory) updates your code for you. Remember that it may not catch everything. It’s hard to anticipate every manner in which developers will use a platform, so please be patient and provide any feedback on issues you hit on the forums or mailing lists.

The id Attribute in 4.1

One subtle thing that has changed in OpenLaszlo 4.1 is that you can no longer specify the id attribute when instantiating objects from script.

When instantiating a class from script, you can specify a dictionary of initial properties as the second argument to the constructor:

var v = new lz.view(canvas, {width: 300, height: 300, bgcolor: 0xff0000}); // 4.1

Prior to OpenLaszlo 4.1, the constructor shown above would have been LzView() instead of lz.view(). Also, prior to 4.1, you could have specified the id attribute in addition to the width, height, and bgcolor attributes, and then referenced your newly-created view by that id later in the application. e.g.

var v = new LzView(canvas, {id: "myNewView", width: 300, height: 300, bgcolor: 0xff0000}); // 4.0.12

Since the SWF9 does not allow global variables to be created on-the-fly, OpenLaszlo 4.1 will no longer allow you to do this either. For better or for worse.

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.