I worked on writing a small application with a lot of help from stackoverflow. The basic premise is simple, and I have seen such functionality all over the Internet: I am trying to build a list of locations on a Google search / customized map. Places are stored on the backend, and the controller passes these locations to the view. AJAX is involved because I do not want to reload the entire page. Here are the scenarios: a) A user searches for a location via zipcode => map, loads a new location, a search request is sent to the server, and the map loads any markers, if there is any of the set radii, the map sets the default zoom level; b) Custom panning / zooming around => the map remains where it was left, the search with the bounding box of the viewport is sent to the server and the results are compared. The map will be the default for Seattle at boot time, and the first thing it tries is to bind the user to geolocation ...
Using the gmaps4ails wiki page and basically a modified version of the answer to this question: Google Maps for Rails - update markers using AJAX . I am very close. It works, in fact, just with a hitch. Here's what it looks like:
sightings_controller.rb
def search if params[:lat] @ll = [params[:lat].to_f, params[:lng].to_f] @sightings = Sighting.within(5, origin: @ll).order('created_at DESC') @remap = true elsif search_params = params[:zipcode] geocode = Geokit::Geocoders::GoogleGeocoder.geocode(search_params) @ll = [geocode.lat, geocode.lng] @sightings = Sighting.within(5, origin: @ll).order('created_at DESC') @remap = true elsif params[:bounds] boundarray = params[:bounds].split(',') bounds = [[boundarray[0].to_f, boundarray[1].to_f], [boundarray[2].to_f, boundarray[3].to_f]] @ll = [params[:center].split(',')[0].to_f, params[:center].split(',')[1].to_f] @sightings = Sighting.in_bounds(bounds, origin: @ll).order('created_at DESC') @remap = false else search_params = '98101' geocode = Geokit::Geocoders::GoogleGeocoder.geocode(search_params) @ll = [geocode.lat, geocode.lng] @sightings = Sighting.within(5, origin: @ll).order('created_at DESC') @remap = true end @hash = Gmaps4rails.build_markers(@sightings) do |sighting, marker| marker.lat sighting.latitude marker.lng sighting.longitude marker.name sighting.title marker.infowindow view_context.link_to("sighting", sighting) end respond_to do |format| format.html format.js end end
search.html.haml
= form_tag search_sightings_path, method: "get", id: "zipform", role: "form", remote: true do = text_field_tag :zipcode, params[:zipcode], size: 5, maxlength: 5, placeholder: "zipcode", id: "zipsearch" = button_tag "Search", name: "button" %input{type: "button", value: "Current Location", onclick: "getUserLocation()"} #locationData .sightings_map_container .sightings_map_canvas#sightings_map_canvas #sightings_container - content_for :javascript do %script{src: "//maps.google.com/maps/api/js?v=3.13&sensor=false&libraries=geometry", type: "text/javascript"} %script{src: "//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js", type: "text/javascript"} :javascript function getUserLocation() {
search.js.erb
(function() { var json_array = <%= raw @hash.to_json %>; if (<%= @remap %>) { var latlng = <%= raw @ll %>; resetMarkers(handler, json_array); resetMap(handler, latlng); } else { resetMarkers(handler, json_array); } })();
map.js
(function() { function createSidebarLi(json) { return ("<li><a>" + json.name + "</a></li>"); }; function bindLiToMarker($li, marker) { $li.on('click', function() { handler.getMap().setZoom(18); marker.setMap(handler.getMap());
There is a problem here, and it is also related to this specific example, like my seeming misunderstanding of how javascript works (I'm new to it). When the user rotates and zooms in, but the search result is the same, I would prefer NOT to call the resetMarkers function, but rather leave the map alone. Currently, the map will always reload Markers / sidebar / etc, and this causes a little flickering of markers on the screen.
I tried several different versions of this, but it does not work. In map.js:
var __markers; var __oldmarkers; function resetMarkers(handler, json_array) { if(!(_.isEqual(__oldmarkers, __markers))) { handler.removeMarkers(__markers); clearSidebar(); clearZipcode(); if (json_array.length > 0) { __markers = handler.addMarkers(json_array); _.each(json_array, function(json, index){ json.marker = __markers[index]; }); createSidebar(json_array); } __oldmarkers = __markers.slice(0); } };
Since __markers seems to retain its value over the life of the page (we use it to remove old markers before setting new ones), I thought I could just create another variable to test it. However, this is always false, even if I think it should be true.
Another thing I tried is to resubmit the old hash as a parameter with each search request and then set the flag, but this seems complicated and the string / hash / array manipulation is so confused that I gave up. I really don't think this would be a better approach, but maybe I should do it this way?
Or is there something that I am missing completely and should do instead?