Search code examples
ruby-on-railsajaxgoogle-mapsamazon-web-servicesamazon-cloudfront

Google Maps asynchronous loading with AWS Cloudfront


My google-map is only loading asynchronously after a page reload or an HTTP redirect. For the first onload, it won't appear-- none of the javascript is called.

In Rails development mode, it works without any problem. Likewise, the map loads asynchronously in production mode if I serve assets locally. But when I configure Amazon Web Services' Cloudfront CDN, it fails on the first page load.

Here's the javascript from the view that calls the map:

  <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.1.min.js"></script>


  <script type="text/javascript">

function initialize() {

var map = new google.maps.Map(document.getElementById('map'), {
  zoom: 4,
  streetViewControl: false,
  overviewMapControl: false,
  mapTypeControl: false,
  zoomControlOptions: {
    style: google.maps.ZoomControlStyle.DEFAULT,
  },
  center: new google.maps.LatLng(39.82, -98.58),
});


// Info Window Content
var infoWindowContent = <%= raw @content %>;
var locations = <%= raw @location %>;

 // Display multiple markers on a map

var infowindow = new google.maps.InfoWindow(), marker, i;

var marker, i;
var markers = new Array();

for (i = 0; i < locations.length; i++) {  
  marker = new google.maps.Marker({
    position: new google.maps.LatLng(locations[i][1], locations[i][2]),
    map: map
  });

  markers.push(marker);

  google.maps.event.addListener(marker, 'click', (function(marker, i) {
    return function() {
      infowindow.setContent(infoWindowContent[i][0]);
      infowindow.open(map, marker);
    }
  })(marker, i));
}

}
   function loadScript() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&' +
    'callback=initialize';
document.body.appendChild(script);
  }

    window.onload =loadScript;
     </script>  

Any idea what may be going wrong with the code on AWS Cloudfront?


Solution

  • Wow. Finally solved this.

    A few things went wrong.

    First, AWS S3 and Cloudfront do not allow CORS, or Cross Origin Resource Sharing, by default. This means that you cannot call an AJAX resource served by either-- it won't work. That said, you can configure both to allow CORS. To do so, you need to edit your S3 bucket permissions and add a CORS Configuration (AWS offers good examples on help pages). Next, you need to whitelist the 'Origin' header on your Cloudfront distribution. This will allow cloudfront to forward the Origin header from the request to the response, and allow CORS. (I didn't fully get this to work. Check AWS help resources on CORS for more information)

    Second, I noticed that even though my AJAX script was served locally and not by Cloudfront, a different javascript file was: my Turbolinks js file. It turns out the Turbolinks gem that the Rails Asset Pipeline requires creates a js file ended up being uploaded to my distribution (it's required by default in the application.js file). If you read the Turbolinks documentation, you'll see that Turbolinks ensures only reloads the html body and title of the head. Here's this from the README:

     "Instead of letting the browser recompile the JavaScript and CSS between each page
      change, it keeps the current page instance alive and replaces only the body and 
      the title in the head"
    

    As it turns out, the Turbolinks interfered with the local AJAX and prevented the Google Map from loading asynchronously. Since the AWS CORS configuration evaded me, I simply added the option to turn turbolinks off for the link leading to the Google Map page:

    data: { no_turbolink: true }
    

    to make the full link:

    <%=link_to "/map", data: { no_turbolink: true } %>
    

    With no turbolinks, the page was never called using the Cloudfront Turbolinks.js file, and thus the local AJax could load the Google Map.

    I hope this helps ye weary travelers!