Rails 4: how to use $(document).ready() with turbo-links
Asked Answered
E

20

431

I ran into an issue in my Rails 4 app while trying to organize JS files "the rails way". They were previously scattered across different views. I organized them into separate files and compile them with the assets pipeline. However, I just learned that jQuery's "ready" event doesn't fire on subsequent clicks when turbo-linking is turned on. The first time you load a page it works. But when you click a link, anything inside the ready( function($) { won't get executed (because the page doesn't actually load again). Good explanation: here.

So my question is: What is the right way to ensure that jQuery events work properly while turbo-links are on? Do you wrap the scripts in a Rails-specific listener? Or maybe rails has some magic that makes it unnecessary? The docs are a bit vague on how this should work, especially with respect to loading multiple files via the manifest(s) like application.js.

Euxenite answered 12/9, 2013 at 17:16 Comment(0)
E
235

I just learned of another option for solving this problem. If you load the jquery-turbolinks gem it will bind the Rails Turbolinks events to the document.ready events so you can write your jQuery in the usual way. You just add jquery.turbolinks right after jquery in the js manifest file (by default: application.js).

Euxenite answered 16/9, 2013 at 17:49 Comment(14)
Awesome, thanks! Exactly what I need. The rails way. Convention.Clinician
Acording to the documentation, you should add //= require jquery.turbolinks to the manifest (e.g. application.js).Clotho
your answer mixed with Meltemi's one IS the perfect solutionCoverage
Very clean and useful, especially when you have integrated some third party theme along with its js files. Thanks!Ganger
@Coverage the two answers are mutually exclusive. 0.oVaios
thanks @gwho I forgot to update my comment few months ago when I had this issue. You're right!Coverage
This gem doesn't seem to execute with AJAX calls. how would you do that?Vaios
This answers is the correct one. Adding gem jquery-turbolinks helped me with this issue.Baler
You need to restart your rails server when you add this gemLangley
I still get issues getting turbolinks to work properly with select2 using this.Harass
This somehow still doesn't work for me. I've added it to my gemfile, I bundled installed. I added "// = requre jquery.turbolinks" to the application.js. Is there something I'm missing?Pride
Please note that Rails 4 is now defaulting to Turbolinks 5 which, in turn, is not supported by jquery.turbolinks! See github.com/kossnocorp/jquery.turbolinks/issues/56Protuberate
@Protuberate - thanks for this important clarification. So, for now, the best solution would be to remove turbolinks from the app.Pierpont
@Anwar - I remove it from Rails entirely now, to avoid issues with js not working in many cases.Pierpont
G
561

Here's what I do... CoffeeScript:

ready = ->

  ...your coffeescript goes here...

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

last line listens for page load which is what turbo links will trigger.

Edit...adding Javascript version (per request):

var ready;
ready = function() {

  ...your javascript goes here...

};

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

Edit 2...For Rails 5 (Turbolinks 5) page:load becomes turbolinks:load and will be even fired on initial load. So we can just do the following:

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

  ...your javascript goes here...

});
Grave answered 12/9, 2013 at 17:21 Comment(19)
Is that coffee script? I haven't learned it yet. Is there a js or jQuery equivalent to your example?Euxenite
Think I got it: var ready = function() { ... } $(document).ready(ready) $(document).on('page:load', ready) Is there any concern for both .ready and 'page:load' getting triggered?Euxenite
@Emerson there is a very useful website, with an explicit name: js2coffee.orgMilliemillieme
@Milliemillieme Thanks. I was just looking at it. I still don't entirely get the appeal of coffeescript but it looks pretty straight-forward, so I'll give it a try.Euxenite
1. What is the difference between using page:load and page:change 2. What is the difference between using $(document).on and $(document).bind ?Browse
One problem with this approach is that the ready function will be called twice.Peltz
doesn't it only get called when it needs to though? It calls it when the document is ready and then it calls it when the page loads (which we are assuming came from a turbo-link trigger). Right?Convertible
I can confirm that the ready function does not get called twice. If it's a hard refresh, only the ready event is fired. If it's a turbolinks load, only the page:load event is fired. It's impossible for both ready and page:load to be fired on the same page.Emlyn
Look at SDP's answer to his own question, it's the right way to do it. This will work but it's not the best way.Mcmullan
The page:change event seems to work for both hard refresh and turbolinks load. Anyone know a downside to using this instead of ready and page:load? The only one I can think of that might be a possibility is that page:change is an earlier event so the DOM may not be loaded all the way when your code is run. Not sure though.Clausen
This can be shortened to $(document).on('ready page:load', ready), no?Gebhart
FYI You can also apply this for multiple events page:load and ready by including a space-separated string for the first argumet. $(document).on('page:load ready', ready); The second argument is simply a function, which happens to be named ready, following this answer's example.Vaios
$(document).on('page:load ready', ready);' is the way to goFootwear
$(document).on('page:load', functionName); is definitely the way to go. Works with javaScript and jQueryFedericofedirko
This solution didn't work for me in Rails 4.1. Scroll down below for a solution with jquery.turbolinks that works.Tripterous
This DOES work for me in Rails 4.2.6 -- maybe it doesn't have to be Rails 5?Heine
For Rails 4.2.7, and Turbolinks 5, it looks like turbolinks will intercept page:load and only fire turbolinks:load. It works for me when I listen for the latter, not for the former. But Chrome confirmed that the browser did in fact fire page:loadBrooch
$(document).ready(ready) $(document).on('page:load', ready) kept firing twice for me. $(document).on('page:load ready', ready) works the way I expect it to.Jampack
If you have a code called on ready and turbolinks:load, it will be called twice. It's logic because if the code executed after the turbolinks:load event is called, it means that he has necessarily gone through the ready event. If you want that your code works on a page without turbolinks and on a page with, you need to put your code in a function, then do something like if (typeof Turbolinks === 'undefined') { $(document).ready(yourFunction); } else { $(document).on('turbolinks:load', yourFunction) }. So your code will be executed properly on a view with and without turbolinksUnder
E
235

