Friday, August 8, 2008

How to Embed Google Maps into Google Gadgets

Google Maps API is the perfect tool for creating flexible map based applications. and using this API is a great way to increase a gadget's functionality. For us, developers, it is a great way to create rich and deep content for the users.

This article will demonstrate how to implement Google Maps API into Gadgets step by step with code examples and provide a different logic in order to solve several issues. To fully understand the article you should have basic knowledge of both Google Gadgets API and Google Maps API.

First of all, we need to create the Gadget XML that we will include the Google Maps:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="My Map Gadget" scrolling="false">
</ModulePrefs>
<Content type="html"><![CDATA[

]]></Content>
</Module>

Using Google Maps in Gadgets 101

Now we can start implementing Google Maps to the Gadget. The most common and simple way of implementing Google Maps to a Gadget is embedding The Google Maps' JavaScript Library directly, using <script> tag. Then we just need to create a div element which will contain our map and add the map initialization function to our Gadget XML:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="My Map Gadget" scrolling="false">
</ModulePrefs>
<Content type="html"><![CDATA[
<script src="http://maps.google.com/maps?file=api&v=2&key=ABQIAAAAoB0i6SDqobDYkh2Eosh8_hTZqGWfQErE9pT-IucjscazSdFnjBSYTmG9lkUGpfCnsUSpnbFtRXVsgAQ" type="text/javascript"></script>
<div id="maphere" style="width: 100%; height: 100%"></div>
<script>
function initMap()
{
var map = new GMap2(_gel('maphere'));
map.setCenter(new GLatLng(41.013066,28.9764), 9);
}

_IG_RegisterOnloadHandler(initMap);

</script>
]]></Content>
</Module>

Test the Sample

However implementing maps using the sample code above will force the gadget user to load Google Maps library even the Map does not required to be loaded during initial Gadget load. Thus, unless you're creating a gadget that solely use Google Maps, using this method will decrease your Gadget performance or worst case scenario; result a failure in validation if you're attempting to create a Gadget Ad (Gadget Ads must not exceed 50k on initial load). This means that in order to avoid performance issues, what we are going do is loading the Maps API after the user interaction with Google AJAX API's. This method is suggested by Dann from Google in the Google Gadget Ads Group. If you want to check that discussion, click here.

Google Ajax API's

Using AJAX API's will provide us a better namespace and the flexibility of loading the Maps API whenever we desire. In order to achieve this we will modify our initial working sample a little bit. Thus, we start modifying our code by embedding the Google AJAX API's Library, which only weighs around 4K.

<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoB0i6SDqobDYkh2Eosh8_hTZqGWfQErE9pT-IucjscazSdFnjBSYTmG9lkUGpfCnsUSpnbFtRXVsgA"></script>

After loading the library we will create a function which will handle loading of Google Maps API.

function loadMapsAPI()
{
google.load('maps', '2.x', {
'callback': initMap // This is our callback function
});
}

Now all that is left to do is replacing the former namespace inside the initMap function that was used in our sample and call the new loader function instead of initMap inside _IG_RegisterOnLoadHandler().

function initMap()
{
var map = new google.maps.Map2(_gel('maphere'));
map.setCenter(new google.maps.LatLng(41.013066,28.9764), 9);
}

_IG_RegisterOnloadHandler(loadMapsAPI);

The Gadget XML after applying these modifications should look like this:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="My Map Gadget" scrolling="false">
</ModulePrefs>
<Content type="html"><![CDATA[
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoB0i6SDqobDYkh2Eosh8_hTZqGWfQErE9pT-IucjscazSdFnjBSYTmG9lkUGpfCnsUSpnbFtRXVsgA"></script>
<div id="maphere" style="width: 100%; height: 100%"></div>
<script>
function initMap()
{
var map = new google.maps.Map2(_gel('maphere'));
map.setCenter(new google.maps.LatLng(41.013066,28.9764), 9);
}

function loadMapsAPI()
{
google.load('maps', '2.x', {
'callback': initMap
});
}

_IG_RegisterOnloadHandler(loadMapsAPI);

</script>
]]></Content>
</Module>

Test the Sample

The gadget sample above basically has the same functionality of the first sample, which is loading the Google Maps API as the user loads the Gadget itself. However in order to use the AJAX API's full potential, we need change our code so that the API will load only after the user interaction. For basic representation we will start with a simple example.

To prevent the map load during gadget initialization we first remove the _IG_RegisterOnLoadHandler(loadMapsAPI); line from our code. Then we create a simple link inside the div element which handles map load:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="My Map Gadget" scrolling="false">
</ModulePrefs>
<Content type="html"><![CDATA[
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoB0i6SDqobDYkh2Eosh8_hTZqGWfQErE9pT-IucjscazSdFnjBSYTmG9lkUGpfCnsUSpnbFtRXVsgA"></script>
<div id="maphere" style="width: 100%; height: 100%"><a href="javascript:loadMapsAPI();">Click here to load the map!</a></div>
<script>
function initMap()
{
_gel('maphere').innerHTML = '';
var map = new google.maps.Map2(_gel('maphere'));
map.setCenter(new google.maps.LatLng(41.013066,28.9764), 9);
}

function loadMapsAPI()
{
_gel('maphere').innerHTML = 'Loading...';
google.load('maps', '2.x', {
'callback': initMap
});
}


</script>
]]></Content>
</Module>

