Behind The Scene: Integrating Google Maps

From Documentation
DocumentationSmall Talks2006OctoberBehind The Scene: Integrating Google Maps
Behind The Scene: Integrating Google Maps

Author
Henri Chen, Principal Engineer, Potix Corporation
Date
October 26, 2006
Version
Applicable to ZK 2.1.3.
Google Maps Version 2


Purpose

As has mentioned in the smalltalks Integrating FCKeditor, this is the second one that talks about how to wrap a pure Javascript component into a ZK component. In this article, the target to be wrapped as a ZK component is the famous Google Maps. If you are not familiar with the Javascript coding of the Google Maps, you can refer examples in this document.


Demo

http://www.zkoss.org/zkdemo/test/gmaps.zul

Smallctrl.gif

The source code, zk-GMaps-2.0-2006-10-20.zip, could be download here. If you are interested in developing ZK components, the source code provides the directory structure and necessary libraries and tools to build such components. If you are interested in how to use this Google Maps component only, you can see this smalltalks, Put Google Maps In Your ZK Application , and related ZKForge JavaDocs.

The four steps

To develop a Javascript component into a ZK component, we need to prepare four types of files.

  • The lang-addon.xml file: Register the new ZK component.
  • The Java files (*.java): Component as Java objects.
  • The template files (*.dsp): Template to generate final HTML tags.
  • The Javascript files (*.js): Glue logic to link the Javascript component to the Java class.


Register the new ZK component: the lang-addon.xml file

A new ZK component must be configured and registered in lang-addon.xml file such that ZK engine knows how to access and use it.

<language-addon>
	<!-- The name of this addon. It must be unique -->
	<addon-name>gmapsz</addon-name>
	<!-- Specifies what other addon this depends
	<depends></depends>
	-->
	...
	<component>
		<component-name>gmaps</component-name>
		<component-class>org.zkforge.gmaps.Gmaps</component-class>
		<mold>
			<mold-name>default</mold-name>
			<mold-uri>~./gmaps/gmaps.dsp</mold-uri>
		</mold>
	</component>
	...
</language-addon>

In the file we define the component-name to be gmaps. That is the zul tag name used in designing zul pages. The component-class is the ZK component class used by application developers. Each component can have multiple molds. Here we define only the default mold to gmaps.dsp, the template file to generate final HTML tags that will be read by the browser.


Component as a Java object: the Gmaps.java

Each ZK component is in fact a Twins. It is composed of the browser side Javascript and HTML codes and the server side Java classes. ZK as a server centric Ajax Framework, the Java APIs is like the face of the ZK component. A good set of APIs would help application developers to easily use the component. Of course, the definition of the APIs are naturally limited by what the original Javascript component can provide. Here we pick one as our example: The maps zoom level.

/** set zoom level.
 */
public void setZoom(int zoom) {
    if (zoom != _zoom) {
        _zoom = zoom;
        smartUpdate("z:zoom", ""+_zoom);
    }
}

/** get zoom level.
 */
public int getZoom() {
    return _zoom;
}


Send command from server to browser: the smartUpdate() method

Something interesting in the setZoom method is the smartUpdate. It is used by the ZK server to send command back to the browser in an Ajax response. The first argument of the smartUpdate is the attribute name or the command name. The second argument is a String that is the new value of the attribute or the associated data of the command to be sent to the browser. E.g. The smartUpdate("z:zoom", "10") will send z:zoom command along with the zoom level 10 to the browser and tells the Google Maps at the browser side to change zoom level to ten (10). Notice that for each http request, command with same name will be sent only once. Thus within one event handling (one XMLHttpRequest), if the Java API called the smartUpdate with same command name more than once, only the last one is sent.

mymap.setZoom(12);
mymap.setZoom(10);


E.g. The application developer calls setZoom(12) then setZoom(10) in event handling codes. Though the smartUpdate("z:zoom") is called twice, the Google Maps will not change zoom to 12 then 10 since the "z:zoom" command is sent only once with the last value. (It will zoom to 10 directly.) We have known the pitcher is the smartUpdate() method. Then who is the catcher in the browser side? It is of course the ZK Javascript engine. Then to where the ZK Javascript engine would route the command from the server? It is the key questions here. Before dig in the details of that, let us talk about the HTML tags generation mechanism first.


