May 01 2008

Gwt-Ext and Google Maps - II (handle click)

Tag: Ext, Gwt, Java, Javascript, Maps, Programming, WebAbhijeet Maharana @ 5:54 pm

Lat and Lon info being shown in a message box
This post is related to my earlier post on Gwt-Ext and Google Maps. While browsing the Gwt-Ext forum, I came across this thread with a simple-looking question from Martin: How can I get the LatLonPoint from a map when a user clicks on the map ??

I looked at the available methods to see if I could figure this out. When I realized I wasn’t getting anywhere, I tried looking at the source to figure out what was going on. I found that Mapstraction does have a facility to register callback functions for events. Below code snippets are from mapstraction.js.

1
2
3
4
5
6
7
8
9
10
11
Mapstraction.prototype.addEventListener = function(type, func) {
    var listener = new Array();
    listener.push(func);
    listener.push(type);
    this.eventListeners.push(listener);
    switch (this.api) {
    case 'openlayers':
        this.maps[this.api].events.register(type, this, func);
        break;
    }
}

When the callback functions registered for ‘click’ event are invoked, they are supplied with a LatLonPoint instance with the latitude and longitude information of the location which was clicked. See line 4 below.

1
2
3
4
5
6
7
Mapstraction.prototype.clickHandler = function(lat, lon, me) {
    for (var i = 0; i < this.eventListeners.length; i++) {
        if (this.eventListeners[i][1] == 'click') {
            this.eventListeners[i][0](new LatLonPoint(lat, lon));
        }
    }
}

However, this argument is lost because of the way Gwt-Ext API exposes this functionality: MapPanel class registers a function with no parameters. Below code snippet is from MapPanel.java. Note the native method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void addEventListener(final String event, final Function listener) {
	if (!mapRendered) {
		addListener(MAP_RENDERED_EVENT, new Function() {
			public void execute() {
				doAddEventListener(event, listener);
			}
		});
	} else {
		doAddEventListener(event, listener);
	}
}
 
private native void doAddEventListener(String event, Function listener) /*-{
        var map = this.@com.gwtext.client.widgets.map.MapPanel::mapJS;
        map.addEventListener(event, function() {
            listener.@com.gwtext.client.core.Function::execute()();
        });
}-*/;

The solution is to override these two methods. For that we need an interface with an execute() method that can accept arguments. I added an interface ‘OneArgFunction’ that does this. We need a proper fix for this so that we can handle more arguments. For now, a one-argument method will suffice.

1
2
3
4
5
package com.maharana.gwtextmaps.client;
 
public interface OneArgFunction {
	public void execute(com.google.gwt.core.client.JavaScriptObject arg);
}

In the overridden methods below, I register a function which accepts the LatLonPoint instance as parameter and hands it over to the execute() method for further processing. Then I invoke the overridden addEventListener() to register an event handler that places a new marker and centers the map on the clicked location. GoogleMap inherits from MapPanel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mapPanel = new GoogleMap() {
	public void addEventListener(final String event, final OneArgFunction listener) {
		if (!this.isRendered()) {
			addListener(MAP_RENDERED_EVENT, new Function() {
				public void execute() {
					doAddEventListener(event, listener);
				}
			});
		} else {
			doAddEventListener(event, listener);
		}
	}
 
	private native void doAddEventListener(String event, OneArgFunction listener) /*-{
	      	var map = this.@com.gwtext.client.widgets.map.MapPanel::mapJS;
	      	map.addEventListener(event, function(llp) {
	            	listener.@com.maharana.gwtextmaps.client.OneArgFunction::execute(Lcom/google/gwt/core/client/JavaScriptObject;)(llp);
	      	});
	}-*/;
 
	// constructor - attach event listener
	{
		addEventListener("click", new OneArgFunction(){
			public void execute(JavaScriptObject arg) {
				LatLonPoint llp = new LatLonPoint(arg);
				mapPanel.setCenterAndZoom(llp, mapPanel.getZoom());
				mapPanel.addMarker(new Marker(llp));
				MessageBox.alert("Clicked Location", "Lat: " + llp.getLat() + "<br>Lon: " + llp.getLon());
			}
		});
	}
};

