GMaps4Rails and Turbolinks not loading without ful

2019-02-02 00:03发布

I'm using gmaps4rails to load a map on a "clients" show page. I've integrated turbolinks to make the app speadier all together, but now I'm hitting an issue where I have to refresh the page with the map for the map to show up. All I get is a blank map frame. When I refresh the page, the map shows up correctly.

I tried adding the gem 'jquery-turbolinks', but the problem persists.

In view:

<%= gmaps("map_options" => {:container_class => "map_container_renamed", "zoom" => 15, "auto_zoom" => false},"markers" => { "data" => @json }) %>

In application.js

//= require jquery
//= require turbolinks
//= require jquery.turbolinks
//= require jquery_ujs
//= require twitter/bootstrap
//= require_tree .

Gist of page source where map is blank here.

6条回答
何必那么认真
2楼-- · 2019-02-02 00:16

The fix ended up that I need a full page load for the scripts to refresh.

Adding

'data-no-turbolink' => true

to the link that goes to the maps page worked.

For example, if I have an index page of clients, and I have a map of where the client lives on the show page, the link to go to the show page would be formatted as below:

<%= link_to 'View Client', client, class: "btn", 'data-no-turbolink' => true %>

This will cause a full refresh of the html and javascript, instead of just the changed html.

查看更多
虎瘦雄心在
3楼-- · 2019-02-02 00:17

You can use the callback directly from the google map URL

<script src="https://maps.googleapis.com/maps/api/js
 ?callback=yourInitFunction
 &key=&sensor=false">

Note however that you must have your init functions defined BEFORE you call this script.

Here is how I proceed. I have a helper that generates the Gmaps script tag with a callback to the right function (+ loading other gmaps scripts)

# view/your_view.html.erb
<script>
function onGmapsInit() {
    // Your code here. You can also define this function in your assets/js directly.
}
</script>
<%= gmap_scripts(callback: 'onGmapsInit') %>

And my helper

# helpers/maps_helper.rb
module MapsHelper
  GMAPS_V3_URL = 'https://maps.googleapis.com/maps/api/js'

  def gmap_scripts(options = {})
    callback = options.delete(:callback)
    gmaps_v3_options = {
      src: "#{GMAPS_V3_URL}?#{callback ? "callback=#{callback}&" : ''}key=&sensor=false"
    }
    gmaps_utility_options = {
      src: '//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js'
    }
    capture do
      concat content_tag(:script, nil, gmaps_v3_options)
      concat content_tag(:script, nil, gmaps_utility_options)
    end
end

What ? Turbolinks ? Is it something to eat ?

I'm using Turbolinks 5, and as you can see with this code you don't even need a

$(document).on('ready turbolinks:load')

查看更多
萌系小妹纸
4楼-- · 2019-02-02 00:27

Technical Background

data-no-turbolink

Please note, just adding the attribute data-no-turbolink="true" to a link does suppress turbolinks for that link. Clicking on that link does load the entire page rather than replacing the content with turbolinks.

But there are several ways to use turbolinks with gmaps4rails without suppressing turbolinks.

Turbolinks Callbacks

The key to getting something to work with turbolinks, is to use the provided callbacks. In this case, one has to tell gmaps4rails to perform the map loading when turbolinks has loaded a new page.

In general, one can use $(document).on('page:load', function ... ) as, for example, shown in Railscast #390.

JavaScript's Same-Origin Policy

The google api script included by the gmap4rails gem requests another script from a google static server. But if the api is dynamically included by turbolinks, the single-origin policy prevents the request to the google static server.

The single-origin policy itself is a useful thing. Although there are a couple of ways to circumvent it, this appears not to be a natural thing to do. Instead, the "right way" to do it is to "prove" that the web page really wants to execute the remote script (the google api) and that the remote script is not requested by a malicious script. This is done by including the script tag that requests the google api directly in the html tree.

Including the google api script tag directly in the html tree is only possible on the first request, when using turbolinks, since all later changes are dynamically injected into the tree by turbolinks. That means that, when using turbolinks, the google api has to be included even on pages that do not show a map.

Solution 1 (Recommended): Use fork for gmaps4rails

I've made a pull request, hopefully adding Turbolinks support for gmaps4rails.

Demo

If you'd like, have a look at this demo first that shows the use of gmaps4rails with turbolinks:

How to use this in your project

You can try this out and check if it's working for your project by adding the fork to your Gemfile:

# Gemfile
# ...
gem 'turbolinks'
gem 'gmaps4rails', '~> 2.0.1', git: 'https://github.com/fiedl/Google-Maps-for-Rails.git'
# ...

Furthermore, you need to modify your layout file to include the api in the html head:

<!-- app/views/layouts/application.html.erb -->
<html>
  <head>
    ...
    <%= stylesheet_link_tag    "application", :media => "all" %>
    <%= javascript_include_tag "application" %>
    <%= gmaps4rails_api_script_tags  #   <-- THIS IS NEW ----------------- %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    ...
    <%= yield %>
    <%= yield :scripts %>
  </body>
</html>

If, for some reason, you do not want to use this fork, I see two alternatives to use gmaps4rails together with turbolinks:

Solution 2: Do it manually

Basically doing the same thing as the fork (Solution 1), one can manually add the api scripts to the head in the layout file. Then, when loading a page via turbolinks, one can configure and activate the map using javascript.

This is demonstrated generally for the use with UJS in the gmaps4rails wiki: https://github.com/apneadiving/Google-Maps-for-Rails/wiki/Using-with-UJS

Solution 3: Include the api statics manually

If you do not want to add the api scripts globally, there is a way to get around the single-origin policy and load the api dynamically.

I have prepared a gist for that: https://gist.github.com/fiedl/6573175

You would have to add a javascript file that does two things:

  1. Load the google maps api itself.
  2. Load the statics file, which is loaded by the api.

You can find out the url to the statics file by opening the api script in a browser and copying the url from the getScript call at the bottom.

But: Doing this fixes the static api request to your locale, which is probably not a good thing if your app is used internationally.

查看更多
欢心
5楼-- · 2019-02-02 00:31

I had trouble filling the map canvas on revisits to pages already managed by turbolinks, especially when using the back button. I fixed the issue by adding a listener, for the turbolinks event page:load, to map initialization.

map.js.coffee

google.maps.event.addDomListener(window, 'load', @_setup)
google.maps.event.addDomListener(window, 'page:load', @_setup)

application.js.coffee

//= require jquery
//= require jquery.turbolinks
查看更多
仙女界的扛把子
6楼-- · 2019-02-02 00:33

As can be found in this Github issue, you can include google scripts to all your pages. That fixes the issue with Turbolinks not executing gmaps4rails javascripts for newly navigated pages.

Note that gmaps4rails gem has javascript dependencies, namely:

<script src="//maps.google.com/maps/api/js?v=3.13&amp;sensor=false&amp;libraries=geometry" type="text/javascript"></script>
<script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js' type='text/javascript'></script>

You can add them to all your pages typically by editing app/views/layouts/application.html.erb.

application.html.erb

....
<head>
...
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>

  # insert the following two lines
  <script src="//maps.google.com/maps/api/js?v=3.13&amp;sensor=false&amp;libraries=geometry" type="text/javascript"></script>
  <script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js' type='text/javascript'></script>

</head>
...

Also, don't forget to add the source code to your asset pipeline by editing app/assets/javascripts/application.js

application.js

Add this:

//= require gmaps/google

Now, the map should be loading without you having to refresh the page.

查看更多
男人必须洒脱
7楼-- · 2019-02-02 00:39

I had the same problem, but I did not want to turn off turbolinks and I couldn't get it right turbolink DOM listeners.

While fixing another problem and refactoring my javascript I resolved this by accident.

This is my functions.js

var init_map = function (allMarkers) {
handler = Gmaps.build('Google');
handler.buildMap({ provider: {maxZoom:17, minZoom:5}, internal: {id: 'map'}}, function(){
  markers = handler.addMarkers(allMarkers());
  handler.bounds.extendWith(markers);

   handler.fitMapToBounds();
});
};

var marker_check = function(){...};

var init_autocomplete = function () {...};


//function to call google maps functions

var init_google_api = function(function_name){
  $(document).ready(function(){
  google.maps.event.addDomListener(window, "load", function_name );
});
};

Since I am using also google places libary I have several functions that I can call with the init_google_api()

In each view where I want maps I just put a

<%= javascript_tag do %>
   init_google_api(init_map(marker_check));
<% end %>

In the very end of the view. Fixed everything + keeps the code dry for wanting more than one map etc.

If I'm not mistaken, this approach goes around the turbolink problem, because each time a view is called, it executes the init_google_api function and the function that is passed in as the parameter ( this why I use function expressions). So the code is run each time the function is called.

查看更多
登录 后发表回答