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 %>");
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 :)
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