Search code examples
javascriptruby-on-railsgoogle-mapsgmaps4rails

Gmaps4rails - Rails 4


I'm trying to figure out how to get a map to display the address a user creates.

I have an address model and a projects model.

The associations are:

Address:

 belongs_to :addressable, :polymorphic => true

Project:

has_many :addresses, as: :addressable
      accepts_nested_attributes_for :addresses,  reject_if: :all_blank, allow_destroy: true

In address.rb, I have:

def full_project_address_formal
    [self.first_line, middle_line, last_line, country_name].compact.join("<br>").html_safe
  end

I also then have:

def first_line
    [unit, street_number, street.titlecase].join(' ')
  end

  def middle_line
    if self.building.present? 
    end
  end

  def last_line
    [city.titlecase, region.titlecase, zip].join('   ')
  end

  def country_name
    country = self.country
    ISO3166::Country[country]
  end

In my show page, I then have:

<%= @project.addresses.first.project_address %>

All of that works to display the project address.

I then want to display a map.

I have installed these gems:

gem 'geocoder'
gem 'gmaps4rails'
gem 'underscore-rails'
gem 'countries'
gem 'country_select'

In my application.js, I have:

//= require gmap3.min
//= require underscore

In my address.rb, I have:

geocoded_by :full_project_address_formal   # can also be an IP address
after_validation :geocode      

In my address controller I have (I don't know why - I copied it from another post on SO - although my address model does have latitude and longitude in the schema):

def index
  @addresses = Address.all
  authorize @addresses

  @hash = Gmaps4rails.build_markers(@addresses) do |address, marker|
    marker.lat address.latitude
    marker.lng address.longitude
    end
  end

I'm completely lost for what to do next. I've tried at least 30 different ways of trying to display the address in a map.

<strong>PROJECT HQ</strong>
                                <ul class="map-list medium-text">
                                    <%= @project.addresses.first.project_address %>
                                    <!-- <li><span data-icon="&#xe01e;"></span>[email protected]</li> -->
                                </ul>

                            </div>
                        </div>
                        <div class="two-cols-column">
                            <div id="map">

                            </div>
                        </div>

In my main.js file, I have:

if (dp.fn.gmap3) {
        var target_map = dp('#map');
        var lat = target_map.data('lat');
        var lng = target_map.data('lng');
        target_map.gmap3({
                marker: {
                latLng: [lat, lng],
                draggable: false
            },
                map: {
                      options: {
                            zoom: 12,
                    scrollwheel: false,
                    disableDefaultUI: true,
                    styles:
                            [{"featureType": "landscape.man_made", "elementType": "geometry", "stylers": [{"color": "#f7f1df"}]}, {"featureType": "landscape.natural", "elementType": "geometry", "stylers": [{"color": "#d0e3b4"}]}, {"featureType": "landscape.natural.terrain", "elementType": "geometry", "stylers": [{"visibility": "off"}]}, {"featureType": "poi", "elementType": "labels", "stylers": [{"visibility": "off"}]}, {"featureType": "poi.business", "elementType": "all", "stylers": [{"visibility": "off"}]}, {"featureType": "poi.medical", "elementType": "geometry", "stylers": [{"color": "#fbd3da"}]}, {"featureType": "poi.park", "elementType": "geometry", "stylers": [{"color": "#bde6ab"}]}, {"featureType": "road", "elementType": "geometry.stroke", "stylers": [{"visibility": "off"}]}, {"featureType": "road", "elementType": "labels", "stylers": [{"visibility": "off"}]}, {"featureType": "road.highway", "elementType": "geometry.fill", "stylers": [{"color": "#ffe15f"}]}, {"featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [{"color": "#efd151"}]}, {"featureType": "road.arterial", "elementType": "geometry.fill", "stylers": [{"color": "#ffffff"}]}, {"featureType": "road.local", "elementType": "geometry.fill", "stylers": [{"color": "black"}]}, {"featureType": "transit.station.airport", "elementType": "geometry.fill", "stylers": [{"color": "#cfb2db"}]}, {"featureType": "water", "elementType": "geometry", "stylers": [{"color": "#a2daf2"}]}]
                      }
                }

          });
    }

I"m lost and stuck. Does anyone know how to get from where I am to the point where I can take an address and produce a map in my show?

A SECOND ATTEMPT. HAVING WATCHED THIS YOU TUBE VIDEO: https://www.youtube.com/watch?v=R0l-7en3dUw&feature=youtu.be

I'm still stuck, but not sure how to approach solving this problem. My complete address setup is now:

gems:

gem 'geocoder'
gem 'gmaps4rails'
gem 'underscore-rails'
gem 'countries'
gem 'country_select'

application.js

//= require underscore
//= require gmaps/google

vendor/assets/javascript/ underscore.js

I copied and pasted the entire underscore production version. In the video, that appears to be 1 uncommented line. I couldn't find that. Instead, I have a long page of gibberish.

address model:

class Address < ActiveRecord::Base


  geocoded_by :full_address   # can also be an IP address
  before_save :capitalise_address
  before_save :upcase_zip
  # --------------- associations

    belongs_to :addressable, :polymorphic => true

  # --------------- scopes

  # --------------- validations
    validates_presence_of :street_number, :street, :zip, :country 


  # --------------- class methods

  def first_line
    [unit, street_number, street.titlecase].join(' ')
  end

  def middle_line
    if self.building.present? 
    end
  end

  def last_line
    [city.titlecase, region.titlecase, zip].join('   ')
  end


  def country_name
    country = self.country
    ISO3166::Country[country]
  end


  def full_address
    [self.first_line, middle_line, last_line, country_name].compact.join("<br>").html_safe
  end


  after_validation :geocode#, if  self.full_address.changed? 


end

address controller:

def index
  @addresses = Address.all
  authorize @addresses

  @hash = Gmaps4rails.build_markers(@addresses) do |address, marker|
    marker.lat address.latitude
    marker.lng address.longitude
    end
  end

I don't know why this is in an index action. I"m trying to use a projects/show action. I've not understood the reasons for the above step.

projects show:

<div id="map">
    <%= render partial: "address" %>
</div>

views/projects/_address.html.erb

<script src="//maps.google.com/maps/api/js?v=3.18&sensor=false&client=&key=&libraries=geometry&language=&hl=&region="></script> 
<script src="//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js"></script>
<script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/infobox/1.1.9/src/infobox_packed.js' type='text/javascript'></script> <!-- only if you need custom infoboxes -->

Not sure if this file is supposed to have any html in it. following this tutorial ( https://github.com/apneadiving/Google-Maps-for-Rails), I've no clue as to how the maps (even if it is working, would know what address to use.

app/assets/javascripts/addresses.js

handler = Gmaps.build('Google');
handler.buildMap({ provider: {}, internal: {id: 'map'}}, function(){
  markers = handler.addMarkers(<%=raw @hash.to_json %>);
  handler.bounds.extendWith(markers);
  handler.fitMapToBounds();
});

not sure if Im supposed to put the above inside script tags

I'm lost. At this point, I have an error that says:

ExecJS::RuntimeError at /projects/26
SyntaxError: [stdin]:13:32: unexpected <

I don't know where to look for the error because I don't know what anything is supposed to look like. I'm lost and stuck. I have read the google api documentation (it assumes knowledge of js at a level that I don't have) and all of the geocoder and gmaps4rails wiki documentation.

A THIRD ATTEMPT

I found this wiki resource, which gives template js and html. http://apneadiving.github.io

I replace the content of my addresses.js file with:

var handler = Gmaps.build('Google');
handler.buildMap({ internal: {id: 'geolocation'} }, function(){
  if(navigator.geolocation)
    navigator.geolocation.getCurrentPosition(displayOnMap);
});

function displayOnMap(position){
  var marker = handler.addMarker({
    lat: position.coords.latitude,
    lng: position.coords.longitude
  });
  handler.map.centerOn(marker);
};

And add make the content of my partial:

<script src="//maps.google.com/maps/api/js?v=3.18&sensor=false&client=&key=&libraries=geometry&language=&hl=&region="></script> 
<script src="//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js"></script>
<script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/infobox/1.1.9/src/infobox_packed.js' type='text/javascript'></script> <!-- only if you need custom infoboxes -->


    <div style='width: 800px;'>
        <div id="geolocation" style='width: 800px; height: 400px;'></div>
    </div>

I get an error that says:

ExecJS::RuntimeError at /projects/26
SyntaxError: [stdin]:2:1: reserved word 'var'

Thanks to Max below, for the help with removing the '.coffee' extension from my js files. Now, there is just a blank space where the map was supposed to be.


Solution

  • If I understood you correctly, you want to display a map on a project's show page along with the project's address/addresses.

    The @hash variable (from the tutorial) contains the required information to create markers on the map (eg. latitude, longitude etc) and it is used as markers = handler.addMarkers(<%=raw @hash.to_json %>); which then actually adds the markers with that information.

    In your case, since you need the map on project's show page, you should set @hash in show action of the ProjectsController with @addresses set to whatever addresses you want to display on the page - presumably @project.addresses, where @project value is set before @addressses.

    I think your lat & lng are stored not in fields/columns named lat & lng but latitude & longitude in which case you need to mention them specifically like this - geocoded_by :full_project_address_formal, latitude: :lat, longitude: :lng. You should make sure that the values of latitude & longitude fields are indeed present for the addresses in the database to be safe.

    def show
      @project = Project.find(params[:id])
      @addresses = @project.addresses
    
      @hash = Gmaps4rails.build_markers(@addresses) do |address, marker|
        marker.lat address.latitude
        marker.lng address.longitude
        marker.infowindow address.full_project_address_formal
      end
    end
    

    Once you set the value of @hash in ProjectsController's show action, you can access it inside a script tag in projects/show view. To start with, you can keep the show view simple as this.

    <script src="//maps.google.com/maps/api/js?v=3.18&sensor=false&client=&key=&libraries=geometry&language=&hl=&region="></script> 
    <script src="//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js"></script>
    <script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/infobox/1.1.9/src/infobox_packed.js' type='text/javascript'></script> <!-- only if you need custom infoboxes -->
    
    <div style='width: 800px;'>
      <div id="map" style='width: 800px; height: 400px;'></div>
    </div>
    
    <script>
    handler = Gmaps.build('Google');
    handler.buildMap({ provider: {}, internal: {id: 'map'}}, function(){
      markers = handler.addMarkers(<%=raw @hash.to_json %>);
      handler.bounds.extendWith(markers);
      handler.fitMapToBounds();
    });
    </script>
    

    Hope this helps. If you get stuck somewhere or need assistance, let me know.