Google Maps For Rails – 仅在搜索结果发生变化时通过ajax更新标记

我一直在编写一个小应用程序,有很多来自stackoverflow的帮助。 基本前提很简单,我在网络上看到了这种function:我试图将一个位置列表绘制到可搜索/可平移的谷歌地图上。 位置存储在后端,控制器将这些位置提供给视图。 涉及AJAX是因为我不想重新加载整个页面。 以下是场景:a)用户通过zipcode搜索位置=>地图加载新位置,搜索获取发送到服务器并映射加载任何标记,如果有任何设置半径,地图设置默认缩放级别; b)用户平移/缩放=>地图停留在用户离开的任何地方,使用视口边界框的搜索被发送到服务器并映射结果。 地图将在初始加载时默认为西雅图,它尝试的第一件事是地理定位用户…

使用gmaps4ails wiki,主要是这个问题答案的修改版本: Google Maps for Rails – 用AJAX更新标记我已经非常接近了。 实际上,这很有效。 这是它的样子:

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() { //check if the geolocation object is supported, if so get position if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(setLocation); } else { document.getElementById("locationData").innerHTML = "Sorry - your browser doesn't support geolocation!"; } } function setLocation(position) { //build text string including co-ordinate data passed in parameter var displayText = "Latitude: " + position.coords.latitude + ", Longitude: " + position.coords.longitude; //display the string for demonstration document.getElementById("locationData").innerHTML = displayText; //submit the lat/lng coordinates of current location $.get('/sightings/search.js',{lat: position.coords.latitude, lng: position.coords.longitude}); } // build maps via Gmaps4rails handler = Gmaps.build('Google'); handler.buildMap({ provider: { }, internal: { id: 'sightings_map_canvas' } }, function() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(setLocation); } var json_array = #{raw @hash.to_json}; var latlng = #{raw @ll}; resetMarkers(handler, json_array); resetMap(handler, latlng); // listen for pan/zoom and submit new coordinates (function gmaps4rails_callback() { google.maps.event.addListener(handler.getMap(), 'idle', function() { var bounds = handler.getMap().getBounds().toUrlValue(); var center = handler.getMap().getCenter().toUrlValue(); $.get('/sightings/search.js',{bounds: bounds, center: center, old_hash: #{raw @hash.to_json}}); }) })(); }); 

search.js.erb

 (function() { var json_array = ; if () { var latlng = ; resetMarkers(handler, json_array); resetMap(handler, latlng); } else { resetMarkers(handler, json_array); } })(); 

map.js

 (function() { function createSidebarLi(json) { return ("
  • " + json.name + "
  • "); }; function bindLiToMarker($li, marker) { $li.on('click', function() { handler.getMap().setZoom(18); marker.setMap(handler.getMap()); //because clusterer removes map property from marker google.maps.event.trigger(marker.getServiceObject(), 'click'); }) }; function createSidebar(json_array) { _.each(json_array, function(json) { var $li = $( createSidebarLi(json) ); $li.appendTo('#sightings_container'); bindLiToMarker($li, json.marker); }); }; function clearSidebar() { $('#sightings_container').empty(); }; function clearZipcode() { $('#zipform')[0].reset(); }; /* __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 resetMarkers(handler, json_array) { 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); } }; function resetMap(handler, latlng) { handler.bounds.extendWith(__markers); handler.fitMapToBounds(); handler.getMap().setZoom(12); handler.map.centerOn({ lat: latlng[0], lng: latlng[1] }); } // "Publish" our method on window. You should probably have your own namespace window.resetMarkers = resetMarkers; window.resetMap = resetMap; })();

    这是问题所在,这与这个具体的例子有关,因为我似乎误解了javascript(我是新手)变量是如何工作的。 当用户平移和缩放,但搜索结果相同时,我宁愿不调用“resetMarkers”函数,而宁愿只留下地图。 该地图目前将始终重置漫游者/侧边栏等,这会在屏幕上产生一点点标记。

    我已经尝试了几种不同的版本,但是没有用。 在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); } }; 

    由于__markers似乎在页面的生命周期中保持其价值(我们在设置新标记之前使用它来删除旧标记),我想我可以简单地创建另一个变量来检查它。 然而,即使我认为它应该是真的,它总是错误的。

    我尝试过的另一件事是重新提交旧哈希作为每个搜索请求的参数,然后设置一个标志,但这看起来很复杂,并且字符串/哈希/数组操作变得如此令人困惑我放弃了。 我真的不认为这是最好的方法,但也许我应该这样做?

    或者,是否有一些我完全错过的东西而应该做的呢?

    您的问题在于比较两个标记列表以决定是否应该更新。

    问题是,虽然_.isEqual(__oldmarkers, __markers)确实进行了深度比较,但是列表中的Marker实例中可能会发生甚至相同点(id,timestamps,……)的变化。
    或许它只是因为在开始时, __markers__oldMarkers都是null ,因此是相等的,这意味着你永远不会进入if块。

    无论如何,我认为这里的深度比较可能会变得太昂贵。 我要做的是比较容易比较的东西,比如每组标记的平面坐标列表。

    像这样的东西:

     var __markers, __coordinates = []; function resetMarkers(handler, json_array) { var coordinates = _.map(json_array, function(marker) { return String(marker.lat) + ',' + String(marker.lng); }); if(_.isEqual(__coordinates.sort(), coordinates.sort())) { 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); } __coordinates = coordinates; } }; 

    这里__coordinatescoordinates只是String的平面数组,应该快速比较并给出预期的结果。
    在使用_.isEqual进行比较时,两个数组都是预先排序的。

    注意:旧代码使用_.difference但不正确(请参阅注释中的讨论)
    (请注意,我使用_.difference ,可能比_.isEqual更昂贵,但奖励是独立于返回的标记顺序。)

    编辑:哦,当然你现在可以停止在搜索查询参数中发送“oldHash”;)