Using the Google Maps API with Vertical Site

Google Maps is a popular Google service, where users are provided with a map that they can pan and zoom. They can also search the map by address or area, and also search for businesses or attractions. It also has an interface for adding maps to your own site, and it's possible to add custom points to the maps. In this article we will show how to use this feature by adding Google Maps to a page and adding content from Enonic Vertical Site using XMLHttpRequest.

Creating content with coordinates

The first step will be to create a content type for our geocoded images. We will use the following content type definition:

<config name="geoimage" version="1.0">
    <form>
        <title name="name"/>
        <block name="Positioning">
            <input name="name" required="true" type="text">
                <display>Title</display>
                <xpath>contentdata/name</xpath>
            </input>
            <input name="latitude" type="text">
                <display>Latitude</display>
                <xpath>contentdata/latitude</xpath>
            </input>
            <input name="longitude" type="text">
                <display>Price</display>
                <xpath>contentdata/longitude</xpath>
            </input>
            <input name="image" type="image">
                <display>Image</display>
                <xpath>contentdata/image</xpath>
            </input>
        </block>
    </form>
</config>

To add the content type, go to Content types in the admin web, select New, select "Custom content" from the "Content handler" drop down, add a name, add the definition above to the Configuration text box, and save it. Next, add some images and create some content with the new content type. Remember to add latitude and longitude. Finding the right coordinates can be a little tricky, but this tool can be used to get latitude and longitude from anywhere you click.

Serving data as XML

Since the page containing the map will fetch data from Enonic Vertical Site using XMLHttpRequest, we will need to create a page that returns our data as XML. Instead of creating a page that transforms the data to HTML, we will simply avoid transforming it and using the data as is. We will use the following page XSL, which simply outputs an object:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="saxon xs portal" version="2.0" xmlns:portal="http://www.enonic.com/cms/xslt/portal" xmlns:saxon="http://icl.com/saxon" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output encoding="utf-8" indent="yes" method="xml"/>
  <xsl:param name="object">
    <type>object</type>
  </xsl:param>

  <xsl:template match="/">
 <xsl:value-of disable-output-escaping="yes" select="$object"/>
  </xsl:template>

</xsl:stylesheet>

Create a new page template using this page XSL. The XSL for the object is equally simple:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="xs portal" version="2.0" xmlns:portal="http://www.enonic.com/cms/xslt/portal" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" method="xml" />

  <xsl:template match="/">
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>

Add a new object XSL in the adminpage using this XSL, then create an object that uses this object XSL, with the following datasource:

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <datasource>
    <methodname>getContentByCategory>/methodname>
    <parameters>
      <parameter name="query" type="string"/>
      <parameter name="categories" type="int[]">22</parameter>
      <parameter name="recursive" type="boolean">true</parameter>
      <parameter name="orderby" type="string">@publishfrom DESC</parameter>
      <parameter name="index" type="int">0</parameter>
      <parameter name="count" type="int">10</parameter>
      <parameter name="titlesOnly" type="boolean">true</parameter>
      <parameter name="childrenLevel" type="int">1</parameter>
      <parameter name="parentLevel" type="int">0</parameter>
      <parameter name="parentChildrenLevel" type="int">0</parameter>
      <parameter name="relatedTitlesOnly" type="boolean">true</parameter>
      <parameter name="includeTotalCount" type="boolean">false</parameter>
      <parameter name="includeUserRights" type="boolean">false</parameter>
      <parameter name="contenttypes" type="int[]"/>
    </parameters>
  </datasource>
</datasources>

Replace the category key with the key of the category you created earlier, containing the images with latitudes and longitudes. Finally, create a new menu item on the site that uses the page template and object we have just created. If you preview this page now, you should see a standard verticaldata XML document, the kind we normally use in XSLs and transform to HTML.

A simple map

API key

Before we start using the Google Maps API, we need an API key. This must be acquired per combination of domain and port that the API is used on. You can read more about this on the Google Maps API web pages. To get the API key, you'll need to get a Google Account if you don't have one already, and go here to sign up. If you are deploying on your own computer, it shouldn't be a problem if you specify localhost as the web site URL.

Save the API key somewhere it will be safe, it will be shown below how and where to use it.

Creating a Google Maps page