This does the trick. I am not using any Google Maps specific code here so it should work for other providers as well. Do let me know if I have missed something obvious or got something wrong.

I have modified the demo I posted in my earlier blog entry to include this. You can download it from Gwt-Ext.com or Rapidshare.


Apr 07 2008

Gwt-Ext and Google Maps

Tag: Ext, Gwt, Java, Maps, Programming, WebAbhijeet Maharana @ 9:14 pm

Dahisar on a Google Map

Sometime back, I uploaded few pics to Picasa. While creating an album, it asked for an optional “Location” information. I did provide it and when I went to view the album, it displayed the location on a map. I liked it. Few days later, Gwt-Ext 2.0.3 got released with some cool features. One of them is a map API built on top of Mapstraction. Mapstraction lets you use maps from different providers such as Yahoo, Google, Microsoft and lets you switch between them easily. I had not worked with maps before and thought this would be a good opportunity to try it. I wanted to create something like the one I had seen on Picasa.

I have written a small Gwt-Ext application which marks user supplied addresses on a Google Map. Click thumbnail above for a larger image of the output. It took quite some time to figure out how to geocode an address to obtain the latitude and longitude information. But at the end, it works! I have used Google Maps specific code. However, Gwt-Ext provides powerful abstraction, thanks to Mapstraction, and you may want to use that instead.

Code is given below with brief explanation. The entry point looks like this:

1
2
3
4
5
6
public void onModuleLoad() {
	createMapPanel();
	addMapControls();
	new Viewport(mapPanel);
	updateMap("mumbai", JavaScriptObjectHelper.createObject(), this);
}

In createMapPanel(), I create the panel which will hold the map:

1
2
3
4
5
6
7
8
private void createMapPanel()
{
	mapPanel = new GoogleMap();
	mapPanel.setTitle("Google Maps using Gwt-Ext [http://abhijeetmaharana.com]");
	mapPanel.setHeight(400);
	mapPanel.setWidth(400);
	mapPanel.addLargeControls();
}

mapPanel.addLargeControls() adds controls to the map which let you zoom, pan and select image type (map / satellite / hybrid).

Then, I add a textfield and a button in the top toolbar of the panel to accept user input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void addMapControls()
{
	final MainModule thisModule = this;
 
	addressField = new TextField();
	addressField.setValue("mumbai");
	refreshMapButton = new ToolbarButton("Refresh map", new ButtonListenerAdapter() {
		public void onClick(Button button, EventObject e) {
			String address = addressField.getText();
			if (!address.trim().equals(""))
				updateMap(address, JavaScriptObjectHelper.createObject(), thisModule);
		}
	});
 
	Toolbar toolbar = new Toolbar();
	toolbar.addText("Enter an address: ");
	toolbar.addField(addressField);
	toolbar.addSpacer();
	toolbar.addButton(refreshMapButton);
 
	mapPanel.setTopToolbar(toolbar);
}

On line 11, updateMap() is called when user clicks “Refresh map” after supplying an address. This is a native method which uses has Javascript code to obtain the latitude and longitude of the provided address. If it can obtain valid results, it calls renderMap() to display the location. I had to resort to JSNI to geocode the address. There might be a cleaner way which would avoid any native code. I have posted a question in the Gwt-Ext forum. Lets see what comes up.

