This article describes the process of adding Google Maps to FlexJS. Google Maps is a web-based technology and can be readily used in AIR or JavaScript FlexJS applications. You can think of Google Maps as an example of how to extend FlexJS with web services.
In case you are not familiar with Google Maps and its API, Google has provided programmatic interfaces to its map service. You declare a region on your web page, usually as a <div>, where you want to display the map and using the Maps API, make calls to display a map based on latitude and longitude coordinates at various zoom levels. You can display a standard map, a satellite image, or a hybrid. In addition to displaying maps, you can also overlay the map with symbols and use other map-related services to find the location of places and addresses.
There are two ways to use the Google Maps API. The first is via a RESTful web service which returns JSON as its response. The second is through a JavaScript library that uses the REST interface under the covers.
Incorporating Google Maps into FlexJS was a matter of deciding how to use the Google Map API and services. The JavaScript (v3) API was chosen over the REST API because it easily integrates with the JavaScript side of FlexJS SDK. There are two options on the ActionScript side: display the Google map in a floating HTML IFrame or use the HTMLLoader of AIR. While many Flex web apps, which use the browser Flash Player plug-in, use IFRAMEs to display web content, it requires more complex HTML than the, much simpler, AIR HTMLLoader component.
Using this approach, the result is a FlexJS app that can be run as an AIR application or cross-compiled into HTML/JavaScript and run in the browser.
Google API Classes
The Google Map API has many parts, so only a core portion was added to FlexJS, but more can be added following the same pattern. The Google MAP API classes used so far are: Map, Place, and Marker, with supporting classes, LatLng and Geometry. When a FlexJS app is cross-compiled into JavaScript, the FalconJX compiler looks for corresponding JavaScript classes in the same packages. For example, if your FlexJS application code uses org.apache.flex.maps.google.Map, FalconJX looks for that same class in the JavaScript library, so it was necessary to wrap the Google API classes.
Google Map Class | FlexJS Proxy Class |
---|---|
google.maps.Map | org.apache.flex.maps.google.Map |
google.maps.LatLng | org.apache.flex.maps.google.LatLng |
google.maps.Geometry | org.apache.flex.maps.google.Geometry |
google.maps.Place | org.apache.flex.maps.google.Place |
google.maps.Marker | org.apache.flex.maps.google.Marker |
In addition to these proxy classes, there are a couple of additional FlexJS classes.
org.apache.flex.maps.google.beads.MapView is a traditional IBeadView-compliant class that is responsible for creating the Map visual interface (more on this below). This class loads the Google Maps API and creates the Map display.
org.apache.flex.maps.google.models.MapModel is the data model for the FlexJS Map component. The model contains the map's current center, its zoom level, and search results, among other things. Changing the model updates the map.
Loading the Google Maps API
In order to use the Google Maps API, a developer (or company) needs to apply for a developer token. Using the token, you incorporate the Google Maps API via an HTML Script element. FlexJS does this in a lazy fashion by building the script element at runtime. The JavaScript version (in MapView) uses the strand-setter override function to build the Script element and trigger the download.
var token = this.strand_.token; var src = 'https://maps.googleapis.com/maps/api/js?v=3.exp'; if (token) src += '&key=' + token; src += '&libraries=places&sensor=false&callback=mapInit'; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = src;
The ActionScript side works a little differently. The MapView class has a JavaScript "template" of functions written into a String variable. When the strand-setter override is called, the template is copied and the API token is written into it and then given to the HTMLLoader component to run, causing the Google Maps API to be downloaded into the HTMLLoader instance.
ActionScript Interface and HTMLLoader
The ActionScript Map class uses the AIR HTMLLoader to present and interact with the Google Map. It works like this: JavaScript code is put into HTMLLoader and is accessible via htmlLoader.window, such as htmlLoader.window.map or htmlLoader.window.nearbysearch("coffee") where 'search' is a JavaScript function in the template that was loaded into HTMLLoader from the strand-setter function.
The MapView class acts as a proxy between the HTMLLoader's JavaScript world and ActionScript. MapView listens for HTML events and reacts to them, often dispatching ActionScript events. One example of this is the Map class's "ready" event. When the Javascript code loads the map, the Google API dispatches an event that is intercepted by the ActionScript MapView instance and converted into a "ready" event. That event is used by an application to finish setting itself up, possibly subscribing to other events the Map class will dispatch.
The MapView class also acts as a proxy for the data being exchanged with the Google Map API. For instance, when a search is run using the Google Map Places API, the results are returned in the JavaScript code (that was loaded in the template).
JavaScript (part of the template string in MapView.as):
' function nearbysearch(placename) {' +
' if (markers == null) markers = [];' +
' service = new google.maps.places.PlacesService(map);'+
' service.nearbySearch(options, function(results, status) {' +
' places = results;' +
' if (status == google.maps.places.PlacesServiceStatus.OK) {' +
' for(var i=0; i < results.length; i++) {' +
' var place = results[i];' +
' var marker = createMarker(place.geometry.location);' +
' marker.title = place.name;' +
' markers.push(marker);' +
' }' +
' var event = document.createEvent("Event");' +
' event.results = places;'+
' event.initEvent("searchResults", true, true);' +
' window.dispatchEvent(event);' +
' }' +
' });'+
' };' +
When the Google PlacesService's nearbySearch is run, the result handler function creates markers and then sends an event signaling that the search is complete.
Earlier in the MapView class, an event listener for "searchResults" was added to transform the JavaScript data into ActionScript classes and store them into the model (which triggers a property change event).
_loader.window.addEventListener("searchResults",onSearchResults); ... private function onSearchResults(event:*):void { var results:Array = []; for(var i:int=0; i < event.results.length; i++) { var result:Place = new Place(); result.geometry.location.lat = event.results[i].geometry.location.lat(); ... } var model:MapModel = _strand.getBeadByType(IBeadModel) as MapModel; model.searchResults = results; } }
In the for-loop above, JavaScript data (as event.results[i].geometry.location.lat()) is extracted and put into the ActionScript Place class instance.
Basically, for the ActionScript side to work, a template with JavaScript functions is embedded in the MapView bead and loaded into an instance of HTMLLoader (modified with the developer's Google Maps API token). Once this is loaded, communication between JavaScript in HTMLLoader and ActionScript is handled via _loader.window; you can invoke functions at the window level and exchange data.
The ActionScript Map class is acting as a proxy to the Google Maps API so the rest of the application does not need to know or care about the JavaScript part that lies underneath.
When the app is cross-compiled into JavaScript, a more "native" approach to using Google Maps is employed by the FlexJS Map component and MapView bead. The Google Maps API is still dynamically loaded, but the data in Google Maps can be used directly.
Compiler Warnings
As you look through the JavaScript implementation of org.apache.flex.maps.google.Map, you'll see that direct references to the Google Map API have been converted to property index addresses. For example,
this.map = new google.maps.Map(this.element, mapOptions)
is actually written as:
this.map = new window['google']['maps']['Map'](this.element, mapOptions);
This syntax prevents the FalconJX compiler and Google Closure compiler from issuing warning since it does not know about the google.maps package at compile time.