Google Maps callback not always triggering - javascript

Problem: It seems like when loading the Google Maps script, the callback is not performed. It might be related to how to load the Google Maps script.
Context: My website displays a Map in the frontpage when certain minimum viewport conditions are met. Specifically, I avoid loading it when I'm on mobile. I do not want to use jQuery :)
Implementation: The way I'm doing it is by first loading a small handcrafted script (google-map.js), which does a bunch of things and ends with:
function dynamicallyLoadScript(url) {
var script = document.createElement("script");
script.src = url;
script.type = "text/javascript";
script.defer = true;
document.head.appendChild(script);
};
// Load Google Maps when NOT on mobile
if ( ! mobile ) {
dynamicallyLoadScript('https://maps.googleapis.com/maps/api/js?key=wIzaScChUs5NxF3Z5LZoxkAS4wca9A7Pk53I024&callback=initMap');
}
(The JS key is fake).
Outcome: This actually works when I load the site for the first time. I see that google-map.js is loaded, the Google Maps script tag is added to the head, and both resources are loaded and working well. But sometimes (!) when I refresh, the map stops loading and upon inspection I see that the script tag was added successfully, and the resource is loaded, but the initMap() callback function hasn't triggered. If I go to the console, it does fire.
Since this happens only sometimes (and never upon a fresh load without cache), I think it might have something to do with a race condition or just with how cache works. But I'm not sure how to address it. Thoughts?

Solution in a nutshell: This is not a very elegant solution imho, but I figured that what I needed to create is some retry logic for loading initMap() when it runs before it should. In other words, I'm diagnosing this as a race condition present when cache is available.
Implementation: Added a small function to check every 0.1 seconds if the DOM has an element that only exists when the map is fully loaded. When so, the timer stops, but otherwise, it runs the initMap() function.
function checkMaps() {
if (document.getElementsByClassName("gm-style").length == 0) {
initMap();
} else {
clearInterval(checker);
}
}
checker = setInterval(checkMaps, 100);

Related

You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors. when called using $.getScript()

