Introduction

This is the third article in a three article series examining a custom ASP.NET server control I developed to make using the Google Maps API easier for .NET developers. This is a technical article which does not focus on the usage of the Google Maps .NET control. If you want to see what this baby can do, check out Part 1 and Part 2. This article assumes you are familiar with the Google Maps API. You may see references throughout the article to "my control, my GoogleMaps control, my GMap control, etc". I did not create the Google Maps API; I merely created a wrapper for ASP.NET using C#, XML and XSL.

The main goal of this article is to show you how I created the Google Maps .NET control; the design decisions I made, the technology used, and the tools involved.

The server control

I spent a lot of time thoroughly documenting the code for GMap.cs. Rather than go through it line-by-line and re-type my documentation, I will highlight the portions that are the magic behind the GMap. The GMap control contains a lot of advanced material which can be difficult to conceptualize merely by reading. I encourage you to look at Part 2 of this article series as you are reading through this material. Part 1 and Part 2 are the "what" and Part 3 is the "how." There is a team of monkeys working around the clock trying to figure out the "why."

First things first

The first step in creating the GMap control was to re-create all of the Google Maps API JavaScript objects in C#. If you download the code (see Download source files above) and look in the WCPierce.Web project, drill down through the folders until you come to GoogleMap. In this folder you will see one code file for every Google Maps API object. GIcon, GMarker, GPoint, GSize, etc. Each one recreated to exactly mimic the functionality of their JavaScript counterpart. If you look closely at anyone of these objects, you'll notice they are decorated with System.Xml.Serialization attributes. Those will come in handy when we want to convert from C# to XML (more on this later). Those Rolla guys have a good article on XML Serialization if you want to learn more.

Why go through the trouble of re-creating the JavaScript functionality in C#? So that you can write clean looking Google Maps code in the .NET language of your choice. The sample below shows how to create some points and markers and add them to a map:

Public methods

Each of the public methods from the Google Maps API is re-created in the GMap control. Each method takes the provided parameters and creates the JavaScript code required to execute the method on client-side. This may seem a little odd: server-side code is creating client side code. But the real goal is for the code to be executed on client side. The examples below show the creation of the proper JavaScript:

The control's JavaScript ID, this.JsId, is created by the control to be unique. This allows multiple GMap controls to be manipulated on a page. The generated JavaScript code is added to the StringBuilderinitJs and it is also the return value of each method call. This allows the developer to use the control to initialize a GMap (in a Page_Load handler for example) as well as make method calls during a client callback (more on this later).

CreateChildControls()

In the CreateChildControls() method we add four HtmlInputHidden controls:

These controls allow the GMap control to capture the CenterLatLng, SpanLatLng, BoundsLatLng, and ZoomLevel values from a GMap during a postback. For example, let's say you added a GMap to your page and an asp:button. The user then moves the map around, perhaps zooms in/out, and then clicks your button. In the event handler for your button you could retrieve the center coordinate of the map and the current zoom level of the map based on its position when the user clicked the button.

OnDataBinding()

The GMap control allows developers to bind data from various sources (custom business objects, ArrayList, DataSet, etc.) to automatically create markers on the map. The developer specifies the following fields to implement databinding: DataLatitudeField, DataLongitudeField, DataMarkerIdField, and DataIconIdField. Latitude and Longitude fields are required. If the developer does not specify the MarkerId and IconId fields, markers are created with no ID and the default (red) icon. Developers can now easily add markers to a map using code like this:

OnDataBinding() takes the bound DataSource and iterates through the records looking for the fields specified and creating a GMarker for every item.

Render()

Render() is usually the busy method for complex server controls (like the GMap). However, I decided to do something clever. All of the objects that make up the server-side GMap (GIcons, GMarkers, GPoints, GPolyines, etc.) are converted to XML using standard .NET XML serialization. Remember the serialization attributes I pointed out at the beginning of the article? That combined with the built in functionality of the framework allows us to easily generate XML from .NET objects.

RaisePostBackEvent()

I created the GMap to support five events: Click, Zoom, Move Start, Move End, and Marker Click. These are server-side events that respond to client callbacks. If you're a little hazy on client callbacks, check out my article series AJAX Was Here. What it all boils down to is running server-side code as a result of client side events. With some custom JavaScript, we send the GMap control the event we would like to be raised along with any event arguments. RaisePostBackEvent() parses the event request and raises the proper server-side event which then runs the developer's code.

This can be really difficult to conceptualize so I encourage you to look at Part 2 of this article series as you are reading through this material.

LoadPostData()

The final point of interest in the GMap control is the LoadPostData() method. This method is called during a postback so that custom controls can retrieve data from the posted form. Here we are grabbing the values from the HtmlInputHidden controls we added during CreateChildControls(). We grab these values and save them to member variables so that developers can access when needed.

The XSL

Before I launch in to an explanation of GMap.xsl, I want to provide a few soap box remarks. Before working on the GMap, I hadn't used XSL. I thought it would be neat to try it out and decided to use it for a few reasons:

The Google Maps API is still evolving. If Google decided to make a change to the API, I needed an easy way to tweak the JavaScript generated by my control. XSL seemed like the answer.

I wanted to learn something new and see what the big deal was about XSL.

I wanted to attract a larger audience by promoting my article with the XSL buzz word.

