Tab Completion in the Debugger

There’s a handy tab completion feature in the OpenLaszlo debugger now. It’s been around for a few months, but I’ve avoided using it mainly because the keyboard focus was a little flaky on my old Windows laptop.

It works just like a typical Unix (say bash) shell:

  1. Recompile your application with the debugger. e.g. browse to the Hello, World! example with ?debug=true.
  2. Click on the Debugger text input field, and begin typing. e.g. can
  3. Hit tab. The Debugger should complete the word “canvas”.
  4. Now add a dot, and hit tab once. e.g. canvas.
  5. If there are too many possible matches, you’ll see output like the following in the Debugger:
    INFO: 456 possibilities: press ‘\t’ again to see them all
  6. Hit tab again, and you should see a long list of possible properties on canvas:
    INFO: Possible completions: $cfn $isstate $lzc$bind_id $lzc$bind_name …
  7. Now add “sub” to the text input field. It should read canvas.sub
  8. Hit tab. You should see two possible matches for canvas.sub:
    Possible completions: subnodes subviews
  9. Add a “v”, so your text input field now says canvas.subv
  10. Hit tab. The debugger should complete that to canvas.subviews, since its the only possible match. Now hit the enter key, and the Debugger will inspect canvas.subviews.

Enjoy!

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.

Rmenuz – OpenLaszlo and Google Maps

I looked at Google Map’s Flash API a while ago, trying to determine a way to integrate it into OpenLaszlo, but never had time to finish it. Today, I discovered Rmenuz, which is an OpenLaszlo application that lets you search for restaurants, and displays them using the Flash-based Google Maps.

Rmenuz Screenshot
Rmenuz Screenshot

I’m not sure what the status is of the site; it may still be under development. There’s a problem with its security certificate and I found a number of glitches with the application. You can try it at:

https://my.rmenuz.com/default.aspx

It can be hard to get to the map, since it’s only displayed if your search returns valid search results. To see the map, follow these steps:

  1. Click on the “Search by: CUISINE” window.
  2. Check “American Restaurants”.
  3. Select “Distance” 2.
  4. Enter the zip: 94107.
  5. Click go.