Master/Detail with ASP.NET AJAX and the MS Virtual Earth Control
I was recently working on a research project with the purpose of exploring how ASP.NET AJAX could be combined with Microsoft's Virtual Earth Map Control to implement the common master - details UI pattern. Our application allows users to browse zip-codes using the Virtual Earth Map Control and we thought it would be useful to display details for the zip-code as it was being hovered over.
While investigating this I created a prototype web application that does the following:
- Renders purple polygons on the VEMap surface representing the following 3 zip-codes: 32224, 32246, and 32256.
- When the user hovers over the polygons, 2 things happen:
- The border of the polygon changes to a thicker width. This is a visual cue meant to inform the user of their action
- The information displayed in the table to the right of the map updates. This table displays some high-level demographic and weather statistics.
Here is a brief explanation of how I accomplished this. The example uses PageMethods to fetch the latitude and longitudes for the zip-codes. If you are not familiar with PageMethods you should Google it to find out more, or read about it here.
Load the Map
First I added a HTML div element to the page that will contain the map.
<div id="myMap" class="map" />
Following closely to the MSDN documentation, I named the div myMap and provided a css class for controlling height and width. Next I added a javascript function for handling the ASP.NET AJAX pageLoad client side event. There are 2 points here to be aware of:
- You don't have to explicitly subscribe to the client side pageLoad function. Just like the server side AutoEventWireup, as long as you add a function named pageLoad to the page, the ASP.NET AJAX framework will find it
- pageLoad is invoked on both full and partial postbacks. So if you need to conditionally execute logic based on the type of postback you can use the isPartialLoad property of the event args object.
With this in mind, here is the javascript pageLoad function. You will notice 2 other items here as well. The first is that we are also using the AttachEvent method on the VEMap to handle the onmouseover and onmouseout events. The second is that we are using the PageMethods proxy to invoke the GetZipCodes page method. We will look at these items next.
function pageLoad(sender, args){ if(!args.get_isPartialLoad()){ // create the map map = new VEMap('myMap'); // set the dashboard size to tiny so it doesn't get in the way map.SetDashboardSize(VEDashboardSize.Tiny); // load the map map.LoadMap(); // attach to the events we care about map.AttachEvent("onmouseover", onMouseOver); map.AttachEvent("onmouseout", onMouseOut); // add the customers to the map PageMethods.GetZipCodes(plotZipCodes); } }
Handle the Map Events to Provide Visual Cues
Next, we will implement the onmouseove and onmouseout event handlers so we can give the user a nice visual cue of their action. The argument passed the the event handlers can be used to lookup the VEShape object that fired the event. In the onmouseover handler we will use this to find the currently selected shape and increase its border width. In the onmouseout handler we will revert it back to its original value. Here are the handlers that do this ...
function onMouseOver(e){ if(e.elementID){ // fetch the polgon we are moused over var polygon = map.GetShapeByID(e.elementID); // increase its width polygon.SetLineWidth(2); } } function onMouseOut(e){ if(e.elementID){ // fetch the polgon we are moused over var polygon = map.GetShapeByID(e.elementID); // restore its width polygon.SetLineWidth(1); } }
Plot the Zip-Codes
Now we are ready to add the code that handles adding the VEShape's to the VEMap. As shown earlier, the pageLoad method invokes the GetZipCodes page method. This is done asynchronously and when execution completes the plotZipCodes javascript function is invoked. This handler is responsible for parsing the resulting data stream and picking out the latitude/longitude pairs that make up each of the three zip-codes. Once the latitude and longitude pairs are extracted, a new VEShape object is added to the map. The majority of the code in this function handles the parsing. After the latitude and longitudes are extracted, a VEPolygon is created from the coordinates and added to the VEMap.
function plotZipCodes(result){ if(result && result.length > 0){ // split the zipCodes var zipCodes = result.split('END'); // keep track of all of the latlongs // so we can make sure to set the zoom // correctly var veLatLongs = new Array(); // parse out the lat/longs for each of the zip-codes for(index = 0; index < zipCodes.length; index++){ var zipCodeSegments = zipCodes[index].split('|'); var zipCode; var zipCodeBounds = new Array(zipCodeSegments.length - 1); for(var i = 0; i < zipCodeSegments.length; i++){ if(i == 0){ // this is the zipcode id zipCode = zipCodeSegments[i]; } else{ // this is a latlon pair var lat = zipCodeSegments[i].split(',')[0]; var lon = zipCodeSegments[i].split(',')[1]; zipCodeBounds[i - 1] = new VELatLong(lat, lon); } } // create the polygon var polygon = new VEShape(VEShapeType.Polygon, zipCodeBounds); // set the colors polygon.SetFillColor(new VEColor(0,0,255,.2)); polygon.SetLineColor(new VEColor(0,0,255,1)); polygon.SetTitle(zipCode); polygon.SetLineWidth(1); // don't display the shapes icon polygon.HideIcon(); // add our new polygon map.AddShape(polygon); // keep track of all of the bounds so we can // set the view correctly veLatLongs = veLatLongs.concat(zipCodeBounds); } // set the view to these points map.SetMapView(veLatLongs); } }
Display Zip-Code Details
Finally, the last step. As mentioned earlier I want to display demographic details for the zip-code the user is currently selected. I am doing this by adding a DetailsView to the page embedded in an UpdatePanel. I have setup an AsyncPostBackTrigger for the UpdatePanel. This trigger is an asp:Button with its style set to hidden so it is not displayed on the screen - I will use this as a hook to cause the UpdatePanel to refresh when the use mousesover a polygon by forcing a postback using the buttons id. To do this I need to update the onMouseOver handler as follows ...
function onMouseOver(e){ if(e.elementID){ // fetch the polgon we are moused over var polygon = map.GetShapeByID(e.elementID); // increase its width polygon.SetLineWidth(2); // get the value from the title $get('<%= this.hdnZipCode.ClientID %>').value = polygon.GetTitle(); // get the id of the element // force the update panel to update __doPostBack('<%= this.btnUpdateZipCode.ClientID %>', ''); } }
That's it. Enjoy!
Comments
Hey Matt,
Great blog...Quick question have you had any luck getting vs2008 jscript intellesense to work with the virtual earth?
Great post! I just looked at some of your other posts and I think I going to have to subscribe to your feed.
On a related note to this post, You might be interested that there is an all new ASP.NET AJAX Virtual Earth Mapping Server Control that abstracts out all the JavaScript necessary when implementing Virtual Earth. This control is really cool! You can find it at: http://simplovation.com/Page/WebMapsVE.aspx