Next step is to create a page that will display the map. Create a page XSL in the adminweb using the following XSL, replacing APIKEY with your own API key:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="saxon xs portal" version="2.0" xmlns:portal="http://www.enonic.com/cms/xslt/portal" xmlns:saxon="http://icl.com/saxon" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd" encoding="utf-8" method="xhtml" omit-xml-declaration="yes"/>

  <xsl:template match="/">
    <html dir="ltr" xml:lang="en">
      <head>
        <title>Google Maps API Example</title>
        <script type="text/javascript" src="http://www.google.com/jsapi?key=APIKEY"></script>
        <script type="text/javascript" src="resources/scripts/google_maps.js"/>
      </head>

      <body style="margin-left: 10%; margin-right: 15%">
        <div id="map" style="width: 500px; height: 300px;"></div>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

This will load two Javascript files, one from Google and one that we will create ourselves, and outputs a div that will contain the map. To display a simple map, save the following Javascript as a script resource in the adminweb, and name it google_maps.js.

google.load("maps", "2.x");

// Call this function when the page has been loaded
var map;
function initialize() {
    map = new google.maps.Map2(document.getElementById("map"));
    map.setCenter(new google.maps.LatLng(39.83689, 3.123819), 16);
    map.setMapType(G_HYBRID_MAP);
}

google.setOnLoadCallback(initialize);

This loads the Google Maps API using the Google AJAX API loader and creates a map in the div element with the id attribute set to map. That's it! The default map is without any controls, but we can easily add that by making a few methods calls. Put the following below the call to map.setCenter:

    map.addControl(new google.maps.SmallMapControl());
    map.addControl(new google.maps.MapTypeControl());

Adding content to the map

Map showing markers and images

Now let us add some content to the map. First we will have to get our content from Enonic Vertical Site using XMLHttpRequest. Below the call to google.setOnLoadCallback I will add the following function to get the content:

function getContent() {
    var request =  new XMLHttpRequest();
    request.open("GET", "/cms/site/0/XML", false);
    request.send(null);

    imgDoc = request.responseXML;

This makes a synchronous request to our page that we set up to serve XML and puts the result in imgDoc. Next we loop over the content and get the latitude and longitude.

    var contents = imgDoc.evaluate('/verticaldata/contents/content', imgDoc, null, XPathResult.ANY_TYPE, null );
    var elem = contents.iterateNext();
    while (elem) {
        var latitude = elem.getElementsByTagName("latitude")[0].firstChild.data;
        var longitude = elem.getElementsByTagName("longitude")[0].firstChild.data;

Then we use the Google Maps API to create a marker and add it to the map.

        var latlng = new google.maps.LatLng(latitude, longitude);
        var marker = new google.maps.Marker(latlng);

        var imgId = getImageID(imgDoc, elem.getAttribute("key"));
        addListener(marker, latlng, imgId);

        map.addOverlay(marker);
        elem = contents.iterateNext();
    }
}

The function for getting the binary key, getImageId, is a little complicated, which is why I have put it in its own function. This basically does exactly what we'd have to do if we were doing the same thing in an XSL.

function getImageID(doc, key) {
    alert(key);
    var relatedElem =  doc.evaluate('/verticaldata/contents/content[@key='+ key +']/relatedcontentkeys/relatedcontentkey', doc, null, XPathResult.ANY_TYPE, null).iterateNext();
    var ckey = relatedElem.getAttribute("key");
    var binaryElem = doc.evaluate('/verticaldata/contents/relatedcontents/content[@key='+ ckey +']/binaries/binary[1]', doc, null, XPathResult.ANY_TYPE, null).iterateNext();
    return binaryElem.getAttribute("key");
}

The last function we need, is a function to display the images when the user clicks the markers. This is the addListener function used in the while-loop above, which uses the Google Maps API to add an event listener that fires when the user clicks the marker, and opens an information window with the image.

function addListener(marker, latlng, imgId) {
    GEvent.addListener(marker,"click", function() {
    var myHtml = "";
    map.openInfoWindowHtml(latlng, myHtml);
    });
}

Finally we need to make the getContent function be called when the page loads, which we do by making a call to setOnLoadCallback again.

google.setOnLoadCallback(getContent);

The completed Google Maps page can be seen here

Summary

As this article shows, the Google Maps API is an easy and free way of more value to a website if you have anything that can be placed on a map. The API is very flexible and has many options. For an overview of the possibilities, take a look at the Google Maps API examples.

Comments

If you want to comment on this article you need to be logged in.

Published in 2011

2010

2009

2008

2007