Bing Maps Concepts
(formerly Virtual Earth)
Mike Garza   
VEMaps@garzilla.net   

   
Concept: Dynamic Info Box

In this example, we will look at how you can dynamically load content into the info box of a push pin. There may be scenarios where you want to limit the amount of data that is used when building out the map. There may be a lot of push pins being displayed and you don’t want to carry around all of the content that may not be needed. It may also be the case that the content is of a dynamic nature and you want to display the most up to date information. Loading the content of the info box “on-demand” could help address some of these scenarios.

In the example to the right, some push pins have been randomly added to the map. When you mouse over a given push pin, the info box is displayed. Default content has been added to each push pin to indicate that the content is being loaded and after a short, intentional, delay, the updated content is displayed. (make sure you keep your mouse over the push pin until the content is loaded to get the full effect.). Once the content is loaded, the push pin is updated so that subsequent displays of the info box use the “cached” content.


Walk Through:

This example uses an AJAX (Asynchronous JavaScript And XML) type approach to update the content in the info box. I mention an “AJAX type” approach because conceptually it is similar, but I skipped the XML part and used straight HTML. I guess that make it an AJAH (Asynchronous JavaScript and HTML) approach, but I would not count on this becoming a standard. :-)

AJAX is a very effective way of introducing data into a given web page. There is a lot of information out there on the web about AJAX, so we won’t go into detail here. The decision was made to forego XML and use HTML to save on additional processing. Knowing that the end result of the async call was going be HTML, the process building an XML document and parsing out that XML document to turn it into HTML didn’t seem necessary.

Building the map – There is nothing special about the creation of this map. It is pretty standard, actually. The one item to note is the addition of an event handler that is needed to trigger the AJAX call. The implementation of that event handler is as follows:
    ...
    map.AttachEvent("onmouseover", mouseOverHandler);
    ...
    
Here the onmouseover event, raise by the map, is attached to the function mouseOverHandler. This handler is needed to manage the AJAX calls used for updating the push pin content. When a user moves the mouse over a given push pin, this event will fire and the appropriate code can be executed. The mouseOverHandler function will be discussed a little further in this article.

Once the map has been created, five push pins are added to random locations on the map. The code for this is not really relevant to this example, except for one part. It is just a means of establishing a series of push pins to illustrate the other features of this example. Feel free to examine the source code of the load() function if you want more detail on how the push pins are created.

That being said, there is one important note about the creation of the push pins. For each of the push pins, the description is not set and contains empty string/null by default. This value (or lack thereof) is the determining factor in whether or not the AJAX process is invoked to load the info box content. Again, this will be discussed further in this aticle.