Test the Sample

Raising The Bar

Since we've got through the basics, we can focus on more complicated Gadget samples. Many developers prefer using tabs within their gadgets, which allows them to enrich their gadget content. Luckily we have an easy way to create tabs in our Gadgets: Tabs Library!

First we start writing our gadget by creating our tabs.

function initGadget()
{
var tabs = new _IG_Tabs();
tabs.addDynamicTab("Start", firstTab); //We'll deal with the callback functions later
tabs.addDynamicTab("Map", loadMapsAPI); //We'll deal with the callback functions later
}

As you can see, we used callback functions to dynamically create our tab divs. Basically the developer can create anything he/she desires within that tab. However, since we're focused on embedding Google Maps into a tab we'll disregard the other tab functions. And for the map callback function we will use our basic loadMapsAPI() function as the basis.

function loadMapsAPI(tabId)
{
if(!_gel('maptab')){
var e = document.createElement('div');
e.id = 'maptab';
e.innerHTML = 'Loading...';
_gel(tabId).appendChild(e);
google.load('maps', '2.x', {
'callback': initMap
});
}
}

You may have noticed that our map function has a tabId parameter now which helps us to point the div which has created by the addDynamicTab method. Also,
in order to avoid creating multiple instances of the div "maptab", we write a controller which basically checks whether we created the map before. However if the developer decides to change the map content, thus reinitialize map, every time the tab is selected by users. The controller can be modified like this:

function loadMapsAPI(tabId)
{
if(!_gel('maptab')){
var e = document.createElement('div');
e.id = 'maptab';
e.innerHTML = 'Loading...';
_gel(tabId).appendChild(e);
google.load('maps', '2.x', {
'callback': initMap
}
});
} else {
initMap();
}
}

As for the finishing touches, we just need to add _IG_RegisterOnloadHandler() function. We can also play with the style sheets to make our gadgets cooler, so style sheet was added to this sample as well. After the polishing we should have a gadget like this:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="My Map Gadget" scrolling="false" width="500" height="250">
<Require feature="tabs" />
</ModulePrefs>
<Content type="html"><![CDATA[
<style type="text/css">
.tablib_table {
width: 500px;
height: 25px;
border-collapse: separate;
border-spacing: 0px;
empty-cells: show;
font-size: 12px;
text-align: center;
background-color: #ccc;
}
.tablib_emptyTab {
border-bottom: 1px solid #676767;
padding: 0px 1px;
}
.tablib_spacerTab {
border-bottom: 1px solid #676767;
padding: 0px 1px;
width: 1px;
}
.tablib_selected {
padding: 2px 0px;
background-color: #3366cc;
border: 1px solid #676767;
border-bottom-width: 0px;
color: #fff;
font-weight: bold;
width: 80px;
cursor: default;
}
.tablib_unselected {
padding: 2px 0px;
background-color: #ddd;
border: 1px solid #aaa;
border-bottom-color: #676767;
color: #999;
width: 80px;
cursor: pointer;
}
.tablib_navContainer {
width: 10px;
vertical-align: middle;
}
.tablib_navContainer a:link, .tablib_navContainer a:visited, .tablib_navContainer a:hover {
color: #3366aa;
text-decoration: none;
}
#maptab{width: 499px; height:224px; border: 1px solid #ccc; border-top-width: 0px;}
#starttab{width: 499px; height:224px; border: 1px solid #ccc; border-top-width: 0px;}
</style>

<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAoB0i6SDqobDYkh2Eosh8_hTZqGWfQErE9pT-IucjscazSdFnjBSYTmG9lkUGpfCnsUSpnbFtRXVsgA"></script>
<script>

function initMap()
{
_gel('maptab').innerHTML = '';
var map = new google.maps.Map2(_gel('maptab'));
map.setCenter(new google.maps.LatLng(41.013066,28.9764), 9);
}

function loadMapsAPI(tabId)
{
if(!_gel('maptab')){
var e = document.createElement('div');
e.id = 'maptab';
e.innerHTML = 'Loading...';
_gel(tabId).appendChild(e);
google.load('maps', '2.x', {
'callback': initMap
});
}
}

function firstTab(tabId) {
if(!_gel('starttab')){
var e = document.createElement('div');
e.id = 'starttab';
e.innerHTML = 'This is our first page.. So Welcome!';
_gel(tabId).appendChild(e);
}
}

function initGadget()
{
var tabs = new _IG_Tabs();
tabs.addDynamicTab("Start", firstTab);
tabs.addDynamicTab("Map", loadMapsAPI);
}

_IG_RegisterOnloadHandler(initGadget);

</script>
]]></Content>
</Module>

Test the Sample

Voila! Adding Google Maps to your gadgets is actually as simple as this. You may use the samples provided in this article in your gadgets if you like and hopefully develop them further to create some really cool stuff.

Cheers!

Note: When copying and pasting the codes provided here avoid pasting them directly to Google Gadgets Editor and use a text editor instead. This will prevent unnecessary line breaks, thus you won't have to deal with XML validation errors.

Wednesday, August 6, 2008

So Say We All

document.write(''+
'This is the first blog post!'+
'');