David Janes' Code Weblog

November 8, 2008

How to dynamically load map APIs

html / javascript,maps · David Janes · 1:11 pm ·

When using various map APIs it’s often useful for performance reasons not to actually load the API until we’re sure we’re going to need it. This is called dynamic loading or lazy loading. The post explains how to do this on the three major mapping APIs.

Note about the examples

These have been excerpted from a project I am currently working on, and so don’t work as-is. The basic calling structure (not shown) is as follows:

  • assume each function is being called from CALLER.
  • If true is returned we allow the dynamic loader to do it’s work; when that is complete it calls CALLER.CREATE_MAP.
  • If true isn’t returned, we assume everything is good to go and the caller directly calls CALLER.CREATE_MAP – this will typically happen when the Map API is detected in Javascript!

There’s also a global context in GLOBALS used for keeping track of state. In particular, we assume the user may be trying to create multiple maps so we have to make sure all the CREATE_MAP callbacks are triggered once all the Javascript has been loaded!

In the Google Maps and Virtual Earth we see the Javascript-with-callback pattern:

  • a callback function is passed in the URL as a parameter
  • the downloaded Javascript document is dynamically modified so that it calls that function. Hence, you know the Javascript has been loaded.

If you’re a Javascript noob, scripts are dynamically loaded into your HTML document by creating a SCRIPT element and attaching it to the document HEAD.

Google Maps API

Google Maps API uses a multi-phase loading scheme:

  • get the generic loader, giving it a callback function to invoke when it’s completely loaded
  • when that’s loaded (into google)…
  • call the loader to get the maps API
  • when that’s loaded, create the map

This feature is documented here.

google_preload : function(CALLER) {
    if (window.GMap2) {
        return;
    }

    var api_key = YOUR_GOOGLE_API_KEY_FOR_YOUR_DOMAIN;

    var preloader = 'MapGooglePreloader_' + GLOBALS.MapFieldsNumber;
    GLOBALS[preloader] = function() {
        google.load("maps", "2", {
            "callback" : function() {
                CALLER.CREATE_MAP();
            }
        });
    }

    if (window.google) {
        GLOBALS[preloader]();
    } else {
        var script = document.createElement("script");
        script.src = "http://www.google.com/jsapi?key=" + api_key +
        "&callback=GLOBALS." + preloader;
        script.type = "text/javascript";

        document.getElementsByTagName("head")[0].appendChild(script);
    }

    return  true;
}

Microsoft Virtual Earth

The Virtual Earth API is easy to dynamically load also. As with Google this feature is directly supported through a Javascript callback mechanism; unlike Google it’s essentially a single phase design.

One issue I ran into was the error message p_elSource.attachEvent is not a function error. Although a Google search for a solution mostly turns up a blank, a tip of the hat to Soul Solutions in Australia for finding a work-around (and have similarly solved dynamic API loading at the link).

virtualearth_preload : function(CALLER) {
    if (window.VEMap) {
        return;
    }

    var preloader = 'MapVEPreloader_' + GLOBALS.MapFieldsNumber;
    GLOBALS[preloader] = function() {
        CALLER.CREATE_MAP();
    }

    if (!window.attachEvent) {
        var script = document.createElement("script");
        script.src = "http://dev.virtualearth.net/mapcontrol/v6.2/js/atlascompat.js";
        script.type = "text/javascript";

        document.getElementsByTagName("head")[0].appendChild(script);
    }

    var script = document.createElement("script");
    script.src = "http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2" +
      "&onScriptLoad=GLOBALS." + preloader;
    script.type = "text/javascript";

    document.getElementsByTagName("head")[0].appendChild(script);

    return  true;
}

Yahoo AJAX Map API

The Yahoo AJAX Map API does not directly support dynamic API loading. However, just loading the Javascript and running a timer that waits to see whether the script has loaded seems to do the trick. If you’re new-ish to Javascript, remember that it’s not multi-threaded so we can always be sure that when a Javascript file is loaded, it is completely loaded (excepting errors, of course).

yahoo_preload : function(CALLER) {
    if (window.YMap) {
        return;
    }

    var preloader = 'MapYahooPreloader_' + GLOBALS.MapFieldsNumber;
    if (!GLOBALS[preloader]) {
        GLOBALS[preloader] = 1;

        window.YMAPPID = YOUR_YAHOO_API_KEY;

        var script = document.createElement("script");
        script.src = "http://us.js2.yimg.com/us.js.yimg.com/lib/map/js/api/ymapapi_3_8_0_7.js";
        script.type = "text/javascript";

        document.getElementsByTagName("head")[0].appendChild(script);
    }

    window.setTimeout(function() {
        if (window.YMap) {
            CALLER.CREATE_MAP();
        } else {
            CALLER.yahoo_preload(CALLER);
        }
    }, 0.1);

    return  true;
}

5 comments

  1. Another awesome article – great work!

  2. […] Mapping APIs are dynamically loaded […]

  3. admin · 2008-11-10 11:53

    Thank you!

  4. Arian Hojat · 2010-02-23 10:40

    Saw your useful post. I made a function to use jquery to do almost same task, using lazy loading in the background instead of waiting 2-2.5seconds for the libs to load in (think your above example will load dynamically, but still take time since its not asynchronous and takes place in , right?) …

    function loadVirtualEarthLibs(onScriptLoadFunc){ //lazy-loading in background, pass onScriptLoad function name if needed

    if (window.VEMap){ return; } //already loaded, dont need to download/run script again

    var compat_script_url = “http://dev.virtualearth.net/mapcontrol/v6.2/js/atlascompat.js”;
    var main_script_url = “https://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&mkt=en-us&s=1″+ ((onScriptLoadFunc)? (“&onScriptLoad=”+onScriptLoadFunc) : “”);

    if (!(window.attachEvent)) {
    $.ajax({ type: “GET”, url: compat_script_url, success: function(){
    $.ajax({ type: “GET”, url: main_script_url, success: function(){ }, dataType: “script”, cache: true });
    }, dataType: “script”, cache: true });
    } else {
    $.ajax({ type: “GET”, url: main_script_url, success: function(){ }, dataType: “script”, cache: true });
    }

    }

    so i set that with jquery on document.ready…

    $(function(){
    loadVirtualEarthLibs();
    });

    And then later on when my “ajax questionnaire” is done, a Bing map is dynamically loaded …

    if (!window.VEMap) { // I make sure again when i actually need to use my library, if its available. if not, try to get it again (edge case/unlikely), otherwise just build the map. should be quick since library was loaded while you were reading the page.
    loadVirtualEarth(‘initMap’);
    } else {
    initMap();
    }

    … and the initMap is just initialization code for building the map

    function initMap()
    {
    try
    {
    map = new VEMap(‘map_canvas’);

    }

  5. Ricky Brundritt · 2010-05-21 11:40

    Just got a report from someone sawing that the solution for Bing Maps causes the compass for panning to stop working.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress