Google Maps for Rails - update markers with AJAX

2019-01-27 04:34发布

问题:

I am working on a web app and I use Ruby on Rails. Our index is made of a map and of a search field. You can search a location and the map updates its markers.

I would like to use Ajax to avoid refreshing the page. So I added remote: true to the form, a respond_to in the controller and a new search.js.erb. My search.js.erb renders a partial _googlemap.erb which contains the script to initialize the map.

Here is my problem. As the map already exists, it's like if I wanted to create the same object twice, which of course doesn't work and is awfull. I'd like to update only markers in the map with new ones. But I can't find a way to do it.

I saw the former version of Gmaps4rails integrated a way to do it ( Gmaps.map.replaceMarkers(your_markers_json_array); ) but it doesn't seem to work now. When I use it, I got this error: "TypeError: Gmaps.map is undefined". I tried with "handler.replaceMarkers();" but this time I have "TypeError: handler.replaceMarkers is not a function".

I am new to Javascript and to Rails, but I want to improve my knowledge and I really need to go on with the rest of this web app. I have been looking for a solution everywhere on the internet but in vain.

Live website here

Please, could someone tell me how I could do that and what I am doing wrong?

MANY thanks in advance,

Céline

zones_controller.rb

def search
   respond_to do |format|
     format.html.none do
      search_params = params[:zone][:search]
         coordinates = Geocoder.coordinates(search_params).join(",")
         @zones = Zone.search(
           "", { "aroundLatLng" => coordinates, 
                  "aroundRadius" => 500000  #Searches around 500 km
                })
         if coordinates.nil?
           @zones = Zone.search(params[:search])
         elsif @zones.empty?
           @zones = Zone.all
           flash[:error] = "No zone could be found. Please try again."
         end
       build_map(@zones)
     end

     format.js
   end
end

def build_map(array)
  @hash = Gmaps4rails.build_markers(array) do |zone, marker|
    marker.lat zone.latitude
    marker.lng zone.longitude
    marker.json({ title: zone.description, id: zone.id })
    marker.infowindow render_to_string(:partial => "/zones/map_box", locals: { zone: zone })
  end
end

search.html.erb

    <div id="map" style='width: 100%; height: 700px;'>
</div>
  <!-- Beginning Google maps -->

<script type="text/javascript" id="map_script">
   $(document).ready(function() {
     <%= render 'googlemap', hash: @hash %>
   }); // Document ready
</script>

_googlemap.erb

handler = Gmaps.build('Google');
handler.buildMap({ provider: {
    disableDefaultUI: true,
    mapTypeId: google.maps.MapTypeId.TERRAIN
  }, internal: {id: 'map'}
}, function(){
  markers_json = <%= raw hash.to_json %>;
  markers = _.map(markers_json, function(marker_json){

    marker = handler.addMarker(marker_json);
    handler.getMap().setZoom(4);

    _.extend(marker, marker_json);

    marker.infowindow = new google.maps.InfoWindow({
      content: marker.infowindow
    });

    return marker;
  });

  handler.bounds.extendWith(markers);
  handler.fitMapToBounds();
});

search.js.erb

$('#map_script').replaceWith("<%= render 'googlemap', hash: @hash %>");

回答1:

Why don't you just update the map with the new markers? Meaning, instead of re-rendering the whole map after each search, just update the markers on the existing map by removing all markers and adding the new ones.

I haven't verified the method, but I guess it should work and be more efficient:

Create a app/assets/javascript/map.js file. You can store your map-related functions there. Create a function to update your map's markers in this file:

map.js

(function() {
  /* __markers will hold a reference to all markers currently shown
     on the map, as GMaps4Rails won't do it for you.
     This won't pollute the global window object because we're nested
     in a "self-executed" anonymous function */
  var __markers;

  function updateMarkers(map, markersData) 
  {
    // Remove current markers
    map.removeMarkers(__markers);

    // Add each marker to the map according to received data
    __markers = _.map(markersData, function(markerJSON) {
      marker = map.addMarker(markerJSON);
      map.getMap().setZoom(4); // Not sure this should be in this iterator!

      _.extend(marker, markerJSON);

      marker.infowindow = new google.maps.InfoWindow({
        content: marker.infowindow
      });

      return marker;
    });

    map.bounds.extendWith(__markers);
    map.fitMapToBounds();
  };

  // "Publish" our method on window. You should probably have your own namespace
  window.updateMarkers = updateMarkers;
})();

This function can be used to initialize your map and to update it. As you will not (if my answer satisfies you) render the map twice, you can delete _google_map.erb and put its content back into search.html.erb, but using the function we've just created:

search.html.erb

    <div id="map" style='width: 100%; height: 700px;'>
</div>
  <!-- Beginning Google maps -->

<script type="text/javascript" id="map_script">
   $(document).ready(function() {
       mapHandler = Gmaps.build('Google');
       mapHandler.buildMap({ provider: {
           disableDefaultUI: true,
           mapTypeId: google.maps.MapTypeId.TERRAIN
         }, internal: {id: 'map'}
       }, function(){
         var markersData = <%= raw @hash.to_json %>;
         updateMarkers(mapHandler, markersData);
       });
   }); // Document ready
</script>

Please don't forget the var keyword when declaring variables, otherwise they will end up being globals, and that's bad ^^
Note that I have deliberately left mapHandler as a global variable: you will need access to your handler to update markers later when someone uses the search. This is probably not an ideal thing to do, but this question is not about refactoring your code so let's keep it this way.

So now my solution brings you a map that initializes with the given markers on page load. In other words, nothing has changed!

However you're now allowed to reuse this updateMarkers function to change the markers displayed on your map. That's what you search.js.erb script should do:

search.js.erb

(function() {
  var markersData = <%= raw @hash.to_json %>;
  updateMarkers(mapHandler, markersData);
})();

That's it! Hopefully it'll take you to the next step of your project :)



回答2:

I tried the same thing but instead of updating the marker you should include the map in the partial/placeholder and then update it ...

for example, this is the view which is displaying the map...i will update this view/placeholder with latest map and markers

<div id="map_users_index">
<div id="map" class="map" style="height:relative;">
</div>

in users_controller.rb

   ##take index action or any other action 
    def index
    ##do your stuff and get more users
         respond_to do |format|
           format.html 
           format.js
         end
    end

in index.js.erb

##update the placeholder/partial with new map with new markers

$("#map_users_index").html("<%= escape_javascript(render(:partial => 'index')) %>");

I HAVE MY OWN WORKING CODE...HERE