Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 447
emersonthis Avatar asked Sep 12 '13 17:09

emersonthis


17 Answers

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...

});
like image 198
Meltemi Avatar answered Oct 11 '22 07:10

Meltemi


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).

like image 42
emersonthis Avatar answered Oct 11 '22 07:10

emersonthis


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.

like image 32
Artyom Tsoy Avatar answered Oct 11 '22 06:10

Artyom Tsoy


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

like image 21
migu Avatar answered Oct 11 '22 07:10

migu


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.

like image 34
Vedant Agarwala Avatar answered Oct 11 '22 07:10

Vedant Agarwala


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');
});
like image 42
sled Avatar answered Oct 11 '22 06:10

sled


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
});
like image 42
RockL Avatar answered Oct 11 '22 06:10

RockL


$(document).on 'ready turbolinks:load', ->
  console.log '(document).turbolinks:load'
like image 44
Sukeerthi Adiga Avatar answered Oct 11 '22 05:10

Sukeerthi Adiga


You have to use:

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

from turbolinks doc.

like image 30
Gian Franco Fioriello Avatar answered Oct 11 '22 05:10

Gian Franco Fioriello


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

like image 44
derekyau Avatar answered Oct 11 '22 07:10

derekyau


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');
});
like image 42
wachunei Avatar answered Oct 11 '22 06:10

wachunei


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.

like image 31
Gray Kemmey Avatar answered Oct 11 '22 05:10

Gray Kemmey


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)
like image 32
atw Avatar answered Oct 11 '22 07:10

atw


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

like image 25
tirdadc Avatar answered Oct 11 '22 06:10

tirdadc


$(document).ready(ready)  

$(document).on('turbolinks:load', ready)
like image 20
Konstantinos Mou Avatar answered Oct 11 '22 05:10

Konstantinos Mou


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);
like image 30
Simon Meyborg Avatar answered Oct 11 '22 05:10

Simon Meyborg


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(){})

like image 25
muZk Avatar answered Oct 11 '22 05:10

muZk