Wednesday, April 7, 2010

Developing with Online Mapping APIs - Part 5: Event Handling

We've covered a lot of ground, and our maps are now able to display rich data to our users. The next step is to allow users to interact with the map and to react to the user's input in a meaningful way. In order to do that we need to be able to handle mapping events. There are three general categories of events:

  • Keyboard and Mouse Events
  • Map Events
  • Point of Interest Events

Keyboard and mouse events are just what you might think. These events indicate that the mouse has been clicked, double-clicked, or a keyboard key has been pressed. In relation to the map display we can determine at what X and Y position the mouse was clicked.

Mapping events are related to changes in what is displayed. For instance, the user may utilize a control to pan the map north. This would be associated with a particular event. The same is true for changing the zoom or even the type of map that is displayed.

Point of interest events are specific actions taken on a point of interest. If the map displays several POI's, an event may be associated with a click action on an individual POI.

Each mapping API has a slightly different method for handling events. In many cases, a single user action may result in triggering a variety of events.


Interact with the map to see a log of the events that are received.
Listen for:
onkeypress onclick onchangemapstyle
onkeydown ondoubleclick onchangeview
onkeyup onmousedown onendpan
  onmouseup onendzoom
  onmouseover onresize
  onmouseout onstartpan
  onmousewheel onstartzoom

The VEMap class exposes an AttachEvent method which is used to subscribe to events. The method accepts two parameters: the name of the event to listen for and a reference to a function that will be called when the event occurs.

bingMap5.AttachEvent("ondoubleclick", bingEventHandler);

The VEMap.DetachEvent method takes the same parameters and will unsubscribe from an event.

bingMap5.DetachEvent("ondoubleclick", bingEventHandler);

The event handling function will be called with a single parameter. The parameter contains the details for the event. If the event is relative to a marker or other shape that is drawn on the map, the elementID field of the event details will contain the ID of the shape. The ID for each shape is maintained by the VEMap instance. Use the VEMap.GetShapeByID method to retrieve a reference to the shape instance.

The mapX and mapY properties indicate where on the map the event took place. These may be null for events that do not have a point on the map. To convert these values to a lattitude and longitude use the VEMap.PixelToLatLong method.

function bingEventHandler(eventDetails)
 var eventLog = document.getElementById("bingEventLog");
 var logEntry = "event received: " + eventDetails.eventName + "\n";
 logEntry = logEntry + "\tMap X:\t" + eventDetails.mapX + "\n";
 logEntry = logEntry + "\tMap Y:\t" + eventDetails.mapY + "\n";
 logEntry = logEntry + "\tZoom:\t" + eventDetails.zoomLevel + "\n";
 logEntry = logEntry + "\tElement ID:\t" + eventDetails.elementID + "\n";
 eventLog.value = logEntry + eventLog.value;

I've only hooked up a portion of the events that are available, and only a selection of the event details are shown on each log entry. Be sure to read through the MSDN documentation on Bing VEMap Events for the full set of events and event details. These events allow for you to write code that reacts to the user's actions. In this way you can load data that is within the viewing area when the map is moved, follow the mouse movement across the map, and a variety of other interesting responses.


Interact with the map to see a log of the events that are received.
Listen for:
click click
dblclick dblclick
mouseover mouseover
mouseout mouseout
dragstart dragstart
drag drag
dragend dragend

The Google API uses static methods on the GEvent class for registering event listeners. This is a similar difference to the Bing implementation as we saw with geocoding. Where the Bing API tends to roll functionality into the VEMap class as a one-stop shop for functionality, the Google API encapsulates functionality in separate classes.

The GEvent.addListener method is used to register a listener for an event. This method takes three parameters: the object that is the source of the event, the name of the event, and a reference to the function to call when the event is fired.

GEvent.addListener(source:Object, event:String, handler:Function)

Calling this method returns a reference to a GEventListener instance. This reference is used when calling the GEvent.removeListener method to deregister a listener for an event.