I just learned of another option for solving this problem. If you load the jquery-turbolinks gem it will bind the Rails Turbolinks events to the document.ready events so you can write your jQuery in the usual way. You just add jquery.turbolinks right after jquery in the js manifest file (by default: application.js).

Euxenite answered 16/9, 2013 at 17:49 Comment(14)
Awesome, thanks! Exactly what I need. The rails way. Convention.Clinician
Acording to the documentation, you should add //= require jquery.turbolinks to the manifest (e.g. application.js).Clotho
your answer mixed with Meltemi's one IS the perfect solutionCoverage
Very clean and useful, especially when you have integrated some third party theme along with its js files. Thanks!Ganger
@Coverage the two answers are mutually exclusive. 0.oVaios
thanks @gwho I forgot to update my comment few months ago when I had this issue. You're right!Coverage
This gem doesn't seem to execute with AJAX calls. how would you do that?Vaios
This answers is the correct one. Adding gem jquery-turbolinks helped me with this issue.Baler
You need to restart your rails server when you add this gemLangley
I still get issues getting turbolinks to work properly with select2 using this.Harass
This somehow still doesn't work for me. I've added it to my gemfile, I bundled installed. I added "// = requre jquery.turbolinks" to the application.js. Is there something I'm missing?Pride
Please note that Rails 4 is now defaulting to Turbolinks 5 which, in turn, is not supported by jquery.turbolinks! See github.com/kossnocorp/jquery.turbolinks/issues/56Protuberate
@Protuberate - thanks for this important clarification. So, for now, the best solution would be to remove turbolinks from the app.Pierpont
@Anwar - I remove it from Rails entirely now, to avoid issues with js not working in many cases.Pierpont
K
200

Recently I found the most clean and easy to understand way of dealing with it:

$(document).on 'ready page:load', ->
  # Actions to do

