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.
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...
});
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
).
//= require jquery.turbolinks
to the manifest (e.g. application.js).
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.
ready()
function. Bonus upvotes for the explanation of binding delegated events outside of the ready
function.
$(document).on( "ready", handler ), deprecated as of jQuery 1.8.
jquery/ready
ready
portion made invalid BY using the gem?
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
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.
"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/…
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/…
alert "page has loaded!"
need to be indented to be actually run by the callback function?
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');
});
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 */});
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
});
$(document).on 'ready turbolinks:load', ->
console.log '(document).turbolinks:load'
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
.
You have to use:
document.addEventListener("turbolinks:load", function() {
// your code here
})
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
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');
});
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.
$(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.
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)
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
$(document).ready(ready)
$(document).on('turbolinks:load', ready)
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.
ready
to fire twice on subsequent page loads. Please change or remove it.
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);
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(){})
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.
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!
Success story sharing
var ready = function() { ... } $(document).ready(ready) $(document).on('page:load', ready)
Is there any concern for both.ready
and'page:load'
getting triggered?ready
event is fired. If it's a turbolinks load, only thepage:load
event is fired. It's impossible for bothready
andpage:load
to be fired on the same page.page:load
andready
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 namedready
, following this answer's example.