=Generate the HTML tags: the gmaps.dsp=;;A

dspsp file used in ZK is a template file similar to a typicjspjsp file. You can use tdspdsp tag libraries provided to generate the final HTML tags that will be read by the browser. As we all know, the Google Maps is encapsulate inside an HTML

tag.
<c:set var="self" value="${requestScope.arg.self}"/>
<div id="${self.uuid}"${self.outerAttrs} z:type="gmapsz.gmaps.Gmaps">
</div>

The self is tGmapsaps Java object. The key part in the above code is this attribute z:type. The value of the attribute gmapsz.gmaps.Gmaps is a name pattern mapping that tells the ZK Javascript engine where the associated Javascript gllogicsics are. The last Gmapsaps in gmapsz.gmaps.Gmaps is used to form the Javascript object name zkGmapsaps while the leading gmapszpgmapsaps is the path to the Javascript glue logic file, meaning gmapszpgmapsajs.js. ZK Javascript engine use this information to load-on-demand the required Javascript file and calling appropriate Javascript object methods. The other ${self.xxx} is simply getter methods defined in the Java class that is used in generating the final HTML tags.


The catcher of tsmartUpdateate command

So to where the ZK Javascript engine would route the command from the server? The answer is at the attribute z:type as mentioned above. The engine would try to find the Javascript object zkGmapsaps and check whether the function setAttrttr() exists. If it is there, it will call it first. If it is not there, it will set the named attribute of the HTML tag with the new value directly.

zkGmaps
setAttrAttr = function (mp, name, value) {
    switch (name) {
    case "z:zoom":
        mp._gmaps.setZoom(parseInt(value));
        return true; //command done, do not set attribute.
        
        ...
    }
    return false;
}
ThesetAttrAttr function accepts three arguments. The first is the HTML element that holds the component id (the
HTML tag). The second is the attribute name(or command name). The last is the attribute value. In command z:zoom, the function calls the Google Maps setZoomZoom() method to change the zoom level of the Google Maps and return true to tells the ZK Javascript engine that the command is done. Notice that if you return falsesetAttrAttr, the ZK Javascript engine will continue to set it as an attribute of the HTML tag. Since z:zoom is used as a command rather than an attribute, we return true here. So the whole procedure sequence is like this:
ZK componGmapsmsetZoomZoom(10) 
 smartUpdatedate("z:zoom", "10") 
  -> (command response to browser)
  -> ZK Javascript Engine 
 zkGmapsmsetAttrAttr(mp, "z:zoom", "10") 
  -> Google MsetZoomZoom(10)


The Javascript component life cycinitinit and cleanup

As specified in Google Maps API document, the Google Maps is created with an HTML
as its container. What is the proper moment to create it then? Fortunately, the ZK Javascript engine provides two simple life cycle callback methoinitinit and cleanup.zkGmapsminitinit is defined, it will be called after the HTML tags are rendered. IzkGmapsmaps.cleanup is defined, it will be called just before the HTML element is to be removed.
zkGmaps
init.init = function (mp) {
   GBrowserIsCompatibletible()) {
        //create the Google Maps and store it in the HTML node object
       gmapsgmaps =GMap GMap2(mp);
       gmapsgmagmapsgmaps;
        ...

        //register the Google Maps event to Javascript handling functions
   GEventEaddListenertgmapsgmamoveendveeGmaps_onmovenmove);
   GEventEaddListenertgmapsgmazoomendomeGmaps_onzoomnzoom);
        
        ...     
    zkGmaps

Gmaps.cleanup = function (mp) {
    if (GMapow.GMap2 != null) {
        ...
    }
};
Gmaps.init(mp) is a proper place to do the Google Maps object creation. When the init function is called by the ZK Javascript engine, the HTML
tag is already created and can be used as the HTML container for the Google Maps (new GMap2(mp)). It is also a good place to associate the Google Maps events to our Javascript glue logic event listener codes (addListener(...)).


