GoogleMapsForRails-只有在搜索结果发生变化时才通过ajax更新标记

Google Maps For Rails - updating markers via ajax only when search result changes

本文关键字:ajax 更新 变化 搜索结果 GoogleMapsForRails-      更新时间:2023-09-26

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

使用gmaps4ails-wiki和这个问题的答案的主要修改版本:GoogleMapsforRails-使用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 = <%= 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()); //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"函数,而宁愿不调用地图。地图目前总是重置markers/边栏等,这会导致屏幕上的标记闪烁。

我试过几种不同的版本,但都不起作用。在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)确实执行了深入的比较,但即使对于相同的点(id、时间戳…),列表中的Marker实例中也可能会发生变化。
或者,这可能只是因为在一开始,__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进行比较的顺序,两个数组都是预先排序的。

NB:旧代码使用了_.difference,但这是不正确的(请参阅评论中的讨论)
(注意,我使用的是_.difference,可能比_.isEqual更贵,但额外的好处是独立于返回的标记顺序。)

edit:哦,当然你现在可以停止在搜索查询params中发送"oldHash"了;)