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

时间:2015-03-04 20:32:50

标签: javascript ruby-on-rails ajax gmaps4rails

我一直在编写一个小应用程序,其中有很多来自stackoverflow的帮助。基本前提很简单,我在网络上看到了这种功能:我试图在可搜索/可搜索的谷歌地图上绘制一个位置列表。位置存储在后端,控制器将这些位置提供给视图。涉及AJAX因为我不想重新加载整个页面。以下是场景:a)用户通过zipcode =>搜索位置如果设置半径内有任何标记,则地图加载新位置,搜索获取发送到服务器并映射加载任何标记,地图设置默认缩放级别; 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(我对它的新变化)的变量是如何工作的。当用户平移和缩放,但搜索结果相同时,我宁愿不调用&#34; resetMarkers&#34;功能,但宁愿只留下地图。该地图目前将始终重置漫游者/侧边栏等,这会在屏幕上产生一点点标记。

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

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

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

1 个答案:

答案 0 :(得分:2)

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

问题是,尽管_.isEqual(__oldmarkers, __markers)确实进行了深入的比较,但是列表中的Marker实例中可能会发生更改,即使是相同的点(id,时间戳,......)也会发生变化。 或许这只是因为在开始时,__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”;)