OR

$(document).on('ready page:load', function () {
  // Actions to do
});

EDIT
If you have delegated events bound to the document, make sure you attach them outside of the ready function, otherwise they will get rebound on every page:load event (causing the same functions to be run multiple times). For example, if you have any calls like this:

$(document).on 'ready page:load', ->
  ...
  $(document).on 'click', '.button', ->
    ...
  ...

Take them out of the ready function, like this:

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

$(document).on 'click', '.button', ->
  ...

Delegated events bound to the document do not need to be bound on the ready event.

Kyl answered 7/11, 2013 at 11:1 Comment(5)
This is much simpler than the accepted answer of creating a separate ready() function. Bonus upvotes for the explanation of binding delegated events outside of the ready function.Emlyn
On disadvantage of this method is that $(document).on( "ready", handler ), deprecated as of jQuery 1.8. jquery/readyPincince
@Dezi @Pincince Would this solution work if you're using earlier versions of jQuery, and WITHOUT the jquery-turbolinks gem? Or is the ready portion made invalid BY using the gem?Vaios
The simplest and easiest way to do it. It supports js code distributed in multiple files and I don't have to bother in which order they were included. Much cleaner than the variable assignment.Sternwheeler
You should probably use "page:change" instead of "page:load", as the former is fired whether the page is loaded from the server or from client-side cache.Supernormal
L
90

Found this in the Rails 4 documentation, similar to DemoZluk's solution but slightly shorter:

$(document).on 'page:change', ->
  # Actions to do

OR

$(document).on('page:change', function () {
  // Actions to do
});

If you have external scripts that call $(document).ready() or if you can't be bothered rewriting all your existing JavaScript, then this gem allows you to keep using $(document).ready() with TurboLinks: https://github.com/kossnocorp/jquery.turbolinks

Lauraine answered 3/2, 2014 at 11:6 Comment(0)
Y
80

As per the new rails guides, the correct way is to do the following:

$(document).on('turbolinks:load', function() {
   console.log('(document).turbolinks:load')
});

or, in coffeescript:

$(document).on "turbolinks:load", ->
alert "page has loaded!"

Do not listen to the event $(document).ready and only one event will be fired. No surprises, no need to use the jquery.turbolinks gem.

This works with rails 4.2 and above, not only rails 5.

Yamamoto answered 19/7, 2016 at 11:59 Comment(9)
nice this worked for me. Tried solution here: #17881884 but did not work for some reason. Now I load on turbolinks:load insteadAttorneyatlaw
Yeah, this is the correct solution. All the other answers are oldYamamoto
Can anyone confirm that "turbolinks:load" event works in Rails 4.2? The turbolinks: event prefix was introduced in the New Turbolinks and isn't used in Rails 4.2 IIRC which uses Turbolinks Classic. Consider trying "page:change" if "turbolinks:load" doesn't work on your pre-Rails 5 app. See guides.rubyonrails.org/v4.2.7/…Cass
@EliotSykes The guide has not been updated. Rails 4.2 now uses github.com/turbolinks/turbolinks instead of turbolinks-classic. And in any case, this depends on what you have specified in your Gemfile. Hope you will confirm the same soon :)Yamamoto
Thanks @vedant1811. At time of writing, I'm confirming that a new Rails 4.2.7 app uses Turbolinks 5 (not turbolinks-classic). Developers reading this - check your Gemfile.lock for the turbolinks version used. If it is less then 5.0 then use page:change or upgrade turbolinks. Also found this, may be relevant to some, where turbolinks translates events to the old event names: github.com/turbolinks/turbolinks/blob/v5.0.0/src/turbolinks/…Cass
Works perfect! Using rails 5Nummular
Does the alert "page has loaded!" need to be indented to be actually run by the callback function?Axon
@mbigras: No. You can put anything there and it will runYamamoto
@EliotSykes I'm using rails 4.2.8 and it works! ThanksForegut
M
10

NOTE: See @SDP's answer for a clean, built-in solution

