【问题标题】:Google Maps For Rails - updating markers via ajax only when search result changesGoogle Maps For Rails - 仅在搜索结果更改时通过 ajax 更新标记
【发布时间】:2015-03-04 20:32:50
【问题描述】:

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

使用 gmaps4ails wiki 和这个问题的答案的修改版本:Google Maps for Rails - update markers with 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”函数,而宁愿不理会地图。地图当前将始终重置标记/侧边栏/等,这会导致屏幕上的标记有点闪烁。

我已经尝试了几个不同的版本,但都不起作用。在 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 似乎在页面的整个生命周期中都保持其价值(我们在设置新标记之前使用它来删除旧标记),我想我可以简单地创建另一个变量来检查它。然而,即使我认为它应该是真的,它也总是假的。

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

或者,是否有什么我完全遗漏而应该做的事情?

【问题讨论】:

    标签: javascript ruby-on-rails ajax gmaps4rails


    【解决方案1】:

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

    问题是,尽管_.isEqual(__oldmarkers, __markers) 确实执行了深度比较,但您列表中的标记实例中的某些内容即使对于相同的点(ID、时间戳等)也会发生变化。
    或者可能只是因为一开始__markers__oldMarkers 都是null,因此相等,这意味着你永远无法进入ifblock。

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

    类似这样的:

    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 只是字符串的平面数组,应该快速比较它们并为您提供预期的结果。
    为了使用_.isEqual 进行比较,两个数组都预先排序。

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

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

    【讨论】:

    • 感谢奥利维尔!您的回答看起来不错,并且有道理,但是没有用。我花了一段时间才弄清楚为什么!首先有一个错字,应该是marker.lat 和marker.lng,但这不是你的错,当我弄清楚时,我以为我已经完成了。但我不是!
    • 迷茫中看了半天,终于发现_.difference是问题所在。 _.difference([1], null) = 1 (length = 1) 但是 _.difference(null,[1]) = [blank] (length = 0)。事实证明,我需要同时检查: if((.difference(coordinates, _coordinates).length > 0) || (.difference(__coordinates, coordinates).length > 0 )) 因此,尽管差异具有独立于数组元素顺序的好处,但差异本身取决于参数顺序!明天我会考虑是否有更便宜的比较。感谢 Olivier 的帮助指路!
    • 嗯,我刚刚尝试并得到:_.difference([1], null) =&gt; [1]_.difference(null, [1]) =&gt; [1] 我敢打赌您使用的是 underscore.js,而我个人更喜欢并使用 lodash,它是一种更高效的替代下划线。您可以尝试改用它:)
    • 啊,是的,我正在使用下划线。我会试试lodash!不过,如果 _.difference 不同,那么它们就不是彼此的替代品!
    • 现在使用 lodash,'null' 的例子肯定是不同的。这对于循环很重要,因为当页面首次加载时,_coordinates 将为空,所以我需要首先放置标记。但是,我仍然需要同时检查 (.difference(_coordinates, coordinates)) 和 (.difference(coordinates, __coordinates)) 因为下划线和 lo-dash 在第二个中返回值不在第一个的数组。例如, _.difference([1,2,3],[1] = [2, 3], but _.difference([1], [1,2,3]) = null 。所以我需要检查如果标记被添加或减去,这两种情况都会发生。
    猜你喜欢
    • 1970-01-01
    • 2016-03-11
    • 2013-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    • 2020-03-15
    相关资源
    最近更新 更多