47

I'm building a Rails 4 app and I have a few scattered js files that I'm trying to include "the rails way". I moved jquery plugins into the /vendor/assets/javascripts and I updated the manifest (application.js) to require them. When I load the pages locally I see that they appear correctly.

However, I'm getting inconsistent behavior from one of the scripts that's compiled. I have a controller-specific js file called projects.js which is referenced in the application.js via the use of require_tree .:

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap.min
//= require jquery.form.min
//= require_tree .

I see the file is included correctly, and it works... half the time. The other half of the time, the stuff in projects.js doesn't seem to do anything (it's mostly jquery animations and some ajax requests). When it doesn't work, I'll click buttons a couple times, nothing will happen, and then I'll throw this error:

Uncaught TypeError: Cannot read property 'position' of null 
turbolinks.js?body=1:75

This never happened when the scripts were in the individual views (the wrong way), so I'm fairly certain the problem is not with my javascript code. One other potentially relevant details is that the stuff in projects.js is wrapped in a $(document).ready(function(){. Also, I'm testing in development mode so the javascripts and css are not combined by the asset pipeline.

Any idea what's going on here? I'm new to Rails but I've done my best to follow all the conventions.

Update!

It's predictable when my project scripts don't work. The first page load works every time. Then I click one link to a new page which uses my project.js behaviors and the second page never works. I click a few times and eventually throw the error above. I'm still not sure why, but I'm suspecting this is related to turbo-linking.

Veger
  • 34,172
  • 10
  • 101
  • 111
emersonthis
  • 30,934
  • 52
  • 191
  • 328
  • 1
    One line solution (CoffeeScript): `$(document).on 'ready page:load', ->` – Chloe Jan 27 '14 at 02:15
  • This post solved the problem with a very good example. http://stackoverflow.com/questions/18770517/rails-4-how-to-use-document-ready-with-turbo-links – Chris Jul 16 '16 at 11:18

3 Answers3

44

$(document).ready(function(){ doesn't really work with Turbolinks. Turbolinks:

... makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, it keeps the current page instance alive and replaces only the body and the title in the head.

So the page is only loaded once and then pieces are replaced as needed. Since the page is only loaded once, your $(document).ready() callbacks are only triggered when the site is initially visited, you won't get more document-ready events when switching pages because Turbolinks doesn't actually switch pages. From the fine manual:

With Turbolinks pages will change without a full reload, so you can't rely on DOMContentLoaded or jQuery.ready() to trigger your code. Instead Turbolinks fires events on document to provide hooks into the lifecycle of the page.

You probably want to listen for one of the Turbolinks events instead:

  • page:change the page has been parsed and changed to the new version and on DOMContentLoaded
  • [...]
  • page:load is fired at the end of the loading process.

page:change is usually what you're looking for as it is triggered when loading a fresh page and restoring a page from the Turbolinks page cache.

You might want to turn off Turbolinks until you've had time to review all of your JavaScript and you've done a full QA sweep. You should also test your site's speed to see if it is worth using at all.

Another option would be to use jquery.turbolinks to patch up things for you. I haven't used this but other people are using it to good effect.

Community
  • 1
  • 1
mu is too short
  • 396,305
  • 64
  • 779
  • 743
  • Thanks!! You'll see from my update (just added) that what you said fits exactly with what I'm seeing. However I'm a little confused as to what (if anything) my "page-specific" js code should be wrapped in. For example, if I put everything in application.js, should I just jump right into the events, etc? `No $(document).ready...`? – emersonthis Sep 12 '13 at 17:04
  • On second thought, you're answer solves the problem of this question. Turbo-linking is preventing the ready event. So I created a new question to address the follow-up question of how it should be done: http://stackoverflow.com/questions/18770517/rails-4-how-to-use-document-ready-with-turbo-links – emersonthis Sep 12 '13 at 17:19
  • 3
    The Turbolinks docs say that it triggers its own `page:...` events on `document` so `$(document).on('page:change', function() { ... })` and such. – mu is too short Sep 12 '13 at 17:46
  • for rails 5, you can simply say `$(document).on('turbolinks:load', function() { ... });` – Mark Oct 25 '16 at 04:27
28

A solution to this is already available at this railscast

Here is one example (the one i like to use).

ready = ->
  # ..... your js

$(document).ready(ready)
$(document).on('page:load', ready)

There is also a gem that solves this issue in a way that lets you keep doing it the old way. It works really well :)

Ole Henrik Skogstrøm
  • 5,714
  • 10
  • 53
  • 79
1

I realize this Answer is REAAAAAAALLLLLLLYYYYY Late... but I figured I'd add it.

I just had this issue last week.. While trying to get Foundation to work..

First I added:

gem 'jquery-turbolinks'

Then Placed it in application.js like so:

//= require jquery.turbolinks
//= require jquery_ujs
//= require turbolinks
//= require_tree .

and then I added this right below it:

$(document).on('turbolinks:load', function() {

});

and everything worked for me. Hope this helps!

Kick Buttowski
  • 6,371
  • 12
  • 34
  • 54
Shawn Wilson
  • 1,180
  • 10
  • 27
  • 2
    You really don't need `jquery-turbolinks` gem anymore. New turbolinks 5 works just fine – Anwar May 22 '17 at 15:53