Rails 4, turbolinks, and coffeescript

371 views Asked by At

Forgive me if I'm a bit confused, I'm venturing out into new terratory in my programming, and learning as I go. The basic concept I'm struggling with:

In my Rails 4 app I'm using Leaflet (a JS library for mapping) and calling some of my own code. Long story short, I was unable to get Leaflet and Turbolinks to play well together so for the pages in my site that need Leaflet I've disabled Turbolinks, additionally I'm loading page specific Coffeescript.

I've now decided that I want to add a back button. The usage would go like this - from a page that is using Leaflet, the user can click on a piece of the map and be taken to a page that uses Turbolinks (if it matters). From the Turbolinks page, I have a back button to take you back to the Leaflet map.

However, when I get back to the Leaflet page, my coffeescript either isn't being run, or is being run at the wrong time (I get several errors that weren't there the first time it ran). I'm guessing I would need to add another event at the beginning of the Coffeescript or choose a different event, but I'm just not sure what it should be.

I am in no way wedded to not using Turbolinks on the Leaflet page if you have a suggestion for how to make it work and it helps to get the back button to work. :)

_navbar.html.erb

<li><%= link_to("By grid", grid_path, data: {turbolinks: false}) %></li>

grid.html.erb

... code for layout goes here ...
<% content_for :header do %>
  <%= javascript_include_tag "leaflet" %>
<% end %>
<% content_for :javascript do %>
  <%= javascript_include_tag "leaflet-maps/#{filename}" %>
<% end %>

leaflet-maps/grid.coffee

$ ->
  document.addEventListener 'page:restore', ->
    app.init()
    return
... rest of coffeescript goes here ...
... here is how the link is getting called
  layer.on 'click', (e) ->
    window.location.href='/show?grid='+feature.id
    return

show.html.erb

<%= link_to "Back", :back %>

Update

grid.coffee - full coffeescript code

ready = ->
#--------------------------------------------------
# Build tooltip with plant name 
#--------------------------------------------------
  onEachFeature = (feature, layer) ->
    if feature.properties and feature.properties.grid_name
      layer.bindTooltip feature.properties.grid_name
      layer.on 'click', (e) ->
        window.location.href='/show?grid='+feature.id
        return
    return

#--------------------------------------------------
# Variables
#--------------------------------------------------
  L.Icon.Default.imagePath = '/assets'
  gridStyle = {
    "color": "#ff7800",
  }
  neCorner = L.latLng([47.635103, -122.320525])
  swCorner = L.latLng([47.634083, -122.321129])


#--------------------------------------------------
# Set view and load Google Maps
#--------------------------------------------------
  map = L.map("map", zoomSnap: .25)
  map.fitBounds([swCorner, neCorner])
  map.invalidateSize(false)
  map.options.maxZoom = 22
  map.options.bounceAtZoomLimits = true
  googleLayer = L.gridLayer.googleMutant(type: 'roadmap').addTo(map)
  map.addLayer googleLayer

#--------------------------------------------------
# Get Ajax data
#--------------------------------------------------
  $.ajax
    dataType: 'text'
    url: 'grid.json'
    success: (data) ->
      L.geoJSON(JSON.parse(data), style: gridStyle, onEachFeature: onEachFeature ).addTo map
    error: ->
      alert "Failed to load AJAX data"

document.addEventListener 'turbolinks:load', ready()
document.addEventListener 'DOMContentLoaded', ready()
1

There are 1 answers

1
Lanny Bose On

So, there are two places where you might be going off-track. The first is here:

$ ->
  document.addEventListener 'page:restore', ->
    app.init()

What this is saying is... "when the web page emits the DOMContentLoaded event, then, when something else emits the page:restore event, then, run my JavaScript code."

Let's start with DOMContentLoaded. This is an event browsers emit by default, and which the jQuery framework uses to trigger event listeners. You can read more about jQuery's .ready() function here.

The page:restore event is NOT a standardly-emitted event, but was a custom event used in Turbolinks Classic (version <5.0, deprecated as of February 2016), which I believe you're using since you mentioned Rails 4.

Either way, stacking both listeners on top of one another is probably not what you want to do. What you could do it define a function to run on both ready and page:restore...

function ready() {
   // All of the setup you want to do...
}

document.addEventListener("turbolinks:load", ready());
document.addEventListener("DOMContentLoaded", ready());

(Sorry, I realize you're writing CoffeeScript. That's not my jam. I hope my JavaScript is OK...)

That's one. I see another opportunity for improvement.

layer.on 'click', (e) ->

What you're saying is "when the document is loaded, listen for clicks on the layer object, and then do this thing."

I'm not exactly sure what layer is, and if it's Leaflet-specific I probably won't be able to help you. However, there's actually a more resilient way to write this.

$(document).on("click","layer", function() {
  // do things with the layer
});

This might seem identical, but there's a subtle difference. It's saying "any time I click on the document, if I also happen to be clicking on a layer, do this thing."

This is more resilient because your line of JavaScript can always find the document when it's run. If your layer hasn't rendered by the time that line of code runs, it'll never catch those clicks.

Happy JavaScripting! :)