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
trueis returned we allow the dynamic loader to do it’s work; when that is complete it callsCALLER.CREATE_MAP. - If
trueisn’t returned, we assume everything is good to go and the caller directly callsCALLER.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;
}
Another awesome article – great work!
[...] Mapping APIs are dynamically loaded [...]
Thank you!
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’);
…
}
Just got a report from someone sawing that the solution for Bing Maps causes the compass for panning to stop working.