Loading the content – As previously mentioned, the mouseOverHandler function will manage the process of dynamically loading the content for the info box. Every time the user moves the mouse over a push pin this function is executed. The code for this function is as follows:
    function mouseOverHandler(e){
        if (e.elementID){
            shape = map.GetShapeByID(e.elementID);
            if (!shape.GetDescription()){
                shape.SetDescription('<span id="' + shape.GetID() + '_Content">
                                    <img src="images/progress_loading.gif" alt="" align="absmiddle" /> Loading data....
                                    </span>');
                url = 'SampleData.aspx?id=' + shape.GetID();
                req = GetRequestObject();
                if(req) {
                    req.onreadystatechange = function(){
	                    if(req.readyState == 4){
		                    document.getElementById(shape.GetID() + '_Content').innerHTML = req.responseText;
		                    shape.SetDescription(req.responseText);
	                    }
                    }

                req.open("get", url, true);
                req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                req.send(null);
    	        
                } else {
                    alert('Cannot load XMLHTTP object');
                } 
            }
        }
    }
    
When this function is invoked by the onmouseover event, the event object, e, is passed to the function. The elementID property is tested to ensure that there is an object associated with this event. If there is, then the value of this property will contain the ID for that object, otherwise the value will be NULL.

Once we have a valid ID, an instance of the push pin associated with the event is created. The ID contained the elementID property of the event object, e, is passed to the GetShapeById() method in the VEMap object, map. This methods returns an instance of a VEShape object that corresponds to the ID provided. This VEShape object is assigned to the variable “shape”.

It was mentioned previous that not having a description for a given push pin was important to the dynamic data load process. Here is when it comes into play:
    ...
    if (!shape.GetDescription()){
    ...
    
The GetDescription() method on the VEShape object, shape, returns a string containing the content to be displayed in the info box. If no value has been set for the description, then the default value is NULL. If the value for the description of the VEShape object, shape is NULL, that is the cue that indicates the no content has been loaded and the process should continue. If the value for the description of VEShape object, shape, is not NULL, then content has been loaded previous and there is not need to execute any code.

When it has been determined that the content for the push pin needs to be loaded, place holder content is added to the push pin. This content serves two purposes. First, it is a visual cue to the user that content is being loaded. Second, it serves as a container to inject the content once it is returned to the browser. The implementation of the place holder content is as follows:
    ...
    shape.SetDescription('<span id="' + shape.GetID() + '_Content">
                        <img src="images/progress_loading.gif" alt="" align="absmiddle" /> Loading data....
                        </span>');
    ...
    
The place holder content consists of a SPAN tag that contains and image and some text. The image is an animated icon and the text simply stated “Loading…”. A predictable ID is created for the SPAN tag. This ID is the ID of the push pin with the text “_Content” appended to it. The GetID() method in the VEShape object, shape, returns the ID of the push pin being used. This ID is needed to identify the container that the dynamic content will be injected into later in the process.

Next the location of the where the dynamic content is coming from is established. The variable “url” will contain the location (URL) of the source of the data to be put into the info box. This variable is established as follows:
    ...
    url = 'SampleData.aspx?id=' + shape.GetID();
    ...    
    
In this example the source of the data is an aspx page named “SampleData.aspx”. Additionally a query string parameter named “id” is also included in the [relative] URL. This parameter is a means of illustrating how data could be passed to a data source to determine what content should be loaded. In this example the ID that is being passed will just be returned as part of the content, but as you can imagine, these parameters could serve other purposed.

Why an aspx web form and not an asmx web service? Glad you asked! Web services, even when they are returning just a string, always return XML data. Since we did not want to go through the effort of parsing out XML, it eliminated the use of a web service. Since a web form (aspx) will return HTML, which is essentially the formatted text we need, it made more sense to use a web form to return the content. That being said, should you decide to parse out XML, a web service can be used to serve up the data. Note: If XML is going to be used as the data transport, the responseXML property should be used instead of responseText. responseXML contains an XML Document object.

Now that we know where to get the data from, the next step is to go and get it. The data is going to be fetched via an XMLHTTP object. An instance of a request object is created as follows:
    ...
    req = GetRequestObject();
    ...
    function GetRequestObject(){
        req = false;
        
        // branch for native XMLHttpRequest object
        if(window.XMLHttpRequest) {
	        try {
		        req = new XMLHttpRequest();
	        } catch(e) {
		        req = false;
	        }
        // branch for IE/Windows ActiveX version
        } else if(window.ActiveXObject) {
	        try {
		        req = new ActiveXObject("Msxml2.XMLHTTP");
	        } catch(e) {
		        try {
			        req = new ActiveXObject("Microsoft.XMLHTTP");
		        } catch(e) {
			        req = false;
		        }
	        }
        }
        return req;
    }
    ...
    
The variable “req” is set to an instance of the XMLHTTP object returned from the GetRequestObject() function. I should point out that the GetRquestObject function is not something that I wrote. This is a routine that I came across on the net some time ago. My apologies, but I don’t recall exactly where I got it from. The general concept behind this code is that a variety of approaches are tried in order the get a valid instance of an XMLHTTP object. If one cannot be established then a NULL value is returned. I am sure there are a few different approaches that could be taken to accomplish this. This is just one approach that has been working for me.

If a valid request object is returned, the request can be made, otherwise an alert is presented to the user indicting that there is an issue. If the request can be made, then the following code is executed:
    ...
    if(req) {
        req.onreadystatechange = function(){
            if(req.readyState == 4){
                document.getElementById(shape.GetID() + '_Content').innerHTML = req.responseText;
                shape.SetDescription(req.responseText);
            }
        }

    req.open("get", url, true);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    req.send(null);
    ...
    
First the “onreadystatechange” event in the request object is configured with a JavaScript function. Every time the “readyState” property of the request object, req, changes, this function will be executed. The following are possible values for the readyState propery:

  • 0: Uninitialized - open() has not been called yet.
  • 1: Loading - send() has not been called yet.
  • 2: Loaded - send() has been called, headers and status are available.
  • 3: Interactive - Downloading, responseText holds the partial data.
  • 4: Completed - Finished with all operations.
The onreadystatechange event contains a function that will test the value readyState property. Once the readyState property value is 4, which indicates the request is complete, the remainder of the code is executed.

The remaining lines of code in this function deal with updating the content in the info box. Previously a place holder container was added to the info box and assigned a predicable ID. The container will be updated with the content that is returned from the request. The innerHTML property of the div container is updated with the value from the responseText property of the request object, req. This property contains the HTML response from the request. Next the same responseText property is used to update the description of the push pin. The is accomplished by passing the responseText property to the SetDescription() method in the VEShape object, shape. Now that the description is set, this will prevent any subsequent requests for this push pin.

The last few lines in this section work with the request object, req, to process the request. The request is opened, the content type is set and the request is send. The thing to note here is that this whole process is asynchronous. The onreadystatechange event was wired with a function early in the process, but that function is actually not executed until after the request is sent and control is returned to the client.

Wrap-up:

The previous was an overview of how dynamic content could be used for the info box of the push pins on a map. The use of dynamic content could help with performance issues cause by large amount of data or aide in the process of presenting real time data in the info box.

AJAX (or AJAH in this case) along with the available events in the Virtual Earth API are the prevalent technologies used to implement this process.

I wanted to take this opportunity to mention that this example was actually a request that I got via email. Thanks for the suggestion (you know who you are) and keep the suggestions coming.

Any feedback, comments or questions are welcome.

Return to Home Page

©2007-2017, Mike Garza, All Rights Reserved