The Google event handlers are much different in invocation than the Bing event handler. The Bing event handler receives single parameter which is of the same type each time. The Google event handlers receive different parameters based on the type of event that was fired. This can make developing a cetralized event handler a bit of a chore. One other noticeable difference is that while Bing gives the X and Y coordinates on the map, the Google event receives lattitude and longitude where applicable.

function googleEventHandler(param1, param2)
 var eventLog = document.getElementById('googleEventLog');
 var eventLogEntry = 'Event fired:\n';
 if ( this == googleMap5 ) 
  eventLogEntry = eventLogEntry + '\tSource:\tMap\n';
  if ( param1 != null )
   eventLogEntry = eventLogEntry + '\tParam1:\t' + param1 + '\n';
  if ( param2 != null )
   eventLogEntry = eventLogEntry + '\tParam2:\t' + param2 + '\n';
 if ( this == googleMarker5 )
  eventLogEntry = eventLogEntry + '\tSource:\tMarker\n';
  if ( param1 != null )
   eventLogEntry = eventLogEntry + '\tLattitude:\t' + + '\n';
   eventLogEntry = eventLogEntry + '\tLongitude:\t' + param1.lng() + '\n';
 eventLog.value = eventLogEntry + eventLog.value;

A key difference between the Google event handlers and the Bing event handlers is that the JavaScript 'this' reference is set to the source of the event at the time when the event handler is called. This means that where code using the Bing API would need to get the element ID of the VEShape for an event, the Google event handler code can simply use the 'this' reference. It is possible to go one step further by instructing the Google API to bind an event to an object other than the source of the event using the GEvent.bind method.

GEvent.bind(source:Object, event:String, object:Object, method:Function)

When an event handler is registered in this manner, 'this' will now refer to the object instance given as the third parameter to the function. The bind method returns a reference to a GEventListener just like the addListener method. Unbinding the event handler is achieved by calling the GEvent.removeListener method with a reference to this GEventListener.

Another convenient feature of the Google event system are methods for triggering events without user action, and for clearing all event handlers. The GEvent.trigger method allows for the simulating of events on mapping elements, as well as the introduction of custom events. For instance, if you would like to simulate the user clicking on a marker (and thus fire all of the handlers for that event) you could call:

GEvent.trigger(gmarker, "click");

This will always fire all of the "click" handlers on teh gmarker instance, rather than attempting to keep track of these references on your own. In addition, you can invent custom events for any element. Simply add your handler using the same GEvent.addListener method with a reference to your custom event name. When you need to fire the custom event, call GEvent.trigger to fire it.

The GEvent class also exposes GEvent.clearListeners and GEvent.clearInstanceListeners. The GEvent.clearListeners method will remove all handlers for a specific event on an element. The GEvent.clearInstanceListeners will remove all handlers for all events.


Interact with the map to see a log of the events that are received.
endMapDraw endMapDraw onEndGeoRSS
changeZoom changeZoom  
startPan startPan  
onPan onPan  
endPan endPan  
startAutoPan startAutoPan  
endAutoPan endAutoPan  
MouseDown MouseDown  
MouseUp MouseUp  
MouseClick MouseClick  
MouseDoubleClick MouseDoubleClick  
MouseOver MouseOver  
MouseOut MouseOut  
KeyDown KeyDown  
KeyUp KeyUp  
onEndGeoCode openExpanded  
polyLineAdded closeExpanded  
polyLineRemoved openSmartWindow  
endMapDraw closeSmartWindow  

The Yahoo Map API uses the YEvent class in much the same way that the Google API exposes the GEvent class. The YEvent class has a single static method, YEvent.Capture, for registering an event handler.

YEvent.Capture(object:[YMap|YMarker], event:EventsList, callback:Function, privateObject:Object)

As with the GEvent.addListener method, the YEvent.Capture method takes a reference to either a YMap instance or a YMarker instance, the event to listen for, and a reference to the function to call when the event is fired. In addition, the Capture method also accepts an optional object reference that will be passed to the handler when it is fired. Another difference is that the Yahoo Maps API lists all known event types as part of the EventsList object. The full list of available events can be retrieved by calling the YMap.getEventsList() method. The event list can also be found in the documentation for the YEvent class.

Unlike the Bing and Google maps API's, this is the only method for dealing with events. There is no way to deregister your handler code. Even worse, the Yahoo Maps documentation is silent on what sort of parameters are passed to the event handler for each event type. After some trial and error and snooping through the example code, I've been able to sort out a bit of the story, but it would be much better if Yahoo would make this more clear in their documentation. An event will send either one or two parameters to the event handler. The first parameter contains information on the source of the event (the YMap or the YMarker).

  • YGeoPoint
    • Lat
    • Lon
  • thisObj
  • zoomObj
    • current
    • previous

The YGeoPoint is the latitude and longitude as it relates to the event. If this is a click event, it is the location of the click. The second optional parameter has two properties, Lat and Lon, indicating the position of the mouse. As this is not documented, I can't guarantee this will be the behavior for all events, so be sure to experiment with what values you get back in your event handler.

function yahooEventHandler( eventDetails, clickDetails )
 var eventLog = document.getElementById('yahooEventLog');
 var logEntry = 'Event Received:\n';
 if (eventDetails != null)
  if (eventDetails.thisObj == yahooMap5)
   logEntry = logEntry + '\Source:\tyahooMap5\n';
  if (eventDetails.thisObj == yahooMarker5)
   logEntry = logEntry + '\Source:\tyahooMap5\n';

  if (eventDetails.YGeoPoint != null)
   logEntry = logEntry + '\tLattitude:\t' + eventDetails.YGeoPoint.Lat + '\n';
   logEntry = logEntry + '\tLongitude:\t' + eventDetails.YGeoPoint.Lon + '\n';
  if (eventDetails.zoomObj != null)
   logEntry = logEntry + '\tZoom Before:\t' + eventDetails.zoomObj.previous + '\n';
   logEntry = logEntry + '\tZoom After:\t' + eventDetails.zoomObj.current + '\n';
 if (clickDetails != null)
  logEntry = logEntry + '\tMouse Lattitude:\t' + clickDetails.Lat + '\n';
  logEntry = logEntry + '\tMouse Longitude:\t' + clickDetails.Lon + '\n';
 eventLog.value = logEntry + eventLog.value;

As with the Google event handling, it can be very difficult to determine just what type of event triggered the call. This makes creating a centralized event handler a bit of a chore, so you'll want to create a unique handler for each event type of you want different behavior for each type of event.


Interact with the map to see a log of the events that are received.
click click
dblclick dblclick
mouseup mouseup
mousedown mousedown
dragstart mouseover
dragend mouseout

Hey, we've got our friendly MapQuest map back! Like the Yahoo and Google mapping APIs, the MapQuest API uses a class with static members for hooking up event handlers: MQA.EventManager. The MQA.EventManager has three methods:

  • MQA.EventManager.addListener(source:Object, eventType:String, handler:Function, target:Unknown)
  • MQA.EventManager.removeListener(source:Object, eventType:String, handler:Function, target:Unknown)
  • MQA.EventManager.clearListeners(source:Object, eventType:String)

When the event handler is called it receives a single parameter of type MQA.Event. Unfortunately, the properties of this class are all set based on the type of event that was fired. Couple that with a lack of documentation surrounding events, and we're back in the same position we were with the Yahoo mapping API. The best source of information I have found is in the MapQuest JavaScript API Developer's Guide, section 13 Custom Events. At the end of this section is a list of the events available for map, POI, overlay, and other mapping element types. Interpretting this documentation, we find that the MQA.Event object has the potential to contain the following data (although not all properties will always be populated, so be sure to check for null):

  • MQA.Event.eventName
    • Name of the event. This will always be present.
  • MQA.Event.button
    • Button associated with the mouse event.
  • MQA.Event.domEvent
    • Name of the DOM event correlating to this event.
  • MQA.Event.poi
    • Reference to the MQA.Poi relevant to this event.
  • MQA.Event.xy
    • Map X,Y coordinates for this event (such as a mouse click).
  • MQA.Event.ll
    • Map Lattitude and Longitude for this event.
  • MQA.Event.srcObject
    • Source of the event.
  • MQA.Event.prevZoom
    • Previous zoom level.
  • MQA.Event.zoom
    • Current zoom level.
  • MQA.Event.clientX
    • X coordinate within the visible portion of the page.
  • MQA.Event.clientY
    • Y coordinate within the visible portion of the page.
  • MQA.Event.dragPercentage
    • (guessing) percentage of the map that was dragged(?)
  • MQA.Event.dragDirection
    • (guessing) Direction the map was dragged(?)
  • MQA.Event.prevMapType
    • MapType in use prior to change in map type.
  • MQA.Event.mapType
    • MapType currently in use.