Prelog:
I have implemented Google maps and Geolocation as a independent widgets, Now the user have the ability to add the widget as much as he wants in a page he is owns.
I am using $.getScript(URl, callback) to load the script for that URL to work.
Problem :
When the user add both the widgets or the same widget multiple times in a same page , the check windows.google fails and the $.getScript(url, callback) gets executed twice. Due to which I get an error from the Google Script
You have included the Google Maps JavaScript API multiple times on
this page. This may cause unexpected errors. when called using
$.getScript()
if(window.google !== undefined && window.google !== null) {
onScriptLoad(null, null, 200);
} else {
$.getScript(googleUrl, onScriptLoad);
}
The above line exists in both the widget and both the widgets are independent of each other. It always goes into the else block of both the functions.
Looking forward for some work around here, like to load the script synchronously using javascript or jquery
when your code runs in the first widget, it initiates the loading of the google API, however window.google isn't created until the script completes loading. This is asynchronous
Now, the second widget tests if window.google exists, but this is still happening before the google API loads, therefore, it too thinks it needs to load google API
so, instead of this:
if(window.google !== undefined && window.google !== null) {
onScriptLoad(null, null, 200);
} else {
$.getScript(googleUrl, onScriptLoad);
}
try
window.loadingGoogleApi = window.loadingGoogleApi || $.getScript(googleUrl);
window.loadingGoogleApi.then(onScriptLoad);
the loadingGoogleApi can be any name you choose (just don't use something that will be clobbered by other code)

Including the Google Maps API multiple times warning on javascript/mvc

I've been getting the warning message
You have included the Google Maps API multiple times on this page.
This may cause unexpected errors.
Each time I open a different tab of my website, as each tab has its own map to show to the users.
The way I've made the code to call the google API was this:
function loadMapScript() {
var script = document.createElement("script");
script.src = "https://maps.google.com/maps/api/js";
script.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(script);
}
And I call it here:
$(document).ready(function () {
loadMapScript();
... ... ...
The website has various tabs and when each one of them opens, the script is called, hence why it's there multiple times, I got that far.
What I didn't get is how I stop this from happening, I've tried to perform a few verification's inside the loadMapScript function but they did not work at all. I'd like to know if someone knows a way to make this verification inside the loadMapScript function, to prevent it from adding the google API script more than once.
I faced the same type of problem. This occurs if your web page including maps api more than one time.
I have checked in my case there was a .js file that was also calling maps api, so please first check if you are including maps api more than one time, if you are, remove the one.
I figured it out, it required me to do the following verification, it was simpler than I thought:
var children = document.getElementsByTagName("head")[0].childNodes;
for (i = 0; i < children.length; i++) {
if (children[i].src == script.src) {
found = true;
break;
}
}
if (found == false) { document.getElementsByTagName("head")[0].appendChild(script); }
Thanks for the attempt to help at least

Why does the Segment.io loader script push method names/args onto a queue which seemingly gets overwritten?

I've been dissecting the following code snippet, which is used to asynchronously load the Segment.io analytics wrapper script:
// Create a queue, but don't obliterate an existing one!
var analytics = analytics || [];
// Define a method that will asynchronously load analytics.js from our CDN.
analytics.load = function(apiKey) {
// Create an async script element for analytics.js.
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = ('https:' === document.location.protocol ? 'https://' : 'http://') +
'd2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/' + apiKey + '/analytics.min.js';
// Find the first script element on the page and insert our script next to it.
var firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript);
// Define a factory that generates wrapper methods to push arrays of
// arguments onto our `analytics` queue, where the first element of the arrays
// is always the name of the analytics.js method itself (eg. `track`).
var methodFactory = function (type) {
return function () {
analytics.push([type].concat(Array.prototype.slice.call(arguments, 0)));
};
};
// Loop through analytics.js' methods and generate a wrapper method for each.
var methods = ['identify', 'track', 'trackLink', 'trackForm', 'trackClick',
'trackSubmit', 'pageview', 'ab', 'alias', 'ready'];
for (var i = 0; i < methods.length; i++) {
analytics[methods[i]] = methodFactory(methods[i]);
}
};
// Load analytics.js with your API key, which will automatically load all of the
// analytics integrations you've turned on for your account. Boosh!
analytics.load('MYAPIKEY');
It's well commented and I can see what it's doing, but I'm puzzled when it comes to the methodFactory function, which pushes details (method name and arguments) of any method calls made before the main analytics.js script has loaded onto the global analytics array.
This is all well and good, but then if/when the main script does load, it seemingly just overwrites the global analytics variable (see last line here), so all that data will be lost.
I see how this prevents script errors in a web page by stubbing out methods which don't exist yet, but I don't understand why the stubs can't just return an empty function:
var methods = ['identify', 'track', 'trackLink', 'trackForm', 'trackClick',
'trackSubmit', 'pageview', 'ab', 'alias', 'ready'];
for (var i = 0; i < methods.length; i++) {
lib[methods[i]] = function () { };
}
What am I missing? Please, help me understand!
Ian here, co-founder at Segment.io—I didn't actually write that code, Calvin did, but I can fill you in on what it's doing.
You're right, the methodFactory is stubbing out the methods so that they are available before the script loads, which means people can call analytics.track without wrapping those calls in an if or ready() call.
But the methods are actually better than "dumb" stubs, in that they save the method that was called, so we can replay the actions later. That's this part:
analytics.push([type].concat(Array.prototype.slice.call(arguments, 0)));
To make that more readable:
var methodFactory = function (method) {
return function () {
var args = Array.prototype.slice.call(arguments, 0);
var newArgs = [method].concat(args);
analytics.push(newArgs);
};
};
It tacks on the name of the method that was called, which means if I analytics.identify('userId'), our queue actually gets an array that looks like:
['identify', 'userId']
Then, when our library loads in, it unloads all of the queued calls and replays them into the real methods (that are now available) so that all of the data recorded before load is still preserved. That's the key part, because we don't want to just throw away any calls that happen before our library has the chance to load. That looks like this:
// Loop through the interim analytics queue and reapply the calls to their
// proper analytics.js method.
while (window.analytics.length > 0) {
var item = window.analytics.shift();
var method = item.shift();
if (analytics[method]) analytics[method].apply(analytics, item);
}
analytics is a local variable at that point, and after we're done replaying, we replace the global with the local analytics (which is the real deal).
Hope that makes sense. We're actually going to have a series on our blog about all the little tricks for 3rd-party Javascript, so you might dig that soon!
Not very related to the question, but may be useful to those who googled for issue "segment not sends queued events".
In my code I assigned window.analytics to another variable at page loading stage:
let CLIENT = analytics;
Then I used this variable instead of using global analytics:
CLIENT.track();
CLIENT.page();
// etc
But I encountered a problem when sometimes events are sent, and sometimes nothing is being sent. That "sometimes" vary between page reloads. Sometimes it also could ignore all events that fire at page loading, and without page reloading start sending events that are binded after page loading.
Then I debugged and found that CLIENT holds all not sent events in queue. Obviously they were put using methodFactory(). Then I found this SO question. So that's what is happening I think:
CLIENT holds reference to stub analytics object, which calls this methodFactory(). After Segment is fully loaded it replaces window.analytics with actual code while CLIENT still holds reference to old window.analytics. That's why this "sometimes" happens: sometimes window.analytics was replaced by Segment before loading the main script which initializes this CLIENT, and sometimes main script loaded earlier than Segment script.
New code:
let CLIENT = undefined;
if (CLIENT) {
CLIENT.page();
} else {
window.analytics.page();
}
I need to have this CLIENT because I'm using same analytics code for web and mobile. On mobile this CLIENT will be initialized separately while on web window.analytics is always available.

Delaying execution of Javascript function relative to Google Maps / geoxml3 parser?

I'm working on a implementing a Google map on a website with our own tiles overlays and KML elements. I've been previously requested to create code so that, for instance, when the page is loaded from a specific URL, it would initialize with one of the tile overlays already enabled. Recently, I've been requested to do the same for the buildings which are outlined by KML elements so that, arriving at the page with a specific URL, it would automatically zoom, center, and display information on the building.
However, while starting with the tile overlays work, the building KML does not. After doing some testing, I've determined that when the code which checks the URL executes, the page is still loading the KML elements and thus do not exist for the code to compare to or use:
Code for evaluating URL (placed at the end of onLoad="initialize()")
function urlClick() {
var currentURL = window.location.href; //Retrieve page URL
var URLpiece = currentURL.slice(-6); //pull the last 6 digits (for testing)
if (URLpiece === "access") { //If the resulting string is "access":
access_click(); //Display accessibility overlay
} else if (URLpiece === "middle") { //Else if the string is "middle":
facetClick('Middle College'); //Click on building "Middle College"
};
};
facetClick();
function facetClick(name) { //Convert building name to building ID.
for (var i = 0; i < active.placemarks.length; i++) {
if (active.placemarks[i].name === name) {
sideClick(i) //Click building whose id matches "Middle College"
};
};
};
Firebug Console Error
active is null
for (var i = 0; i < active.placemarks.length; i++) {
active.placemarks is which KML elements are loaded on the page, and being null, means no KML has been loaded yet. In short, I have a mistiming and I can't seem to find a suitable place to place the URL code to execute after the KMl has loaded. As noted above, I placed it at the end of onLoad="initialize()", but it would appear that, instead of waiting for the KML to completely load earlier in the function, the remainder of the function is executed:
onLoad="initialize()"
information(); //Use the buttons variables inital state to set up description
buttons(); //and button state
button_hover(0); //and button description to neutral.
//Create and arrange the Google Map.
//Create basic tile overlays.
//Set up parser to work with KML elements.
myParser = new geoXML3.parser({ //Parser: Takes KML and converts to JS.
map: map, //Applies parsed KML to the map
singleInfoWindow: true,
afterParse: useTheData //Allows us to use the parsed KML in a function
});
myParser.parse(['/maps/kml/shapes.kml','/maps/kml/shapes_hidden.kml']);
google.maps.event.addListener(map, 'maptypeid_changed', function() {
autoOverlay();
});
//Create other tile overlays to appear over KML elements.
urlClick();
I suspect one my issues lies in using the geoxml3 parser (http://code.google.com/p/geoxml3/) which converts our KML files to Javascript. While the page has completed loading all of the elements, the map on the page is still loading, including the KML elements. I have also tried placing urlClick() in the parser itself in various places which appear to execute after all the shapes have been parsed, but I've had no success there either.
While I've been intending to strip out the parser, I would like to know if there is any way of executing the "urlClick" after the parser has returned the KML shapes. Ideally, I don't want to use an arbitrary means of defining a time to wait, such as "wait 3 seconds, and go", as my various browsers all load the page at different times; rather, I'm looking for some way to say "when the parser is done, execute" or "when the Google map is completely loaded, execute" or perhaps even "hold until the parser is complete before advancing to urlClick".
Edit: Here are links to the map with the basic form of the issue found above. Since I've been developing the next update to the map on a test server, facetClick() is not part of this live version and I instead use its output function sideClick(); however the error is still the same in this arrangement:
active is null
google.maps.event.trigger(active.gpolygons[poly],'click');
Map: http://www.beloit.edu/maps/
Map w/Accessibility: http://www.beloit.edu/maps/?access
Map w/Building Click: http://www.beloit.edu/maps/?middle
EDIT: Spent most of my day working on rebuilding the functionality of the parser in Javascript and, low and behold, without the parser it works just fine. I figure that is obvious as I have to define each shape individually before the code, rather than waiting for it to be passed along by the parser. It would seem the answer is "if you want unique URLs, drop the parser". >_<
I've come across a similar problem when dealing with waiting for markers and infoWindows to load before executing a function. I found a solution here ( How can I check whether Google Maps is fully loaded? see #Veseliq's answer) that using the google maps event listener function for checking when the map is 'idle', does the trick. I assume this solution would work for KML layers as well. Essentially what you will have to do is include the following at the end of your initialize function:
google.maps.event.addListenerOnce(map, 'idle', function(){
// do something only the first time the map is loaded
});
In the API reference ( https://developers.google.com/maps/documentation/javascript/reference ) it states that the 'idle' event "is fired when the map becomes idle after panning or zooming". However, it seems to hold true that it is also fires on initial page load after everything in the map_canvas has loaded. And by using the addListenerOnce call, you ensure that it is never executed again after the initial page load (meaning it won't fire after a zoom or a pan action).
Second option:
As I mentioned you can take the callback approach, I believe this will only call your urlClick function after completing the parsing. Here's how you should probably arrange your code to make it work:
function someFunction(callback){
myParser.parse(['/maps/kml/shapes.kml','/maps/kml/shapes_hidden.kml']);
callback();
}
and then in your initialize you will have:
someFunction(function(){
urlClick();
});
You will have to make your map and myParser variables global.
Resources: This link had an excellent and detailed brief on how callback functions work in javascript, http://www.impressivewebs.com/callback-functions-javascript/

How can I delay the execution of a script block until after an external script has loaded?

I'm trying to dynamically insert and execute a couple of scripts, and I think I'm hitting a race condition where the second is trying to execute before the first is loaded.
The project I'm working on has an unusual requirement: I am unable to modify the page's HTML source. It's compiled into an app for localization purposes.
Therefore, I'm unable to insert <script> tags like I normally would to link in JavaScript files.
It turns out that the client wants to use a hosted web font, so I decided to build and append the two required <script> tags dynamically in an already-linked JavaScript file.
The <script> blocks are appending correctly in the head of the document, but function in the second block seems to be firing before the external script linked in the first <script> tag is fully loaded, and it's throwing an undefined error.
Here's the relevant piece of code:
document.getElementsByTagName("head")[0];
var tag = document.createElement('script');
tag.setAttribute("src", "http://use.typekit.com/izj3fep.js");
document.getElementsByTagName("head")[0].appendChild(tag);
try {
Typekit.load(); // This is executing too quickly!
} catch(e){
console.log("Hosted fonts failed to load: " + e);
}
I tried moving the try block to the window.onload event, but that fires before any of this code is called.
I guess I could dynamically load jQuery and then use it's ready event, but that seems pretty heavy-handed. I'm hesitant to pull in a library on this project, as the client has a lot of custom JavaScript that could potentially clash with it.
What else can I try?
You need to hook into the script element's onload event and execute your code there:
document.getElementsByTagName("head")[0];
var tag = document.createElement('script');
tag.onload = onTagLoaded;
tag.setAttribute("src", "http://use.typekit.com/izj3fep.js");
document.getElementsByTagName("head")[0].appendChild(tag);
function onTagLoaded() {
try {
Typekit.load(); // This is executing too quickly!
} catch(e){
console.log("Hosted fonts failed to load: " + e);
}
}
You can load it with yepnope ( http://yepnopejs.com/ ). I know it's a library, but it's very light (free if your client is already using modernizr). It's well worth it. Hopefully the client doesn't have another yepnope function, and you don't have to worry about the clash.
Are you using jQuery? If not, I highly recommend it. It'll make your life so much easier:
$.getScript('http://use.typekit.com/izj3fep.js', function(data, textStatus){
try {
Typekit.load(); //executes properly now!
} catch(e) {
console.log("Hosted fonts failed to load: " + e);
}
});
Combining the scripts into one big seems to be the easiest solution.

Categories

Resources