Now that I have actually used XSL in a project I've come to this conclusion: XSL is the Devil. I wasn't sure if I was the only one with this sentiment so I did a little digging and came across this great article by Michael Leventhal titled XSL Considered Harmful. I thought Michael made some excellent points many of which I came across during my development. But considering the article was six years old and we still use XSL today, I guess the sadists have won out in preserving XSL as a viable development tool. But, time to come off the soap box.

This XML snippet is not to be confused with the original/current XML that the Google Maps natively understands. The reason I decided to do my own thing is because there is no formal documentation for the Google Maps XML format and I didn't want to commit to something that may change in the near future. The goal now is to take the XML and transform it into the JavaScript necessary to create a Google Map. This is accomplished by the XSL stylesheet I created named GMap.xsl.

Some of the data used to create the Google Map is not found in the XML file. A number of values are passed into the XSL stylesheet as parameters. The XML only contains map data like markers, icons, polylines, controls, etc. GMap.xsl starts off by specifying an output of text (rather than HTML) and declaring a number of parameters that should be passed to the stylesheet. The values can be accessed by developers using the GetXsltArguments() method of a GMap.

The next portion of the stylesheet takes the parameters provided and uses them to create and initialize a Google Map using the proper API values. Any methods called during the creation of the GMap (CenterAndZoom(), OpenInfoWindow(), etc.) are added to the InitJS member variable. InitJS contains the JavaScript required to make the client-side call to the desired Google API method (See Public methods above). The actual map initialization code is placed inside a method, which is later called after the window finishes loading. The reason for this is to avoid getting an "Operation Aborted" error in Internet Explorer.

If the developer enabled client call backs on the GMap control, the five server side callback events Click, Marker Click, Zoom, Move Start, and Move End are attached to the GMap. These events are named GMap_Server<Event Name> (more on this in The JavaScript section below).

A number of default events are "hooked up" to the Google Map. Rather than have the developer specify which JavaScript functions fulfill which events, default events named GMap_<Event Name> are attached to the Google Map. It is up to the developer to create these JavaScript functions if she wants to respond to the events. These events are all optional. This is accomplished by a little JavaScript hackery. If an event named GMap_Click is not found on the page or in a linked JavaScript file, an empty function _ef(), is used in its place. So, if you code it, it will run.

The remainder of GMap.xsl is not as scary as it looks. Each map object is created with a corresponding XSL template. Each template creates the JavaScript necessary to create the Google Maps API object for the map. Default and client side callback events are also added to markers using the same naming conventions for the map as shown below:

The JavaScript

The client callbacks and the postback data features of the GMap are encapsulated in the GMapX.js file found in the downloadable code. Most of the callback plumbing was built using my CallBackObject examined in another set of articles. Most of the other methods will fill in the blanks left up to this portion of the article.

The first few lines create some global variables used whenever a GMap is added to the page. There is also some code to add a method to the Google Maps API GMap object. getOverlayById() allows the developer to retrieve an overlay by a previously assigned string identifier. The next two lines add cross-browser support for attaching an event handler to a DOM object.

Next we'll skip to the GMap_Server<Event Name> functions. Each function builds an argument string including the server-side event name that should be raised and the event arguments that go with it in {event}|{argument1},{argument2}...{argumentN} form.

Remember how the events were bound to the GMap in the XML stylesheet? When these events are fired this represents the GMap causing the event (except for GMap_MarkerClick where this represents the marker being clicked). Using this, we can gather the required parameters to be sent back to the server to execute the server side event.

After the event argument is built, __DoCallBack() is called passing this (the GMap) and the arguments. __DoCallBack() constructs a new CallBackObject, assigns the OnComplete() and OnError() delegates, copies the state of the GMap with a call to GMap_SaveState(), and then performs the asynchronous request back to the server with cbo.DoCallBack().

The interesting thing to note here is the assignment of the OnComplete() delegate.

The assignment is made using an anonymous function.

The function calls the apply() method on the cbo_Complete function passing eventTarget (which is the GMap or GMarker being acted upon)

Why do this rather than simply assign the cbo_Complete function? When the server returns its response from the callback, it will execute the cbo_Complete function. The cbo_Complete function simply interprets and executes the return value from the server. The return value should be JavaScript that the developer wants executed client side. The developer can now use this in her returned code to act upon the GMap or GMarker that raised the event. The State Quarters example from Part 2 makes use of this feature by calling the OpenInfoWindow() method of the GMarker representing a state.

Finally, GMap_SaveState() gets a reference to the GMap in question either through this or by the passed eventTarget. The code then gets reference to the HtmlInputHidden elements rendered by the GMap and updates them with the current map state values. Remember, this method is called before a client callback and just before the main form is submitted to the server as the result of a submit button click.

Conclusion

Did that seem overly complicated to anyone else? Now that you've read through the article aren't you glad I encapsulated all that in a neat little server control? I'd love feedback from anyone who suffered through reading the entire article. Please let me know via the forum below which areas you felt were covered adequately and which features you feel need a little more explanation.

I recently got a copy of VS 2005 Beta 2 and have been spending a lot of time with Microsoft Atlas (hence the delay in publishing this article). Stay tuned for something in the near future on Atlas, Virtual Earth, and Markup Maps.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.