JPN
JPN

Reputation: 632

How to do Turbolinks + Gmaps4Rails 2?

I'm using Turbolinks & Google Maps 4 Rails. For performance reasons, I'm trying to have all the javascript load async.

I find that the map page loads correctly if I'm coming from another page from within the same website. But if I just type in the address in the browser and go to the maps page, I get the two errors:

Uncaught ReferenceError: Gmaps is not defined

Uncaught ReferenceError: jQuery is not defined

Here is my setup:

In my head (in haml)

%script{ src:'//maps.google.com/maps/api/js?v=3.13&sensor=true&libraries=geometry', type:'text/javascript' }
-# http://www.rickypai.com/blog/2013/02/22/web-dev-gotchas-with-rails-4-0-turbolinks/
- if Settings.ASYNC_JAVASCRIPT
    = render 'layouts/async_javascript', path: 'application'
- else
    = javascript_include_tag 'application', data_turbolinks_track: true

_async_javascript.html.erb

<%# http://railscasts.com/episodes/369-client-side-performance?view=asciicast %>
<script type="text/javascript">
    (function() {
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.async = true;
        script.src = '<%= j javascript_path(path) %>';
        var other = document.getElementsByTagName('script')[0];
        other.parentNode.insertBefore(script, other);
    }) ();
</script>

application.js

//= require modernizr
//= require jquery
//= require jquery.turbolinks
//= require google.maps.infobox
//= require google.maps.richmarker
//= require google.maps.markerclusterer
//= require gmaps/google
...
//= require turbolinks

Then finally, in the body (in haml)

- present @location, LocationPresenter do |p|
    = render 'map_coffee', id: 'l_map', markers: [p.marker].to_json, lat: p.latitude, lng: p.longitude

_map.coffee.html.haml

:coffee
    class RichMarkerBuilder extends Gmaps.Google.Builders.Marker

        create_marker: ->
            options = _.extend @marker_options(), @rich_marker_options()
            @serviceObject = new RichMarker options

        rich_marker_options: ->
            marker = document.createElement 'div'
            marker.setAttribute 'class', 'marker_container'
            marker.innerHTML = @args.marker
            _.extend @marker_options(), { content: marker }

        create_infowindow: ->
            return null unless _.isString @args.infowindow

            boxText = document.createElement 'div'
            boxText.setAttribute 'class', 'infowindow_container'
            console.log @args
            boxText.innerHTML = @args.infowindow
            @infowindow = new InfoBox(@infobox(boxText))

            @bind_infowindow()

        infobox: (boxText)->
            content: boxText
            pixelOffset: new google.maps.Size(0, 0)
            boxStyle:
                width: '280px'


    handler = Gmaps.build 'Google', { builders: { Marker: RichMarkerBuilder }, markers: { maxRandomDistance: null } }

    handler.buildMap
        provider: {}
        internal:
            id: '#{id}'
    , ->
        json = #{markers}
        if json
            markers = handler.addMarkers json
            handler.bounds.extendWith markers
            handler.fitMapToBounds()
            if json.length == 1
                handler.getMap().setZoom #{Settings.LOCATIONS_DEFAULT_ZOOM}
        else
            handler.map.centerOn { lat: #{lat}, lng: #{lng} }
            handler.getMap().setZoom #{Settings.LOCATIONS_DEFAULT_ZOOM}

Does anyone know how to set this up correctly? I find that there is much documentation with regards to this, which is surprising since Turbolinks is standard for Rails 4. Any help would greatly be appreciated!

For those who are also struggling with this, I'll update the Gmaps4Rails wiki as soon as I understand how to do this!

Upvotes: 1

Views: 711

Answers (1)

JPN
JPN

Reputation: 632

Best way (and cleanest) I figured out is to add the javascript to the asset pipeline. This will ensure that JQuery is available. So something like this in some coffeescript file:

$('#map').mappy()

Where you define the JQuery plugin Mappy like this:

(($, window) ->

    class Mappy
        # defaults:
        constructor: (el, options) ->
            @$el = $(el)
            # @options = $.extend({}, @defaults, options)

            @id         = @$el.attr 'id'
            @lat        = @$el.data 'lat'
            @lng        = @$el.data 'lng'
            @markers    = @$el.data 'markers'
            @handler    = Gmaps.build 'Google', { builders: { Marker: RichMarkerBuilder }, markers: { maxRandomDistance: null } }

            @handler.buildMap
                provider: {}
                internal:
                    id: @id
            , =>
                json = @markers
                if json
                    markers = @handler.addMarkers json
                    @handler.bounds.extendWith markers
                    @handler.fitMapToBounds()
                    if json.length == 1
                        @handler.getMap().setZoom $.LOCATIONS_DEFAULT_ZOOM
                else
                    @handler.map.centerOn { lat: @lat, lng: @lng }
                    @handler.getMap().setZoom $.LOCATIONS_DEFAULT_ZOOM


    class RichMarkerBuilder extends Gmaps.Google.Builders.Marker

        create_marker: ->
            options = _.extend @marker_options(), @rich_marker_options()
            @serviceObject = new RichMarker options

        rich_marker_options: ->
            marker = document.createElement 'div'
            marker.setAttribute 'class', 'marker_container'
            marker.innerHTML = @args.marker
            _.extend @marker_options(), { content: marker }

        create_infowindow: ->
            return null unless _.isString @args.infowindow

            boxText = document.createElement 'div'
            boxText.setAttribute 'class', 'infowindow_container'
            console.log @args
            boxText.innerHTML = @args.infowindow
            @infowindow = new InfoBox(@infobox(boxText))

            @bind_infowindow()

        infobox: (boxText)->
            content: boxText
            pixelOffset: new google.maps.Size(0, 0)
            boxStyle:
                width: '280px'

    $.fn.extend mappy: (option, args...) ->
        @each ->
            $this = $(this)
            data = $this.data('mappy')

            if !data
                $this.data 'mappy', (data = new Mappy(this, option))

            if typeof option == 'string'
                data[option].apply(data, args)

) window.jQuery, window

Then make the data available by doing something like this in your haml

- present @location, LocationPresenter do |p|
     #map{ data: {  markers: [p.marker].to_json, lat: p.latitude, lng: p.longitude } }

Upvotes: 2

Related Questions