Convergence of Technologies: Restlets, Highslide Viewer & Google Maps API
Posted: 08/13/2009 by Serge IlynIt started when I decided to add Google maps to some of articles published on Naviquan.com. That would certainly make user experience better by providing additional location information for various destinations we often talk on our site. Since Google maps are not something new, and you can often see them embedded on various sites, I decided to search the Web in a hope that some kind soul would post in a blog a canned solution I could easily adapt to our site without much thinking about details and Google Map API (I have enough of them without maps).
Indeed, I found plenty of examples how to use Google maps - I wanted a Javascript solution (instead of using Google special URLs). But I wanted a solution with a twist - maps I wanted should open in a non-blockable pop-up when the user clicks on a button or link. Something like this: Oh Almighty! Please show Toledo, USA on a map!. Indeed, I found a couple of examples like this on the Web. One turned out to be a subscription (!) service for a monthly fee. Another looked like very close to what I wanted, but it was obfuscated enough to frustrate and repudiate any attempt to understand how it was done on that site.
Then, I recalled an old maxim, If you want something done right do it yourself, and decide that I will be myself that kind soul. So, if you want something like this - keep reading, use my solution and/or adapt it as you need.
For the recipe to cook this baby, I needed some ingredients. Two of them were obvious:
- Restlets open source Lightweight REST Framework for Java to build Web applications - after all, the whole Naviquan.com runs on Restlets.
- The Google Maps API - it lets you geocode locations, generate maps and if needed to embed them on web pages with JavaScript. At the same time, Maps API is a free service, available for any web site that is free to consumers.
I also wanted a solid Javascript library to deal with various pop-ups, and I quickly discovered Highslide JS, an open source image, media and gallery viewer written in JavaScript. It is really good, and it is completely free for non-commercial sites and $29 for all other sites.
Once I decided on all parts of the solution, its implementation became really easy. Here are steps you need to reproduce it.
1. Set up Highslide JS
Download it from the Highslide site, copy appropriate files into your Web server, put them into default /highslide folder of the Web site root and modify your pages to include the following into the
node (see Listing 1). The two files referenced by the code below (first two lines) will be among those from the Highslide Web site. The Javascript code in Listing one is needed to customize the appearance of the pop-up window. If you want to know more about customization options available in Highslide JS, you need to read its documentation for rather extensive API offered by the library.Listing 1
... <script type="text/javascript" src="/highslide/highslide-with-html.js"> </script> <link rel="stylesheet" type="text/css" href="/highslide/highslide.css" /> ... <script type="text/javascript"> hs.graphicsDir = '/highslide/graphics/'; hs.wrapperClassName = 'dark'; hs.width = 530; hs.height = 460; hs.preserveContent = false; hs.showCredits = false; hs.captionEval = 'this.a.innerHTML'; </script> ...
2. Modify Your Restlet Web Application
First, I would like to clarify that this solution does not rely exclusively on Restlet framework and can be easily implemented on other frameworks or even other Wed development technologies. For example, FreeMarker template mentioned below, geo.ftl, can be easily modified as a JSP page, geo.jsp. Values for address and country I pass to the template using FreeMarker techniques, can be passed using JSP ways of doing this. If your framework relies on query string parameters to pass values to Web container, use them. Thus, you link and its HREF attribute in particular can look something like:
<a href="/geo.jsp?address=Toledo&country=es" onclick="return hs.htmlExpand(this, { objectType: 'iframe' } )">Toledo, Spain</a>
Anyway, this our site it built on Restlet technology - let's see how it can be done with Restlets.
Since we are going to open pop-up using link, we need to set a new route like
described in Listing 2. As you see, my URIs for maps will include /geo
following address and optionally country. WARNING: Since address may include
characters prohibited in URLs (for example, spaces), you need encode addresses
before you can use them in URLS. For example, this address
105 N Market St, Frederick, MD 21701
when encoded will look like
105%20N%20Market%20St%2C%20Frederick%2C%20MD%2021701
You can do encoding manually (if you remember how to encode prohibited characters). Another option is to find a site that offers to encode and string - your can copy and paste an encoded address into your URL. Finally, you can opt for more sophisticated solution where you use regular address that is encoded right before (using Javascript) using it in a link.
Several words about the country part of the URI pattern. If you want to know more details why this parameter is needed and how, you have to read documentation about the Geocoder in Google Maps API. Suffice to say here that if you do not use this option, Google Maps API Geocoding Service returns address results influenced by the domain (country) from which the request is sent. By using the country parameter, you can set the Geocoding Service to return results biased to a particular country. This parameter takes a country code in ccTLD (county code top-level domain) format.
For example, this link will show Toledo, USA /geo/Toledo/us.
On the other hand, this link will show Toledo, Spain /geo/Toledo/es
Listing 2
...
// pop-up iframes with maps
router.attach("/geo/{address}", GeoMappingResource.class);
router.attach("/geo/{address}/{country}", GeoMappingResource.class);
...
From the code above you can see that I am using GeoMappingResource.class to process requests for maps. The GeoMappingResource.java code is in Listing 3. Nothing unusual here - if you followed my previous Restlet cookbook recipes, you should know that NaviquanBaseHTMLResource is a very simple base class for my resources that in its turn extends org.restlet.resource.Resource as it should.
Listing 3
import org.restlet.*;
import org.restlet.data.*;
import org.restlet.ext.freemarker.*;
import org.restlet.resource.*;
import com.naviquan.*;
import com.naviquan.data.*;
import java.net.URLDecoder;
import java.io.*;
public class GeoMappingResource extends NaviquanBaseHTMLResource {
GeoMapPage geoMapPage;
public GeoMappingResource(Context context, Request request, Response
response) {
super(context, request, response);
geoMapPage = new GeoMapPage();
String address = (String) request.getAttributes().get("address");
String country = (String) request.getAttributes().get("country");
try {
address = URLDecoder.decode(address, "UTF-8"); }
catch (UnsupportedEncodingException ex) {
address = ""; }
geoMapPage.setAddress(address);
geoMapPage.setCountry(country);
}
public Pagable getPagable() {
return geoMapPage; }
@Override
public Representation getRepresentation(Variant variant) {
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_HTML)) {
try {
result = new TemplateRepresentation("geo.ftl",
Naviquan.freeMarkerConfig, getPagable(), MediaType.TEXT_HTML);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
return result;
}
}
If you take a closer look at the code, you will see that there are still two things that I have not yet covered: one is GeoMapPage class and the second is geo.ftl FreeMarker template. GeoMapPage type is really simple - essentially, it's a bean with two attributes that we will need to pass (their values) to geo.ftl FreeMarker template. See Listing 4 for GeoMapPage.java. If you are curious about Pagable interface which GeoMapPage implements, again, please read my previous cookbook articles regarding Restlets. Here I can only say that it is a part of a rather trivial plumbing that allows for easy passing of any Pagable bean to FreeMarker templates (or rather values of bean attributes that templates may need).
Listing 4
public class GeoMapPage implements Pagable {
private String address;
private String country;
public int getLayout() {
return 0; }
public int getPageType() {
return 0; }
public void setAddress(String address) {
this.address = address; }
public void setCountry(String country) {
this.country = country; }
public String getAddress() {
return address; }
public String getCountry() {
if (country == null) {
return "us"; }
return country;
}
}
Finally, let's talk about geo.ftl FreeMarker template. In a way, this is the central part of the whole solution since this is the piece where. The whole content of the template file is in Listing 5 (you can also download it here). I am not going to talk about how to use Google Maps API to geocode and render maps. If you want to know more details, please read Google documentation on this issue. The only thing I would mention is that the mapping code below relies on newly released Maps API Version 3 (instead of ver. 2 widely used now). Maps API Version 3 still lacks some features you can find in version 2 - if you need them, it's really easy to modify the code below for version 2.
As far as integration of the template below with my GeoMapPage class, please note these two lines in Listing 5:
var address_value = "${address}";
var country_code = "${country}";
Indeed, very elegant: you can inject values you need from your Restlet resource right into the Javascript code!
Listing 5
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-AU">
<head>
<meta http-equiv="Expires" content="Mon, 26 Jul 1997 05:00:00 GMT">
<meta http-equiv="Last-Modified" content="Sat, 10 Nov 1997 09:08:07 GMT">
<meta http-equiv="Cache-Control" content="no-store, no-cache,
must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
var address_value = "${address}";
var country_code = "${country}";
var infowindow;
function showMap() {
var geocoder = new google.maps.Geocoder();
if (geocoder) {
geocoder.geocode( { address: address_value , country: country_code },
function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
var myOptions = {
zoom: 15,
center: results[0].geometry.location,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"),
myOptions);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location,
title: address_value
});
// uncomment three lines below if you want to add InfoWindow
// if (infowindow) infowindow.close();
// infowindow = new google.maps.InfoWindow({content:
results[0].formatted_address});
// infowindow.open(map, marker);
} else {
alert("No results found");
}
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
}
</script>
</head>
<body onload="showMap()">
<div id="map_canvas" style="width:500px; height:400px"></div>
</body>
</html>
Highslide Links to Open Google Maps in Pop-ups
I already used such links above, so, perhaps, you already peeked at how it's done. Anyway, let's do it again and together. Here is the link Toledo, Spain , and that's how its HTML code looks like
<a href="/geo/Toledo/es" onclick="return hs.htmlExpand(this, { objectType:
'iframe' } )">Toledo, Spain</a>
Several things to talk about here. First, the JavaScript code in the onclick event is a part of Highslide JS API. Again, if you want to know why and what and how, please read Highslide JS documentation.
Second - note that objectType specified in Highslide "expander" is 'iframe'. Yes, in my implementation pop-ups are IFRAMEs. Highslide JS is very flexible and feature rich and ability to work with IFRAMEs is one of such features. But not the only one (hint - want to know more? - read documentation). But my choice is not accidental since IFRAMEs allows for cross- domain access that would not be possible if you decided to rely on things like AJAX.
Finally, by now you should have noticed that the body of the ANCHOR tag is actually used as a value for the pop-up caption. There are other ways of doing this available in Highslide JS, but that's how I configured my solution (see Listing 1).
Recent Blogs
Convergence of Technologies II: Restlets, Highslide Viewer & SlideShowPro for Flash How to use Restlets, Highslide Viewer & Slide Show Pro Flash component to generate slide shows and show them in pop-up iframes 08/14/2009
Convergence of Technologies: Restlets, Highslide Viewer & Google Maps API How to use Restlets, Highslide Viewer & Google Maps API to generate Google maps using links on Web pages and show them in pop-up iframes. 08/13/2009
Ambimorphic, Mobile Configurations for Lamport Clocks After years of technical research into Smalltalk, with close cooperation with MIT Computer Science and Artificial Intelligence Laboratory (CSAIL) and its SCIgen, we have been able to show the construction of link-level acknowledgements. As a result, we propose an analysis of A* search, which we call Volt. 10/02/2008
Restlets and HTTP Sessions (Recipe 9) This recipe discusses some options in available to Web site developers using Restlet API to emulate traditional HttpSession's 07/31/2008
Restlet Cookbook - Delving into handleGet() and handlePost() (Recipe 8) This posting provides related examples that illustrate at least some simple things you may do with several handleX() methods where X may stand for various types of calls. 07/30/2008
Restlet Cookbook - How to enable SSL (Recipe 7) How to enable SSL on Restlet Web sites wihtout knowing too much keys, encryption, SSL and such. 07/29/2008
Restlet Cookbook - Using Filters in Restlet Applications (Recipe 6) This recipe contains code snippets illustrating the use of Filters in Restlet applications 03/09/2008
Restlet Cookbook - How to Create Virtual Hosts in Restlet Applications (Recipe 5) This recipe contains a code snippet for creating virtual hosts 02/24/2008
Popular HTML Escape Codes Here is a table of HTML escape codes for most frequently used symbols. If you use HTML, sooner or later you will find this table useful. 12/29/2007
Restlet Cookbook - Access Log (Recipe 4) This recipe describes access Logging using Restlet framework 11/09/2007
more...