How to create a Google Map S-Control?


In many cases you may need to create a control that utilizes the Google Map APIs to show location of a client, client's offices, driving directions, etc to the user.

In this example, I will demonstrate how you can develop a s-control to show driving directions from the User's location to any Account that the User is viewing.

In order to accomplish this I initially thought of a Visualforce Page which hosts the Google Map API and I could use the all-ready standard components with original Salesforce look and feel.

So I tried and I tired and did not seem to get anywhere since Google was not recognizing my key, so Google's authentication for my website let's say: http://na5.salesforce.com/ would fail each time I tried to view my VF page. Finally I decided to do this in a S-Control and then call the S-Control in VF page.

If you need more information about how you can call a S-Control in your VF pages click here.

So first let's see what we want to achieve:
The S-Control reads the URL parameters and looks for two parameters to be passed to it:
  • from: the location where the sales rep (user) is located.
  • to: The target location, could be the Account's address
Once the S-Control is called and the two parameters passed via URL, the S-Control then in turn calls Google's APIs to view the map and show the directions information.

Now let's first create a new S-Control:
  1. Click on "Setup" which is located on the top right corner of the force.com page.
  2. Expand "Develop" item and click on "S-Controls"
  3. Hit "New Custom S-Control"
  4. Provide a Label "Map Directions" and a description is you wished to
  5. The Type should be HTML since we want to create a HTML, Google API mash up
  6. Now it is time to enter the code for the S-Control


<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Google Maps JavaScript API Example: Advanced Directions</title>
<link href="/dCSS/Theme2/default/common.css" type="text/css" media="handheld,print,projection,screen,tty,tv"
rel="stylesheet">
<link href="/dCSS/Theme2/default/custom.css" type="text/css" media="handheld,print,projection,screen,tty,tv"
rel="stylesheet">

<script src=" http://maps.google.com/?file=api&amp;v=2.x&amp;key={YOUR KEY}"
type="text/javascript"></script>

<script src="/js/functions.js" type="text/javascript"></script>

<script type="text/javascript" src="/soap/ajax/13.0/connection.js"></script>

<style type="text/css">
body {
font-family: Verdana, Arial, sans serif;
font-size: 11px;
margin: 2px;
}
table.directions th {
background-color:#EEEEEE;
}

img {
color: #000000;
}
</style>

<script type="text/javascript">

var map;
var gdir;
var geocoder = null;
var addressMarker;
var dirFrom = '{!$Request.from}';
var dirTo = '{!$Request.to}';
var mapLocale = ""
var SControlID = '{!$Request.lid}';
var SFrameIC = '{!$Request.ic}';


function initValues()
{
mapLocale = "en_US";

setInputFields(dirFrom,dirTo);
}

function setInputFields(from, to)
{
window.document.getElementById("fromAddress").value = from;
window.document.getElementById("toAddress").value = to;
}


function initialize()
{
initValues();

if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map_canvas"));
map.addControl(new GSmallMapControl());
map.addControl(new GMapTypeControl());
gdir = new GDirections(map, document.getElementById("directions"));
GEvent.addListener(gdir, "load", onGDirectionsLoad);
GEvent.addListener(gdir, "error", handleErrors);

setDirections(dirFrom, dirTo, mapLocale);
}
}

function setDirections(fromAddress, toAddress, locale) {
if ((fromAddress) && (toAddress))
gdir.load("from: " + fromAddress + " to: " + toAddress,
{ "locale": locale });
}

function handleErrors()
{
if (gdir.getStatus().code == G_GEO_UNKNOWN_ADDRESS)
alert("No corresponding geographic location could be found for one of the specified addresses. This may be due to the fact that the address is relatively new, or it may be incorrect.\nError code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_SERVER_ERROR)
alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + gdir.getStatus().code);

else if (gdir.getStatus().code == G_GEO_MISSING_QUERY)
alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + gdir.getStatus().code);

// else if (gdir.getStatus().code == G_UNAVAILABLE_ADDRESS) <--- Doc bug... this is either not defined, or Doc is wrong
// alert("The geocode for the given address or the route for the given directions query cannot be returned due to legal or contractual reasons.\n Error code: " + gdir.getStatus().code);

else if (gdir.getStatus().code == G_GEO_BAD_KEY)
alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + gdir.getStatus().code);

else if (gdir.getStatus().code == G_GEO_BAD_REQUEST)
alert("A directions request could not be successfully parsed.\n Error code: " + gdir.getStatus().code);

else alert("An unknown error occurred.");

}

function onGDirectionsLoad(){
// Use this function to access information about the latest load()
// results.

// e.g.
// document.getElementById("getStatus").innerHTML = gdir.getStatus().code;
// and yada yada yada...
}
</script>

</head>
<body onload="initialize()" onunload="GUnload()">
<div style="background-color: #CCCCCC">
<form action="#" onsubmit="setDirections(this.from.value, this.to.value, this.locale.value); return false">
<table>
<tr>
<th align="right">
From:&nbsp;</th>
<td>
<input type="text" size="25" id="fromAddress" name="from" value="" /></td>
<th align="right">
&nbsp;&nbsp;To:&nbsp;</th>
<td align="right">
<input type="text" size="25" id="toAddress" name="to" value="" /></td>
<th align="right">
Language:&nbsp;</th>
<th align="right">
<select id="locale" name="locale">
<option value="en" selected="selected">English</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="ja">Japanese</option>
<option value="es">Spanish</option>
</select>
</th>
<td>
<input name="submit" type="submit" value="Get Directions!" class="button" /></td>
</tr>
</table>
</form>
</div>
<div style="border-width: 1px; border-color: #000000; border-style: solid;">
<div style="overflow: auto; width: 99.5%; height: 380px">
<table class="directions" style="width: 100%; height: 100%">
<tr>
<td valign="top" style="width: 275px;">
<div id="directions" style="width: 275px; background-color: #ffffff;">
</div>
</td>
<td valign="top">
<div id="map_canvas" style="width: 100%; height: 375px; background-color: #ffffff;">
</div>
</td>
</tr>
</table>
</div>
</div>
</body>
</html>


If you would like to know how you can add your VF page which contains the S-Control to your Account's page click here.

4 comments:

  1. Where do I put the Merge Fields for my values of 'From' and 'To'?

    ReplyDelete
  2. Charles,
    "From" and "To" values will be pass to this s-control via a custom button or link.

    This part is demonstrated on the next article!
    Thanks

    ReplyDelete
  3. Oh, ok.
    We sell events - and right now I have a custom button that opens a gmap in a new window - and via url passes the 'Event Location' and 'Facility Location' to process mileage (which inherently calculates our minimum budget) Maybe you could write an article on how-to pass this mileage from GMAP directly into a SF field?

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete