First Look at Google Maps API v3

Google announced version 3 of the google maps API.
Some things I noticed right away:
- no API key required! before you had to sign up for an API key, but now you can shed your API key woes (I know I had them) and not have to sign up for one again.
- mobile browser support (iphone and android)

- You now have to specifiy the sensor variable in your include of google maps
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true">
the sensor is for detecting user's location with mobile sensors like GPS - No more polluting the global namespace with tons of variables. Google is moving over to their google namespace. so instead of
new GMap2you donew google.maps.map New mouse event object.- Default UI so that as Google maps updates their UI, you get to enjoy the new interface without updating code.
- it is like 3AM now so that is enough changes to see before I go to bed. =P
The only thing I couldn't figure out is where is the directions object? A lot of my maps applications use driving directions, so I'll be holding off on migrating over until I figure that out with the new API.
jQuery and Google Maps #2: AJAX Storing and Retrieving Points
Note on April 30th: This article was updated do to feedback from Google Maps API team to use the Client Geocoder instead of encoding on the server.
Continuing the series on jQuery and Google Maps, I will teach you how to store and retrieve points with using AJAX and a server-side language. This tutorial will use PHP/MySQL on the server, but it is basic enough that re-writing in another language should not be difficult.
First, let me share with you the design-pattern behind the tutorial. My design pattern has two steps. The first is to use a simple HTML form to create new locations by posting the data to the server via AJAX. The second step is to fetch those locations from the server also via AJAX. Sound simple? Well, then lets get started...
Note: This tutorial builds on the first tutorial's code, so all code I am showing you here will be added onto it.
Step #1: Create the, "Add Location" Form
To allow users to add locations to the map, let's create a basic form. This will include the parts of an address, as well as the name of the location.
HTML:
<form id="add-point" action="map-service.php" method="POST"> <input type="hidden" name="action" value="savepoint" id="action"> <fieldset> <legend>Add a Point to the Map</legend> <div class="error" style="display:none;"></div> <div class="input"> <label for="name">Location Name</label> <input type="text" name="name" id="name" value=""> </div> <div class="input"> <label for="address">Address</label> <input type="text" name="address" id="address" value=""> </div> <button type="submit">Add Point</button> </fieldset> </form>
A couple things to note about the form:
- The form's action is pointed to map-service.php, which is where we will process the form data.
- A hidden input
<input type="hidden" name="action" value="savepoint" id="action">will be used on the server to flag that we want to save a point to the database. This is just a personal preference on how to do things, there are many other ways to flag the intended action. - An empty div with class error
<div class="error" style="display:none;"></div>is placed in the form to be used in a later step to display errors.
Step #2: Add Styles to the Form
By adding a few CSS rules to our page, we will set our form next to the map and spruce up the form a bit.
Css:
#add-point { float:left; } div.input { padding:3px 0; } label { display:block; font-size:80%; } input, select { width:150px; } button { float:right; } div.error { color:red; font-weight:bold; }
Step #3: Geoencode Address Before Submiting Data
3a) Override default form submit
At this point we'll override the form's default submit action by selecting the form $("#add-point"), then using jQuery's submit event method. This method accepts a function that will run on submit of the form.
JavaScript:
$("#add-point").submit(function(){ geoEncode(); return false; });
3b) Add GeoCoder
Then, inside the submit we will post the form data with AJAX using jQuery's ajax post method.
JavaScript:
var geo = new GClientGeocoder(); var reasons=[]; reasons[G_GEO_SUCCESS] = "Success"; reasons[G_GEO_MISSING_ADDRESS] = "Missing Address"; reasons[G_GEO_UNKNOWN_ADDRESS] = "Unknown Address."; reasons[G_GEO_UNAVAILABLE_ADDRESS]= "Unavailable Address"; reasons[G_GEO_BAD_KEY] = "Bad API Key"; reasons[G_GEO_TOO_MANY_QUERIES] = "Too Many Queries"; reasons[G_GEO_SERVER_ERROR] = "Server error";
3c) Get geocode from address
JavaScript:
function geoEncode() { var address = $("#add-point input[name=address]").val(); geo.getLocations(address, function (result){ if (result.Status.code == G_GEO_SUCCESS) { geocode = result.Placemark[0].Point.coordinates; savePoint(geocode); } else { var reason="Code "+result.Status.code; if (reasons[result.Status.code]) { reason = reasons[result.Status.code] } $("#add-point .error").html(reason).fadeIn(); geocode = false; } }); }
Step #4: Submit Data to Server
JavaScript:
function savePoint(geocode) { var data = $("#add-point :input").serializeArray(); data[data.length] = { name: "lng", value: geocode[0] }; data[data.length] = { name: "lat", value: geocode[1] }; $.post($("#add-point").attr('action'), data, function(json){ $("#add-point .error").fadeOut(); if (json.status == "fail") { $("#add-point .error").html(json.message).fadeIn(); } if (json.status == "success") { $("#add-point :input[name!=action]").val(""); var location = json.data; addLocation(location); zoomToBounds(); } }, "json"); }
The $.post method accepts parameters.
- URL to post data to:
$(this).attr('action')will get the action attribute from the form that was submitted in the previous step. - Data in name, value pairs i.e.
{ name: "inputname", value: "inputvalue" }we will get all the inputs using the :input selector in jQuery, then use the serialize array function to turn those inputs into name, value pairs. Then add the two geocode name/value pairs to the data object. - Function to run after AJAX response is received. This function has one parameter which contains the response of the AJAX request.
- Type of data to be returned (optional). In this case we will use JSON.
Step #5: Use PHP on the Server to Process the Form
Once the data is posted with jQuery, we can handle it on the server with PHP.
5a) Check the action and validate the name
Let's simply check if the action variable is posted as, "savepoint". Then validate that the name has the proper characters with a regular expression and also that it is not empty. If any data is invalid, let's call a fail method (defined in next sub-step) with the message we want to show to the user.
PHP:
<?php if ($_POST['action'] == 'savepoint') { $name = $_POST['name']; if(preg_match('/[^\w\s]/i', $name)) { fail('Invalid name provided.'); } if(empty($name)) { fail('Please enter a name.'); } } ?>
Save the file as map-service.php or whatever you named your form's action attribute.
5b) Output the error message as a JSON object
Our fail function will use PHP's die method to stop the script from executing and output an error message to the client. Since the front-end (jQuery) is expecting a JSON object, we want to make sure to always send back a JSON response. To output JSON with PHP, you simply pass an array into the json encode method (json_encode is PHP 5.2+ only, if you are using less than 5.2 then use the JSON PHP library).
PHP:
function fail($message) { die(json_encode(array('status' => 'fail', 'message' => $message))); }
For the JSON array we want to use the JSEND specification for sending back a response. Basically, you have a key/value pair of status equals success or fail. That way the response can easily be checked on the front-end. I'm deviating from the JSEND spec a little bit by only sending a string back instead of a key/value pair of messages.
Using Firebug and Firefox, we can inspect the Ajax requests easily within the browser.

You can see here I submitted the form without entering a name and it sent me back an error message in the form of JSON.
Step #6: Display the Error Messages with jQuery
Hopping back to the jQuery code, we will write the error handling.
Inside the post code, we will first use the hide method to hide the error div in case it is already displaying. Then check if json.status is showing, "fail". If it is, we'll place the json.message inside the error div with jQuery's html attribute method and then fade it in with the fade in method.
JavaScript:
$("#add-point .error").hide(); if (json.status == "fail") { $("#add-point .error").html(json.message).fadeIn(); }
Step #7: Create a Database and Store the Locations
Using SQL, create a database table named locations which has a "name", "latitude", "longitude" and an "id" in it. If you need help with this, you will have to consult w3schools php and mysql for more help.
7a) Create the table with SQL
Mysql:
CREATE TABLE `locations` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) DEFAULT NULL, `lat` FLOAT(15,11) DEFAULT NULL, `lng` FLOAT(15,11) DEFAULT NULL, PRIMARY KEY (`id`) )
7b) Insert name and location into the database with PHP and MySQL
We will use PHP and MySQL to insert the new location into the database. Directly after, we will either flag a success or fail message to the user.
PHP:
$query = "INSERT INTO locations SET name='$_POST[name]', lat='$lat', lng='$lng'"; $result = map_query($query); if ($result) { success(array('lat' => $_POST['lat'], 'lng' => $_POST['lng'], 'name' => $name)); } else { fail('Failed to add point.'); }
If you noticed, I created a custom function called map_query to abstract out the database stuff. Here is the function definition. Make sure to update the, "MYSQL" stuff with your credentials.
PHP:
function map_query($query) { mysql_connect('MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD') OR die(fail('Could not connect to database.')); mysql_select_db ('MYSQL_DATABASE'); return mysql_query($query); }
I also created a similar method to "fail" called "success" which looks like:
PHP:
function success($data) { die(json_encode(array('status' => 'success', 'data' => $data))); }
An example of a succesful response in firebug:

Step #8: Map the New Point
Going back to the jQuery code, we can now add the success response handling. The response is a JSON object with "lat", "lng" and "name" properties. I'll give you the code inside the success handling, then later show you what each custom function is doing.
JavaScript:
if (json.status == "success") { $("#add-point :input[name!=action]").val(""); var location = json.data; addLocation(location); zoomToBounds(); }
After a location is successfully added to the database, we want to clear the form to prevent duplicate entry. Do this by selecting the inputs with the :input selector. Then we need to filter out the action input, do this by using the attribute not equal selector [name!=action].
My addLocation(location) function is simply our code from the last tutorial placed into a function to be reusable later.
JavaScript:
function addLocation(location) { var point = new GLatLng(location.lat, location.lng); var marker = new GMarker(point); map.addOverlay(marker); bounds.extend(marker.getPoint()); $("<li />") .html(location.name) .click(function(){ showMessage(marker, location.name); }) .appendTo("#list"); GEvent.addListener(marker, "click", function(){ showMessage(this); }); }
It has a few things you might want to note:
- using location.name, location.lat and location.lng means that we will be passing in a location object with those properties to the function.
- Ignore
bounds.extend(marker.getPoint());andzoomToBoundsfor now or skip to #13 quickly to find out what they do.
Step #9: Load and Display the Locations from in the Database
When the page initially loads, we want to load all of our stored points. The simplest way to do this (in my opinion) is to do a GET request to fetch a JSON object from the server after the page loads.
To make a, "GET" request to the server, we can use jQuery's getJson method. We will send the server a, "get" variable called action with value, "listpoints".
JavaScript:
$.getJSON("php/map-service.php?action=listpoints", function(json) { // do stuff in step #11 });
Step #10: Get the Locations from the Database
Simply check the, "GET" action in the PHP and run this code to fetch the locations records. Pretty straight-forward code here. We are creating an array of points and then sending them back to the client as JSON.
PHP:
if ($_GET['action'] == 'listpoints') { $query = "SELECT * FROM locations"; $result = map_query($query); $points = array(); while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { array_push($points, array('name' => $row['name'], 'lat' => $row['lat'], 'lng' => $row['lng'])); } echo json_encode(array("Locations" => $points)); exit; }
Step #11: Display the Locations
Iterate through the JSON object that contains the locations inside the getJson response function.
jQuery and Google Maps Tutorial: #1 Basics
There are many times I want to leverage jQuery's strengths to create a custom Google Maps mashup. In this tutorial, I will walk you through how to get started using jQuery inside the Google Maps environment. I will assume nothing, and explain each piece in detail.
If you are already familiar with Google Maps API, skip to step #5, or so.
Step #1: Get API key
First, grab yourself an API key for Google Maps, you will need this in the next step.
Step #2: Load Google Maps and jQuery
We want to load up jQuery and Google Maps with the Google AJAX Libraries API.
JavaScript:
<script type="text/javascript" src="http://www.google.com/jsapi?key=YOUR_API_KEY_HERE"> <script type="text/javascript" charset="utf-8"> google.load("maps", "2.x"); google.load("jquery", "1.3.1"); </script>
Make sure to replace YOUR_API_KEY_HERE with your API key. By using the Google AJAX Libraries API, it allows you to load the JavaScript libraries you need right from Google's servers. This increases the chance that your users will be able to load the scripts faster from their browser cache, as well as shuffle the jQuery script loading off your server.
Step #3: Create the Google Map

To create our Google Map, we need to create a container div and use CSS to give it a width and a height.
HTML:
<div id="map"></div>
Css:
<style media="screen" type="text/css"> #map { width:500px; height:500px; } </style>
Use the GMap2 function to make a map instance. Then, set the center of the map. I wrapped this code block in jQuery's document ready function so that the code is run after the page has loaded.
JavaScript:
$(document).ready(function(){ var map = new GMap2(document.getElementById('map')); var burnsvilleMN = new GLatLng(44.797916,-93.278046); map.setCenter(burnsvilleMN, 8); });
Here, I used Burnsville, MN's latitude and longitude because it is where I live right now. There are many ways to get the latitude and longitude of an address, like this simple service by iTouchMap.
The second parameter for setCenter is the zoom level, which is a number. I set the zoom level to "8" here because it is about in the middle.
At this point we should have a simple map.
Step #4: Load the Google Maps Example

To have some points to work with, let's paste in the google maps example.
JavaScript:
// setup 10 random points var bounds = map.getBounds(); var southWest = bounds.getSouthWest(); var northEast = bounds.getNorthEast(); var lngSpan = northEast.lng() - southWest.lng(); var latSpan = northEast.lat() - southWest.lat(); var markers = []; for (var i = 0; i < 10; i++) { var point = new GLatLng(southWest.lat() + latSpan * Math.random(), southWest.lng() + lngSpan * Math.random()); marker = new GMarker(point); map.addOverlay(marker); markers[i] = marker; }
Note that I added a markers array to the example code. This will be used in the next step.
Step #5: Loop Through Markers and Add Basic Click Event to Markers
In this step, we start to use jQuery and Google Maps together. We want to be careful to use Google Map's built-in API as much as possible, leaving jQuery only for what it is best at.
Let's take that array of markers and loop through them with jQuery's each method.
JavaScript:
$(markers).each(function(i,marker){ GEvent.addListener(marker, "click", function(){ map.panTo(marker.getLatLng()); }); });
Inside the loop, let's use Google Maps's GEvent namespace to attach a click event to each marker. Then, we will add a panTo behavior to center the map on the marker. marker.getLatLng(); returns the latitude and longitude of the marker, while map.panTo(GLatLng) allows us to center the map on that latitude and longitude.
Step #6 - Make a Clickable List of Markers
Let's add a clickable list next to the map. Insert a ul.
HTML:
<ul id="list"></ul>
Then let's style it up a bit by floating the map left and float our list element next to it. We also want to add a hover effect to the list items to give visual feedback to the user that they can click on each item in the list.
Css:
<style type="text/css" media="screen"> #map { float:left; width:500px; height:500px; } #list { float:left; width:200px; background:#eee; list-style:none; padding:0; } #list li { padding:10px; } #list li:hover { background:#555; color:#fff; cursor:pointer; cursor:hand; } </style>
In our jQuery each loop from last step, let's append the clickable list items to the list.
") .html("Point "+i) .click(function(){ map.panTo(marker.getLatLng()); }) .appendTo("#list");JavaScript:
$("<li />") .html("Point "+i) .click(function(){ map.panTo(marker.getLatLng()); }) .appendTo("#list");
Here I am just setting the content to "Point (the count)", adding that same panTo action from before, then appending the list item to our list.
Step #7 - Add a Custom Message

When I create a Google Maps mashup, I usually want to replace the built-in info window with something custom. With jQuery, we can add any arbitrary HTML in place of the info window. This is great when you want complete control over what the info window looks like.
Add a message div with some test text.
HTML:
<div id="message" style="display:none;"> Test text. </div>
Then add some basic styling to the message.
Css:
#message { position:absolute; padding:10px; background:#555; color:#fff; width:75px; }
We have to place the message div inside the map. To do this, we can use jQuery to append it to an object. The map view is seperated into panes. Each pane is a div layered on top of the other. To get the div object that we want to attach our message div to, we can use map.getPane(PANE). The G_MAP_FLOAT_SHADOW_PANE is the layer that I find works best for attaching custom messages.
JavaScript:
$("#message").appendTo(map.getPane(G_MAP_FLOAT_SHADOW_PANE));
To show the message div in place of the info window, we need to separate the click action into a separate function. Replace the map.panTo(marker.getLatLng(); with displayPoint(marker, i);, a call to the new displayPoint function shown below.
JavaScript:
function displayPoint(marker, i){ map.panTo(marker.getPoint()); var markerOffset = map.fromLatLngToDivPixel(marker.getPoint()); $("#message").show().css({ top:markerOffset.y, left:markerOffset.x }); }
We put the panTo action in our new function. Then the magic function here is the map.fromLatLngToDivPixel(GLatLng); which converts the latitude/longitude of the marker into a pixel on the map div. This returns a object containing x (amount of pixels from the left of the map) and y (amount of pixels from the top of the map).
Final Step #8 - Add Some Spice
To finish up, we will add an event when the map stops panning. We can do this by attaching the "movend" event map object. This way, after panning to the marker you've clicked on we can use jQuery's fadeIn method to add some spice.
JavaScript:
function displayPoint(marker, index){ $("#message").hide(); var moveEnd = GEvent.addListener(map, "moveend", function(){ var markerOffset = map.fromLatLngToDivPixel(marker.getLatLng()); $("#message") .fadeIn() .css({ top:markerOffset.y, left:markerOffset.x }); GEvent.removeListener(moveEnd); }); map.panTo(marker.getLatLng()); }
There you have it. We've come a long ways by adding our own custom click event, a clickable list and a custom info window. In the next tutorial, I'll show you how to store and retrieve points with a server-side language.
Why You Didn't Come to the Right Page
Update July 30, 2008: The Google crawl appears to be fixed, the search results look good now, so this issue is resolved! Hoorah.
Update July 23, 2008: There is great discussion going on on Sphinn about this issue.
I was shocked after searching Google today, Google is currently redirecting to my homepage and not the actual page searched for.

"Why is this?" I thought to myself. I tried the url that you should get... http://marcgrabanski.com/pages/code/jquery-ui-datepicker ... ok that works fine.
Now try the old url that has most of the link juice attached to it which has a 301 redirect to the new page... http://marcgrabanski.com/code/ui-datepicker/ ... ok that works fine too. So what is going on?
First, I go to Google Webmaster and see this, an SEO's nightmare:

Time to check my 301 redirects:

And on another 301 redirect tool:

Well that worked great. Still, what is the issue?
To make absolutely sure my 301 redirects work, I dumped the text redirects into a htaccess file. My htaccess code now looks like this:
Apache:
<FilesMatch "\.(htm|html|css|js|php)$"> AddDefaultCharset UTF-8 DefaultLanguage en-US </FilesMatch> RewriteEngine On RewriteBase / RewriteRule ^index\.php http://marcgrabanski.com/articles [R=301,L] RewriteRule ^code\.html http://marcgrabanski.com/pages/code [R=301,L] RewriteRule ^code/beyond-flash(/?) http://marcgrabanski.com/pages/code/beyond-flash [R=301,L] # MANY RedirectRules ... ALL WORK FINE RewriteCond %{HTTP_HOST} ^www.marcgrabanski.com$ [NC] RewriteCond %{REQUEST_URI} !.*tags.php.* [NC] RewriteRule ^(.*)$ http://marcgrabanski.com/$1 [R=301,L] RewriteCond %{REQUEST_FILENAME}.php -f RewriteCond %{REQUEST_URI} !/$ RewriteRule (.*) $1\.php [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.+)/$ /$1 [R=301,L] RewriteRule ^$ webroot/ [L] RewriteRule (.*) webroot/$1 [L] AddHandler php5-script .php
Update July 23, 2008 @1:24AM: I changed all of the Rules to
RewriteRule's which has cleaned up most of my 301 redirects. One issue remains, I need to figure out how to make a RewriteRule to convert:
http://marcgrabanski.com/tags.php?tag=FreeTools
to...
http://marcgrabanski.com/tag/free-tools
Interviewed by Google Part 2

As many of you know, I was interviewed by Google two more times. Once as a UI Engineer for Gmail and once for a web developer position at YouTube (Google owns YouTube). YouTube actually sent me out on a plane to San Francisco, but I was turned down for both positions. Let me explain how this played out...
UI Engineer at Gmail
The phone interview for UI Engineer was for a position on the gmail team. The person who interviewed me asked very heavy JavaScript questions. One question went like this, "How do you profile JavaScript"? So I told him about Firebug for Firefox and Drosera for Safari, that was fine. Then he asked me, "How do you profile JavaScript in Internet Explorer?" I had no idea so he responded with, "The JavaScript date object, outputting the date and time with each line of code executed". Wow, manually profiling JavaScript with the date object? - I wouldn't have thought of that, nor would I do that unless for some drastic situation.
Aside from the heavy JavaScript questions, I think the interview went well and I gained more insight about Google.
Web Developer at YouTube
The phone interview for YouTube went great. I was interviewed by the lead web developer and was interested to find they have less than 300 people there at the moment. He asked me a lot of JavaScript and CSS questions. This all went fine, and I got along with him very well. So they sent me a plane ticket for an on-site interview.
I took this opportunity to visit people I know at Google as well. The Googleplex is a pretty crazy place, seemed like Disney world for work. But, after being there for a few years the mystique of it all wears off and it is just an office to them. Free food and cafeterias made it seem a little reminiscent of college days. I even got a little jealous at one point.
The on-site interview was enjoyable. What was interesting to find out is how everything is based on self-motivation. You set your goals, you set your time lines. This works with very motivated people, but wouldn't work in the general working world for obvious reasons.
In the end, the reason I was given for not given an offer was being, "library dependent" (aka jQuery). This got under my skin a bit, because I don't think it is true. I understand how they thought this because I talked about jQuery a lot and am very involved in the community, but jQuery is used to get stuff done faster - not as a crutch. 4 of my 6 open source projects are without a library... but don't need to go any further.
Wrapping Up My Experience
I'll take free plane flights to Google any day. I was glad to meet the people I met, and to see the inside of Google was a good experience. Sounds like things aren't over quite yet with Google, but once again I still have a great position at RMG. Cheers!
Sightseeing in California
Out and about in California taking some pictures...



Interviewed by Google

I got interviewed by Google and was turned down. I never expected to interview with Google because it was not something I sought out. I love where I work, but you can't say "no" to an opportunity to entertain the idea of working with Google. An internal developer over there saw my work and said I should work there, so I sent over a resume and got interviewed. Even though I didn't get the job, it was a good experience and really tested my convictions of where I am at in my career.
In the interview I botched the technical interview badly. That is what happens when you have no formal education in your area of expertise. I don't know inheritance in JavaScript because I have never seen it in any script I've ever looked at. I learn by doing. I don't know SOAP, REST or any other acronyms that I don't use at work. Another question I was asked was the difference between DTD and Schema. I didn't have an answer for that either. Oh well.
The part that went well was the interview with a developer. They said I would be going to conferences and helping other developers learn their APIs. I do that now with jQuery and CakePHP. I answer questions, emails, etc about their APIs - I spoke at jQueryCamp and soon I'll be speaking at CakePHP Fest. He asked me how I would go about solving certain problems and I think that went well too. I realized that I would like to do more teaching and explaining - I need to get more tutorials on my website and do some teaching sessions at work.
What had me worried for a bit is how much would be taken away from my work with open source and building client applications. I like building applications for the marketplace at work. I think I would miss that. Lately I've had some big successes with projects and I want to make sure it continues. All-in-all it was a nice little trip for a while to be considered by Google, but I'm glad its over with. I get to do all the things that I enjoy right now where I work so I have nothing to complain about.
Customize Google Firefox Extension

Customize Google Firefox extension lets you do some great things to change how Google works. I am using it to remove the ads from Google search results and add Google Sense to the homepage. It works great, I have been wanting something like this for quite a while!
