Make Gmaps4Rails and Grid-a-Licious Work Together

Cover image

For my Coding Da Vinci Project "Wiederaufbau Ost-Berlin" we decided to show a list of building-names and link them to markers on Google Maps.

I'm using Gmaps4Rails to convert latitude- and longitude-values to markers:

# /app/controllers/buildings_controller.rb
def index
  @buildings = Building.all.order(:id)
  @hash = Gmaps4rails.build_markers @buildings do |building, marker|
    marker.json ({id: building.id, name: building.name}) # we'll need the building's ID later!
    marker.lat building.latitude
    marker.lng building.longitude
    marker.infowindow render_to_string(partial: "/buildings/infowindow", locals: {object: building})
  end
end

I first used this example to build the list of links and it was working fine. But then I wanted to style the list a bit and make it responsive. I decided to use Grid-A-Licious - a grid prettifier written in jQuery. I used <div class="grid-item"> to wrap my building-names inside, but this totally broke the link between a building-name and its marker on the map, because Gmaps4Rails' function bindLiToMarker() binds the marker to an object, but this object vanishes when the user resizes the browser window. And the object vanishes because Grid-A-Licious rebuilds each grid-item according to the new window's size. Bummer!

Here is my solution

First, generate the map and the grid with building-names separately:

<!-- /app/views/buildings/index.html.erb -->

<div class="container">
<div id="buildings-map" style='width: 100%; height: 450px;'></div>
</div>

<div class="container">
<div id="buildings-grid"></div>
</div>

<script type="text/javascript">
   buildingsMap( <%= raw @hash.to_json %> );
   buildBuildingsList( <%= raw @hash.to_json %> );
</script><br />
/* /app/assets/javascripts/application.js.erb */
Gmaps.store = {};

function buildingsMap(json_array){
  Gmaps.store.handler = Gmaps.build('Google');
  Gmaps.store.handler.buildMap({ provider:{}, internal: {id: 'buildings-map'}}, function(){

    Gmaps.store.markers = Gmaps.store.handler.addMarkers(json_array);

    _.each(json_array, function(json, index){
      marker = Gmaps.store.markers[index];
      marker.serviceObject.set('id', json.id); // those are the buildings' IDs
      json.marker = marker;
    });

    Gmaps.store.handler.bounds.extendWith(Gmaps.store.markers);
    Gmaps.store.handler.fitMapToBounds();
  });
}

function buildGridItem(json){
 div = document.createElement('div');
 div.setAttribute('class', 'grid-item');
 div.setAttribute('id', json.id); // we give each div the ID of the building, it represents
 div.innerHTML = ("<a href='#map-anchor'><span class='glyphicon glyphicon-map-marker'> " + json.name + "</a>");

 return div;
}

function buildBuildingsList(json_array){
  _.each(json_array, function(json){
    $address = $( buildGridItem(json) );
    $("#buildings-grid").append($address);
  });
  // grid-a-licious funtion settings
  $('#buildings-grid').gridalicious({
    gutter: 20,
    width: 250,
    selector: '.grid-item'
  });
}

Second, when the document has loaded or whenever the browser window has resized, link the grid-item again to the marker:

<!-- /app/views/buildings/index.html.erb -->
<script type="text/javascript">
  $(document).ready(function(){
    bindGridItemsToMarker();
  });

  $(window).resize(_.debounce(function(){
    bindGridItemsToMarker();
  },500));
</script>

_.debounce() is a method from underscore.js and it waits until all resize calls on the browser window has been executed.

/* /app/assets/javascript/application.js.erb */

function findMarker(i){
  for (var l=0; l<Gmaps.store.markers.length; ++l){
    marker = Gmaps.store.markers[l];
    if (marker.getServiceObject().id == i ) {
      return marker;
    }
  }
  return Gmaps.store.markers[0];
}

function bindGridItemsToMarker(){
  $('.grid-item').on('click', function(){
    i = $(this).attr('id');

    marker = findMarker(i);

    Gmaps.store.handler.getMap();
    marker.setMap(Gmaps.store.handler.getMap()); //because clusterer removes map property from marker
    marker.panTo();
    google.maps.event.trigger(marker.getServiceObject(), 'click');
    Gmaps.store.handler.fitMapToBounds();
  });
}

What we basically do is, whenever the user clicks on the link (in my case the building's name), we extract the grid-item's id-attribute, find the marker on the map with the same id and click on it. And that's it!

Here is the result and here is the code to our Coding Da Vinci project on GitHub.