I fixed it as follows:

make sure you include application.js before the other app dependent js files get included by changing the include order as follows:

// in application.js - make sure `require_self` comes before `require_tree .`
//= require_self
//= require_tree .

Define a global function that handles the binding in application.js

// application.js
window.onLoad = function(callback) {
  // binds ready event and turbolink page:load event
  $(document).ready(callback);
  $(document).on('page:load',callback);
};

Now you can bind stuff like:

// in coffee script:
onLoad ->
  $('a.clickable').click => 
    alert('link clicked!');

// equivalent in javascript:
onLoad(function() {
  $('a.clickable').click(function() {
    alert('link clicked');
});
Malaguena answered 28/10, 2013 at 15:20 Comment(6)
This seems the cleanest since you only have to add this in one place, not every coffeescript file. Nice work!Exteroceptor
@Malaguena So is this saying to use SDP's method of using the gem and this code? Or is this an answer that doesn't use the gem?Vaios
forget this answer and use SDP's solution.Malaguena
@Malaguena Is it possible to combine SDP's solution with the notion of putting the call only in one place?Vaios
@gwho: As SDP's answer shows, turbolinks hooks up to the ready event, this means you can use the "standard" way of initializing: $(document).ready(function() { /* do your init here */});. Your init code should be called when the complete page is loaded (-> without turbolinks) and your init code should be executed again when you load a page via turbolinks. If you want to execute some code ONLY when a turbolink has been used: $(document).on('page:load', function() { /* init only for turbolinks */});Malaguena
@Malaguena I see. init code is simply code you wish to execute. So I suppose $(document).on('ready page:load ', function(){} ); wouldn't work for the ready, as documented in the gem's github readme.Vaios
O
9

None of the above works for me, I solved this by not using jQuery's $(document).ready, but use addEventListener instead.

document.addEventListener("turbolinks:load", function() {
  // do something
});
Overplus answered 13/9, 2016 at 12:5 Comment(0)
B
8
$(document).on 'ready turbolinks:load', ->
  console.log '(document).turbolinks:load'
Buddha answered 28/8, 2017 at 10:14 Comment(1)
That is the only solution that works for me on every and each browser, for Turbolinks >= 5 and that triggers both on first page load and also when you click any links (with turbolinks). That is the case because while Chrome triggers turbolinks:load on the very first page load, Safari does not and the hacky way to fix it (triggering turbolinks:load on application.js) obviously breaks Chrome (which fires twice with this). This is the correct answer. Definitely go with the 2 events ready and turbolinks:load.Chuckwalla
B
6

You have to use:

document.addEventListener("turbolinks:load", function() {
  // your code here
})

from turbolinks doc.

Brodsky answered 18/6, 2017 at 0:13 Comment(0)
A
3

Either use the

$(document).on "page:load", attachRatingHandler

or use jQuery's .on function to achieve the same effect

$(document).on 'click', 'span.star', attachRatingHandler

see here for more details: http://srbiv.github.io/2013/04/06/rails-4-my-first-run-in-with-turbolinks.html

Akanke answered 12/9, 2013 at 17:50 Comment(0)
U
2

Instead of using a variable to save the "ready" function and bind it to the events, you might want to trigger the ready event whenever page:load triggers.

$(document).on('page:load', function() {
  $(document).trigger('ready');
});
Untutored answered 25/11, 2014 at 18:45 Comment(0)
W
2

Here's what I have done to ensure things aren't executed twice:

$(document).on("page:change", function() {
     // ... init things, just do not bind events ...
     $(document).off("page:change");
});

I find using the jquery-turbolinks gem or combining $(document).ready and $(document).on("page:load") or using $(document).on("page:change") by itself behaves unexpectedly--especially if you're in development.

Weakminded answered 19/3, 2015 at 7:51 Comment(2)
Do you mind explaining what you mean by behaves unexpectedly?Dado
If I have just a simple alert without the $(document).off bit in that anonymous "page:change" function above it will obviously alert when I get to that page. But at least in development running WEBrick, it will also alert when I click a link to another page. Event worse, it alerts twice when I then click a link back to the original page.Weakminded
C
2

I found the following article which worked great for me and details the use of the following:

var load_this_javascript = function() { 
  // do some things 
}
$(document).ready(load_this_javascript)
$(window).bind('page:change', load_this_javascript)
Commander answered 21/10, 2015 at 14:54 Comment(0)
M
2

I figured I'd leave this here for those upgrading to Turbolinks 5: the easiest way to fix your code is to go from:

var ready;
ready = function() {
  // Your JS here
}
$(document).ready(ready);
$(document).on('page:load', ready)

to:

var ready;
ready = function() {
  // Your JS here
}
$(document).on('turbolinks:load', ready);

Reference: https://github.com/turbolinks/turbolinks/issues/9#issuecomment-184717346

Matriarchate answered 1/7, 2016 at 3:13 Comment(0)
H
2
$(document).ready(ready)  

$(document).on('turbolinks:load', ready)
Hygrograph answered 5/10, 2016 at 11:51 Comment(4)
While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.Karolyn
Actually, this answer is incorrect. The turbolinks:load event fires for the initial page load as well as after following links. If you attach an event both to the standard ready event and the turbolinks:load event, it will fire twice when you first visit a page.Inconsumable
This answer is incorrect. As @Inconsumable said, it causes ready to fire twice on subsequent page loads. Please change or remove it.Octastyle
@Inconsumable Your comment solved one of the problem for meForegut
M
2

Update Turbolinks -> Hotwired Turbo (2021+)

Turbolinks has been archived and is now maintained as part of hotwired under turbo.

If you're using the new version, you can listen for turbo load like this:

document.documentElement.addEventListener("turbo:load", (e) {
  console.log("turbo:load", e)
});

Here's the full list of events they broadcast on the document element:

  • turbo:click
  • turbo:before-visit
  • turbo:visit
  • turbo:submit-start
  • turbo:before-fetch-request
  • turbo:before-fetch-response
  • turbo:submit-end
  • turbo:before-cache
  • turbo:before-render
  • turbo:before-stream-render
  • turbo:render
  • turbo:load
  • turbo:before-frame-render
  • turbo:frame-render
  • turbo:frame-load
  • turbo:frame-render
  • turbo:frame-missing
  • turbo:fetch-request-error
Moonshine answered 14/4, 2023 at 21:10 Comment(0)
Z
1

Tested so many solution finally came to this. This many your code is definitely not called twice.

      var has_loaded=false;
      var ready = function() {
        if(!has_loaded){
          has_loaded=true;
           
          // YOURJS here
        }
      }

      $(document).ready(ready);
      $(document).bind('page:change', ready);
Zellazelle answered 1/7, 2016 at 10:4 Comment(0)
G
1

I usually do the following for my rails 4 projects:

In application.js

function onInit(callback){
    $(document).ready(callback);
    $(document).on('page:load', callback);
}

Then in the rest of the .js files, instead of using $(function (){}) I call onInit(function(){})

Gabbie answered 16/2, 2018 at 18:49 Comment(0)
M
0

First, install jquery-turbolinks gem. And then, don't forget to move your included Javascript files from end of body of your application.html.erb to its <head>.

As described here, if you have put the application javascript link in the footer for speed optimization reasons, you will need to move it into the tag so that it loads before the content in the tag. This solution worked for me.

Misconstrue answered 21/6, 2016 at 9:58 Comment(0)
N
0

I found my functions doubled when using a function for ready and turbolinks:load so I used,

var ready = function() {
  // you code goes here
}

if (Turbolinks.supported == false) {
  $(document).on('ready', ready);
};
if (Turbolinks.supported == true) {
  $(document).on('turbolinks:load', ready);
};

That way your functions don't double if turbolinks is supported!

Nugent answered 10/10, 2019 at 6:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.