Behind The Scene: Integrating Google Maps
From Documentation
| Categories |
|---|
| Small Talks |
|---|
|
Latest Small Talks : |
| Sort By : |
- 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
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 Applicaiton, and related ZKForge JavaDocs.
The four steps
To develope 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 dsp file used in ZK is a template file similar to a typical jsp file. You can use the dsp 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<c:set var="self" value="${requestScope.arg.self}"/> <div id="${self.uuid}"${self.outerAttrs} z:type="gmapsz.gmaps.Gmaps"> </div>
The self is the Gmaps 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 glue logics are. The last Gmaps in gmapsz.gmaps.Gmaps is used to form the Javascript object name zkGmaps while the leading gmapsz.gmaps is the path to the Javascript glue logic file, meaning gmapsz/gmaps.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 the smartUpdate 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 zkGmaps and check whether the function setAttr() 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.setAttr = function (mp, name, value) { switch (name) { case "z:zoom": mp._gmaps.setZoom(parseInt(value)); return true; //command done, do not set attribute. ... } return false; }
ZK component Gmaps.setZoom(10) -> smartUpdate("z:zoom", "10") -> (command response to browser) -> ZK Javascript Engine -> zkGmaps.setAttr(mp, "z:zoom", "10") -> Google Maps setZoom(10)
The Javascript component life cycle: init and cleanup
As specified in Google Maps API document, the Google Maps is created with an HTMLzkGmaps.init = function (mp) { if (GBrowserIsCompatible()) { //create the Google Maps and store it in the HTML node object var gmaps = new GMap2(mp); mp._gmaps = gmaps; ... //register the Google Maps event to Javascript handling functions GEvent.addListener(gmaps, "moveend", Gmaps_onmove); GEvent.addListener(gmaps, "zoomend", Gmaps_onzoom); ... } }; zkGmaps.cleanup = function (mp) { if (window.GMap2 != null) { ... } };
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. |