Send command from browser to server: the zkau.send() method

So how a Google Maps event is converted into a ZK event? Let me explain it with an example. The zoomend event is fired whenever the zoom level of the Google Maps is changed. No matter it is changed by the end user clicking the zoom in/out control button or by programatically changed. First, we have to prepare a Javascript event handler and register zoomend event.

The event registration code is done in zkGmaps.init method.

zkGmaps.init = function (mp) {
    ...
    GEvent.addListener(gmaps, "zoomend", Gmaps_onzoom);
    ...
}

And the event handling code is this.

/** onMapZoom event hander */
function Gmaps_onzoom() {
    var gmaps = this;
    var mp = gmaps.getContainer();
    var uuid = mp.id;
    var comp = mp;
    var zoom = gmaps.getZoom();
    zkau.send({uuid: uuid, cmd: "onMapZoom", data: [zoom]},
    	 zkau.asapTimeout(comp, "onMapZoom"));
}


The Google Maps zoomend event fired and the Gmaps_onzoom event handler is called. The key function in the above Javascript code is the zkau.send() function. This is how a Javascript command is sent to the server via an Ajax request. The first argument is a tuple object with uuid, command name, and data set. The second argument is a number regarding whether how soon this command should be sent to the server.

The catcher (ZK update engine) of the command at the server side will first wrap the command into an AuRequest object then based on its command name (in this case, the onMapZoom), delegate to the specific command processor. In this case, to process the passed back onMapZoom command, we need a MapZoomCommand command processor to update the zoom level of the Gmaps Java component.

/*package*/ class MapZoomCommand extends Command {
    protected void process(AuRequest request) {
        final Component comp = request.getComponent();
        ...

        final String[] data = request.getData();
    
        //update the zoom level of the Gmaps component
        final Gmaps gmaps = (Gmaps) comp;
        final int zoom = Integer.parseInt(data[0]);
        gmaps.setZoomByClient(zoom);
    
        //post onMapZoom event to ZK event queue
        Events.postEvent(new MapZoomEvent(getId(), comp, zoom));
    }
}


This class extends Command and overrides process() method to handle the passed in AuRequest. As you can see, the target component has been fetched back by its uuid when wrapping the command into an AuRequest object. The data set is converted into a String array as the form it was prepared in zkau.send() Javascript method. After setting up the new zoom level of the Gmaps object, it posts a MapZoomEvent to the ZK event queue so application developers can register the event and handle it. So the whole command sequence from the Javascript event to the ZK event is like following.

Google Maps zoomend event fired
  -> Javascript Gmaps_onzoom() event handler 
  -> zkau.send({uuid: uuid, cmd: "onMapZoom", data: [zoom]}, 
       zkau.asapTimeout(comp, "onMapZoom"));
  -> (command request to server)
  -> ZK Update Engine 
  -> MapZoomCommand.process(request)
  -> Events.postEvent(new MapZoomEvent("onMapZoom", comp, zoom));


Notice that to make the ZK update engine aware of a new command processor, you have to register it into the ZK's command processing map. The onXxx command name is where all things are associated together.

public class Gmaps extends HtmlBasedComponent {
    ...
    
    //register the Gmaps related event
    static {    
        new MapZoomCommand("onMapZoom", Command.IGNORE_OLD_EQUIV);
    }
}


Summary

I hope you have catch the way how a Javascript component can be wrapped into a ZK component. The Google Maps API is a versitile API set. There are still lots can be controlled by the Java code. There are still some useful Google Maps events should be sent back to the server. There are still GInfoWindow, GMarker, and GPolyline components to be implemented. The ZK team hope this gmaps implementation is just a beginning. We welcome contributors to enhance this component or implement other components. It is quite simple. In fact, it takes only about two days to implement this gmaps component, including the time to understand the Google Maps API and minor debuggings. So pick a component and just ZK it!




Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.