Friday, March 6, 2015

Automatically closing InfoWindows in Google Maps API v3 when using KML Layers

I had a client request related to the Google Maps API v3. The client is displaying a map that uses KML Layers to display additional markers and data. The map looks great. The issue was: each time you click on a marker, an InfoWindow opens. If you click on another marker, or on the map itself, the marker stays visible. You have to manually close each InfoWindow (using the little X in its upper left corner). The desired behavior was to have just one InfoWindow open at a time, and to automatically close that InfoWindow if the user clicked on the map (where there was no marker).

The solution that worked for the client was to have one single, global InfoWindow object. We could then make sure that only one InfoWindow was open, and we could close it whenever we wanted.

The challenge, though, was that the InfoWindows that appear for KML Layers seem to appear "by magic". It wasn't immediately obvious how to get a handle on the new InfoWindows when they appeared.

Fortunately, KMLLayer objects allow us to disable the default InfoWindows, and then listen to the "click" event so we can create them ourselves.

There's a great simple demo available that shows you all the pieces:
https://developers.google.com/maps/documentation/javascript/examples/layer-kml-features

In the above demo, you can see that, instead of opening an InfoWindow, the InfoWindow contents are displayed in a div to the right of the map.

Instead of displaying in a div, we can display in our own InfoWindow.

First, let's create a global variable for our InfoWindow object, and our map object.

// Using a global infowindow.  Each marker click simply changes this content
// and reopens it at a new location.
var _globalInfoWindow = new google.maps.InfoWindow();

// Global map object used as well.
var map;


Then, when adding your KML layer, you'll want to be sure to suppress the default InfoWindows using the suppressInfoWindows option:

    var kmlLayer = new google.maps.KmlLayer({
        url: 'http://kml-samples.googlecode.com/svn/trunk/kml/Placemark/placemark.kml',
        suppressInfoWindows: true,
        map: map
    });

Next, since we just suppressed the default InfoWindows, we'll need to add our own function to display InfoWindows. Note this function will receive a kmlEvent as a parameter:

// Function to create our own info windows.
// This is used simply to get a global handle on the info windows created
// using the KML data. We can use this handle to close the window later.
function handleInfoWindow(kmlEvent) {

    // Close any prior infowindow.
    _globalInfoWindow.close();

    // Set up the new info window based on the KML data.
    _globalInfoWindow = new google.maps.InfoWindow({
        content: kmlEvent.featureData.infoWindowHtml,
        position: kmlEvent.latLng
    });

    // Open (display) the info window within our global map.
    _globalInfoWindow.open(map);
}

Now the "magic" - we can add a listener to the KML Layer! This allows us to listen for the click event on all those magically-placed markers (defined within the KML data).

    
    google.maps.event.addListener(kmlLayer, 'click', handleInfoWindow);

With that all done, we can add a listener on the map itself to close info windows when the map is clicked:

   google.maps.event.addListener(map, "click", function(event) {  
        // If the map is clicked, close any potentially-open infowindow.
        _globalInfoWindow.close();
 });

Great! The InfoWindow appears when I click on the marker, and disappears when I click on the map.

Note that I can easily add more KML layers with the same functionality:

    var anotherKmlLayer = new google.maps.KmlLayer({
        url: 'http://somethingelse.com/whatever.kml',
        suppressInfoWindows: true,
        map: map
    });
    
    google.maps.event.addListener(anotherKmlLayer, 'click', handleInfoWindow);

Another interesting thing to note is that, although the InfoWindow disappears on click, it will linger if you drag the map.

Here's a demo:
http://jsfiddle.net/b02eoynv/1/





  
   - jsFiddle demo