Using this in code, we can look for data in our event handler.

function mapquestEventHandler(eventDetails)
 var eventLog = document.getElementById('mapquestEventLog');
 var logEntry = 'Event Received:\n';
 if (eventDetails != null)
  if ( eventDetails.eventName != null )
   logEntry = logEntry + '\teventName:\t' + eventDetails.eventName + '\n';
  if ( eventDetails.button != null )
   logEntry = logEntry + '\tbutton:\t' + eventDetails.button + '\n';
  if ( eventDetails.domEvent != null )
   logEntry = logEntry + '\tdomEvent:\t' + eventDetails.domEvent + '\n';
  if ( eventDetails.poi != null )
   logEntry = logEntry + '\tpoi:\t' + eventDetails.poi + '\n';
  if ( eventDetails.xy != null )
   logEntry = logEntry + '\txy:\t' + eventDetails.xy + '\n';
  if ( eventDetails.ll != null )
   logEntry = logEntry + '\tll:\t' + eventDetails.ll + '\n';
  if ( eventDetails.srcObject != null )
   logEntry = logEntry + '\tsrcObject:\t' + eventDetails.srcObject + '\n';
  if ( eventDetails.prevZoom != null )
   logEntry = logEntry + '\tprevZoom:\t' + eventDetails.prevZoom + '\n';
  if ( eventDetails.zoom != null )
   logEntry = logEntry + '\tzoom:\t' + eventDetails.zoom + '\n';
  if ( eventDetails.clientX != null )
   logEntry = logEntry + '\tclientX:\t' + eventDetails.clientX + '\n';
  if ( eventDetails.clientY != null )
   logEntry = logEntry + '\tclientY:\t' + eventDetails.clientY + '\n';
  if ( eventDetails.dragPercentage != null )
   logEntry = logEntry + '\tdragPercentage:\t' + eventDetails.dragPercentage + '\n';
  if ( eventDetails.dragDirection != null )
   logEntry = logEntry + '\tdragDirection:\t' + eventDetails.dragDirection + '\n';
  if ( eventDetails.prevMapType != null )
   logEntry = logEntry + '\tprevMapType:\t' + eventDetails.prevMapType + '\n';
  if ( eventDetails.mapType != null )
   logEntry = logEntry + '\tmapType:\t' + eventDetails.mapType + '\n';
 eventLog.value = logEntry + eventLog.value;

With the Google and Yahoo map event handlers, we couldn't tell which event had been fired. The MapQuest API will always include the event type in the MQA.Event parameter that is passed to the event handling function. If your goal is to have an event logger, this can be very helpful.

Is That It?

While each of the APIs have some similar elements regarding event handling, what became most evident is the quality fo the documentation for each of the mapping APIs. The Yahoo mapping API is simply no help at all when trying to figure out how to handle events. Unless you are only interested in presenting static map information, this could be a real drawback when attempting to create an interactive site. The same is largely true of the MapQuest documentation. The Bing and Google mapping documentation is quite good, and each provides a slightly different method for hooking up events.

With this post we've covered all of the basics for getting a map on your page, displaying some useful information, and allowing for rich interactivity. Now it is time to get creative. In the next post I'll show you how to do some custom drawing which allows you to some very interesting things when presenting information. As always, if you are eager to dive in and learn more, visit the vendor's development site for full documentation.

No comments:

Post a Comment