I'm trying to create a Ember View for Google Maps, and load the script in a on-demand manner, i.e. asynchronously Loading the API.
I have two functions inside the view, one is used to load the Google Maps API and the other is to initialize the map. But since Google requires me to call the callback function through the link that requires the API. But in Ember.JS, I couldn't get the right result. All I've got is an ERROR message saying that the object "google" is undefined when trying to initialize the map.
Here is the Ember view code for now.
App.MapsView = Ember.View.extend({
templateName: 'maps',
map: null,
didInsertElement: function() {
this._super();
this.loadGoogleMapsScript();
},
initiateMaps:function(){
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(this.$().get(0), mapOptions);
this.set('map',map);
},
loadGoogleMapsScript: function(){
var map_callback = this.initiateMaps();
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=map_callback';
document.body.appendChild(script);
},
});
Any ideas how to solve this callback problem? And, what is the optimal way to initialize the map? Should it be in the template or from the JS?
Thanks in advance.
There are two problems here. One is that you're calling your initiateMaps() function in this line:
var map_callback = this.initiateMaps();
This call is made before the Maps API is loaded, leading to the undefined google error.
The other problem is that map_callback is local to this function. The callback used in the Maps API script URL has to be a global function.
(You solved this problem yourself; I'm just adding it here for the benefit of future visitors.)
The fix for both of these problems is to change that line to:
var self = this;
window.map_callback = function() {
self.initiateMaps();
}
There may be a more "native" Ember way to do this, but that should do the trick in any case.
Also, since you're using jQuery along with Ember, you can replace this code:
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=map_callback';
document.body.appendChild(script);
with:
$.getScript( 'https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=map_callback' );
Related
I want to create a div using javascript to put my google map into.
It creates a div, but doesnt put the map api into it. Please help.
You need to make sure the Google Maps API script has loaded before running your code. Currently, you are trying to build the map before the browser has downloaded the map API.
The simplest way to fix this is to change your HTML to this:
<script src="https://maps.googleapis.com/maps/api/js?key="Entered key here deleted it for stackoverflow"&callback=initMap">
<script src="javascript.js"></script>
You could also remove the Google Maps script tag and load it dynamically in javascript.js using jQuery’s $.getScript() or with plain JS using https://github.com/filamentgroup/loadJS, running your code as the callback.
initMap is firing when the script loads, but create isn't getting called until you click that p tag. Here's a way to get around that issue, but still wait for the script to load before allowing clicks:
var mapready = false, createcalled = false;
function create()
{
createcalled = true;
if(mapready){
var newDiv = document.createElement("map");
newDiv.id = "map";
newDiv.style.border="solid";
document.body.appendChild(newDiv);
var uluru = {lat: 54.278556, lng: -8.460095};
var map = new google.maps.Map(document.getElementById('map'), {zoom: 16,center: uluru});
var marker = new google.maps.Marker({position: uluru,map: map});
}
}
function initMap()
{
mapready = true;
if(createcalled) create();
}
In this scenario, if the user clicks the p tag before the map is ready, the create function will fire as soon as the map API finishes loading.
I have tried to solve this issue for weeks, kinda ignored it lately, but it is really starting to annoy me. If someone could tell me what's wrong with my code, i would appreciate it.
Everything works, except that it makes my code run twice, and when i dynamically load my ajax content, it makes my code run even more times.
I am fairly sure it is because of the callback when asynchronous loading the google maps.
if (window.google && google.maps) {
initialize();
} else {
if (!$('script[src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=xxxxxxxxxxxxxxxxx&signed_in=true&language=en&callback=initialize"]').length > 0) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' + '&key=xxxxxxxxxxxxxxxxx&signed_in=true&language=en&callback=initialize';
document.body.appendChild(script);
}
}
This is how i load the map. It works if you refresh the site, it jumps to the Else then. If i move around my site, which is loaded dynamic with Ajax, it goes into the IF, and run initialize().
Now... a simple console.log('test'); always fires twice, no matter what. And if i move away from the site, and go back, it fires three times, and if i go away and back one more time... you guessed it, four times test is written in console.
Now, is this where my problem is? Or does it look normal, and i should start looking at the script that loads my content with ajax?
This is my initialize function.
function initialize() {
var cell;
$('.integrator-country').text(localStorage.country);
$("#select-country").children("option[value='" + localStorage.country + "']").prop('selected', true);
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(localStorage.latitude, localStorage.longitude);
var mapOptions = {
zoom: 7,
center: latlng
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
google.maps.event.addDomListener(window, "resize", function () {
var center = map.getCenter();
google.maps.event.trigger(map, "resize");
map.setCenter(center);
});
google.maps.event.addDomListener(window, 'load', function () {
map.setCenter(latlng);
});
getIntegrators(localStorage.country, cell);
}
I´m working a dynamic way to map certain locations, I´m basing my code on an example by Googlemaps, in this example they map locations statically, using an external file (week) where you write the call to the function, an online initialization and mapping functions, like this:
function initialize() {
map = new google.maps.Map(document.getElementById('map_canvas'), {
zoom: 16,
center: new google.maps.LatLng(19.43,-99.15),
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var script = document.createElement('script');
script.src = 'week';
document.getElementsByTagName('head')[0].appendChild(script);
}
function eqfeed_callback(results){
mapping code
}
the content of week, the external file, is:
eqfeed_callback({"type":"FeatureCollection","features":[{feature01, feature02,... feature_n}]});
I´m able to generate dinamically the features content of week (in fact, the whole content, with the very same structure), but now I need to pass it to the initialization function, now that is the value of a global variable instead of an external file´s content, what I´ve made is to rewrite initialize as a parameter dependant function, in order to make it wait for its parameter to be generated, like this:
function initialize(scriptSource){
map = new google.maps.Map(document.getElementById('map_canvas'), { zoom: 16, center: new google.maps.LatLng(19.43,-99.15), mapTypeId:google.maps.MapTypeId.ROADMAP});
var script = document.createElement('script');
script.src = scriptSource;
document.getElementsByTagName('head')[0].appendChild(script);
}
when initialize is called, scriptSource will be the value of a global variable, with its value being exactly the same as the content of the external file (but now generated dinamically) week; I´ve been trying to make it work, but I think there´s a problem with the way I´m passing the src, how do I do this correctly?
It appears you are attempt to load javascript into a script tag.
Rather than setting the src member, instead set the innerHtml.
The src member is actually the url, not the content, of a script tag.
Also, be weary of other places you are setting the src.
script.src = 'week'; will not work as a uri
script.src = 'week.js'; will work as a uri
Nothing I've tried seems to work.
I found these two links and thought they'd be helpful, but that hasn't worked either.
Dynamically load JavaScript with JavaScript
https://developers.google.com/loader/
Here's roughly what my Greasemonkey script looks like currently:
var init_map = function() {
new google.maps.Geocoder().geocode({address:'1125 NW 12th Ave, Portland, OR'},function(result){
alert(result);
});
}
function loadMaps() {
GM_log("loadMaps called");
google.load("maps", "3", {"callback" : init_map, "other_params":"key=foo&sensor=false"});
}
function loadScript(filename,callback){
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.onload = callback;
fileref.setAttribute("src", filename);
if (typeof fileref!="undefined"){
document.getElementsByTagName("head")[0].appendChild(fileref);
}
}
$(document).ready(
function() {
GM_log("document ready");
loadScript('http://www.google.com/jsapi?key=ABQIAAAAfoo',function(){
loadMaps();
});
}
);
I've found that if I don't include
// #require http://www.google.com/jsapi?key=ABQIAAAAfoo
in the Greasemonkey script, I get a google is undefined error. If I do include it, init_map() never gets called. Any suggestions?
var init_map defines a local variable in the GreaseMonkey context.
If you want to run JavaScript in the context of a webpage, I recommend to inject two <script> tags in the web page (another method is to prefix all of your global variables with unsafeWindow.):
Google's map API
Your script.
Example:
// ==UserScript==
// #name Name of script
// #namespace YourNameSpaceHere
// #match http://blabla.com/*
// #version 1.0
// #run-at document-end
// ==/UserScript==
var head = document.head || document.documentElement;
var script = document.createElement('script');
script.src = 'http://www.google.com/jsapi?key=ABQIAAAAfoo';
head.appendChild(script);
var script2 = document.createElement('script');
script2.textContent = '... code here ..';
head.appendChild(script2);
// Clean-up:
script.parentNode.removeChild(script);
script2.parentNode.removeChild(script2);
E4X instead of a plain string
The easiest option to embed a string of JavaScript code in your GreaseMonkey script, without escaping quotes and newlines is to use the E4X format:
script2.textContent = <x><![CDATA[
alert("test");
]]></x>.toString();
I flagged this question as duplicate of how to use the google maps api with greasemonkey to read a table of addresses and trace the route? but the mod "found no evidence to support it".
So i will just copy-paste what i did in my question, since its not a duplicate...
Nah, just kidding :)
Lets start with your last statement:
I've found that if I don't include // #require
http://www.google.com/jsapi?key=ABQIAAAAfoo in the Greasemonkey
script, I get a google is undefined error. If I do include it,
init_map() never gets called. Any suggestions?
Yes.
First, the google maps API should not be loaded as a #require. Instead, do it like this
API_js_callback = "http://maps.google.com/maps/api/js?sensor=false®ion=BR&callback=initialize";
var script = document.createElement('script');
script.src = API_js_callback;
var head = document.getElementsByTagName("head")[0];
(head || document.body).appendChild(script);
Second, add google = unsafeWindow.google, otherwise you get the "google is undefined" error.
So, your code should start like this
var init_map = function() {
google = unsafeWindow.google
new google.maps.Geocoder().geocode . . . . . .
About the rest of your code... well, just click on the link above and there you will find how to create a DIV on the fly, add the map to it, append the DIV to the page in a fixed position, etc.
Feel free to copy whatever you want.
Greasemonkey scripts are free anyway :)
I tested the answers here and in many other places and nothing would work. Maybe because the API is now v3 or who knows.
I am going to post the answer that worked for me, which is quite different from the others I found, and I believe can be used for many other cases. It's arguably a bit ugly, but after all this is script injection and nobody likes injections.
I don't copy the whole thing in jsbin / codepen / etc. because they simply cannot replicate the GS (Greasemonkey) environment (at least yet) and inner workings.
LOADING API
I had control over the destination webpage so this was there instead of being added via GS.
<script src="https://maps.googleapis.com/maps/api/js?key=my-personal-key"></script>
On my experience, if you don't add the key, after a few requests it will fail and you will have to wait some time until it works again.
I also have there a div whith a floating window where I would create my map.
<div style="overflow:hidden; height:500px; width:700px; position:fixed; top:20px; right:20px; border:3px solid #73AD21;">
<div id="gmap_canvas" style="height:500px;width:700px;"></div>
<style>#gmap_canvas img{max-width:none!important;background:none!important}</style>
<div id="Content_Title"></div>
</div>
GS SCRIPT
// Pass whatever data you need to the window
unsafeWindow.mapdata=JSON.stringify(mapdata);
// Define content of script
var script2 = document.createElement('script');
script2.textContent = `
// Get data
mapdata=JSON.parse(window.mapdata);
// Create map and use data
function initializeX2() {
// some stuff ...
// Create map
var mapCanvas = document.getElementById('gmap_canvas');
var myLatLng = {lat: parseFloat(mapdata[max].latitude), lng: parseFloat(mapdata[max].longitude)};
var mapOptions = {
center: myLatLng,
zoom: 15,
mapTypeControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(mapCanvas, mapOptions);
var marker=[];
var contentInfoWindow=[];
var infowindow=[];
// Create markers
for (var i = max ; i > max-iterations ; i--) {
// Create marker
var BLatLng={lat: parseFloat(mapdata[i].latitude), lng: parseFloat(mapdata[i].longitude)};
console.log(BLatLng);
marker[i] = new google.maps.Marker({
position: BLatLng,
map: map
});
// Create infowindow
contentInfoWindow[i]=mapdata[i].number + " - " + mapdata[i].name;
infowindow[i] = new google.maps.InfoWindow({content: contentInfoWindow[i] });
// The function has this strange form to take values of references instead of references (pointers)
google.maps.event.addListener(marker[i], 'click', function(innerKey) {
return function() {
infowindow[innerKey].open(map, marker[innerKey]);
}
}(i));
// Open markers
infowindow[i].open(map, marker[i]);
}; // end of for
}; // end of initializeX2
initializeX2();
`; // End of string to be added to page
// Add script to the page
var head = document.head || document.documentElement;
head.appendChild(script2);
// Clean-up:
script2.parentNode.removeChild(script2);
Some explanations
In my case the markers are opened when created, and multiple may stay open. That is my desired behaviour. If you want something else you have to search around.
This may help you.
Create only ONE window to have only one infowindow open at a time ( http://www.aspsnippets.com/Articles/Google-Maps-API-V3-Open-Show-only-one-InfoWindow-at-a-time-and-close-other-InfoWindow.aspx )
If someone has got the other solutions working with API v3 (via google = unsafeWindow.google ) I would be very interested to know.
To avoid an ie 7/8 "unspecified error", I recently moved the initialization logic for a JSON powered GoogleMaps v3 implementation from inline following $(document).ready to within an event function triggered from window.onload(). Now, what was once a very fast load is now taking 15-20 seconds + to load. I understand that there are some subtle differences between oninit and onload, but this seems extreme. Any thoughts?
$(document).ready(function(){
var branchitems=[];
var markers=[];
var map="";
window.onload = function() {
var latlng = new google.maps.LatLng(59.5, -100.68);
var myOptions = {
zoom: 3,
center: latlng,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
PopulateMap(map);
}
function PopulateMap(map){
... my logic for the JSON portion of the map ...
};
window.onload is the right place to put this if you're loading the API synchronously, otherwise your code may execute before the Maps API has downloaded.
If this is taking 10 - 15 seconds then it is likely that you are loading a bunch of other resources as well. window.onload won't execute until everything has finished downloading (e.g. all scripts in script tags).
One solution may be to load Maps API V3 asynchronously (see: http://code.google.com/apis/maps/documentation/javascript/basics.html#Async). You can then start constructing your map as soon as the API has loaded.
An alternate solution is to asynchronously load anything that you don't need to be ready as soon as a user visits the site, or to load your resources from faster locations (e.g. load jQuery from the Google CDN instead of from your own server).