Below is the code for updateMap() and renderMap():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public native void updateMap(String locationAddress, JavaScriptObject llp, MainModule thisModule) /*-{
	var geo = new $wnd.GClientGeocoder();
 
	geo.getLocations(locationAddress, 
		function(response) 		// callback method to be executed when result arrives from server
		{
			if (!response || response.Status.code != 200) 
			{
   				alert("Unable to geocode that address");
			} 
			else 
	      		{
		    		var place = response.Placemark[0];
		    		llp.lat = place.Point.coordinates[1];
		    		llp.lon = place.Point.coordinates[0];
 
		    		thisModule.@com.maharana.gwtextmaps.client.MainModule::renderMap(Lcom/google/gwt/core/client/JavaScriptObject;)(llp);
	      		}
      		}
      	);
}-*/;
 
 
public void renderMap(JavaScriptObject jsObj)
{
	double lat = Double.parseDouble(JavaScriptObjectHelper.getAttribute(jsObj, "lat"));
	double lon = Double.parseDouble(JavaScriptObjectHelper.getAttribute(jsObj, "lon"));
 
	LatLonPoint latLonPoint = new LatLonPoint(lat, lon);
	mapPanel.setCenterAndZoom(latLonPoint, 12);
	mapPanel.addMarker(new Marker(latLonPoint));
}

You will need to include code below in the host HTML file to use GClientGeocoder and other Google Maps / Mapstraction related Javascript objects:

<script type="text/javascript" src="js/map/mapstraction.js"></script>
 
<!-- Replace **PLACEHOLDER** in this line with your API key -->
<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2.x&amp;key=**PLACEHOLDER**"></script>

You can obtain a Google Maps API key from http://code.google.com/apis/maps/signup.html.
Do let me know what you think. Specially about geocoding addresses without making a JSNI call.

Download Eclipse project from Gwt-Ext.com or Rapidshare.

UPDATE (8 Apr):
Sanjiv has clarified in the forum that geocoding support is not available yet. So we will have to stick with JSNI code for the time being. Also, since the url in host mode is localhost:8888, you can use this key for Google Maps:

<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2.x&amp;key=ABQIAAAARrCK38aboqQKDotehUjrPhTb-vLQlFZmc2N8bgWI8YDPp5FEVBQ-MFjXfKfAvdbsbp3pa0q7fQNDDA">
</script>

Mar 17 2008

Gwt-Ext screencasts for beginners

Tag: Ext, Gwt, Java, Javascript, Links, Linux, Maps, Programming, Ubuntu, WebAbhijeet Maharana @ 8:51 pm

I have started out with Gwt-Ext and thought I will record some screencasts. These are for beginners who may need a hand when they are starting out and are anxious to jump right into code. The screencasts have been recorded on Ubuntu Feisty with Eclipse Europa.

There are 3 screencasts in avi and ogg format.

  1. First screencast shows the installation of Gwt 1.4 and Cypal Studio for Gwt. Cypal Studio is an Eclipse plugin that automates most of the tasks associated with GWT like adding a module and adding a remote service. In the screencast, I create a Gwt project using this plugin.
  2. Second screencast shows how to add the ExtJS Javascript library (2.0) and Gwt-Ext Java library (2.0.1) to the project. In the screencast, I create a simple Ext form with some text fields and a button.
  3. Third screencast (added on March 22) shows how to send form data back to the server. In the screencast, I validate form fields using regular expressions / Ext Vtype, add a remote service and then save form data in an Oracle database using the remote service.

    You can read this post for installing Oracle 10g XE on Ubuntu Feisty.

All screencasts are hosted at Rapidshare. Sanjiv Jivan, the author of Gwt-Ext has offered to host them at gwt-ext.com. I think the files are quite big and I am not well-versed with audio/video formats and parameters. If you know any way to reduce file size while maintaining the quality of screencasts, do let me know.

Download screencasts from Rapidshare.
Download Eclipse projects shown in the screencasts from Gwt-Ext.com or Rapidshare.

Update:
Sanjiv has been kind to host them at http://www.gwt-ext.com/screencasts/.
I have also edited the gwt-ext wiki accordingly. (See comments)

Related links:
Google Web Toolkit
ExtJS Javascript library
Gwt-Ext
Cypal Studio for Gwt