Last Updated: February 25, 2016
·
7.372K
· jkeck

Migrating jQuery for use in a Turbolinks Application

Updating to a rails version with Turbolinks can potentially cause issues with existing jQuery code. The primary issue is with jQuery that is invoked in the ready() function.

Take this jQuery as an example.

$(document).ready(function(){
  $("[data-toggle='autocomplete']").each(function(){
    $(this).prop("autocomplete", "off");
  });
});

In the context of a Turbolinks application this will only be triggered on an initial page load or refresh (i.e. not after any subsequent Turbolink controlled link clicks). One way to get around this is to wrap the code to be executed in a function and call it from an eventListener bound to the page:load event in addition to the ready() function.

function toggleAutoComplete(){
  $("[data-toggle='autocomplete']").each(function(){
    $(this).prop("autocomplete", "off");
  });
 }
 $(document).ready(function(){ toggleAutoComplete() });
 document.addEventListener('page:load', function(){ toggleAutoComplete() });

EDIT: @glaszig points out that you can use

jQuery(document).on("ready page:change", function() { toggleAutoComplete() })

to make this a bit more concise and keep your jQuery as an anonymous function if you so choose. Also page:change will cause the jQuery to get executed a few milliseconds before page:load.

This will call the toggleAutoComplete() function when the page is initially loaded ( via ready() ) and when the page:load (or page:change if you use it) event is triggered at the end of the Turbolinks Events Sequence.

There are a lot of things you can do to write your jQuery to make it Turblolinks compatible now (most of them are javascript best-practices anyhow). There are a lot of good resources out there on how to do that in the Turbolinks context, however an easy one to keep in mind is using (EDIT: delegated) on() events instead of explicit event functions (e.g. click, change, submit, etc) when possible. This ensures that the functions are attached to these DOM objects regardless if the object was in existence when the function was initially called.

8 Responses
Add your response

The .on() part is actually not true anymore, read the docs: Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().

That said, you could bind events to document and then as the first parameter send in the selector. This is NOT a best practice but will do it's job.

over 1 year ago ·

That's not exactly right either, $.fn.on was added to bring together event binding (elements that exist on document ready) and delegation (elements which may exist in the future).

over 1 year ago ·

Yes but if you bind directly to the element and it does not exist it will not bound to it if it is created in the future. That's why I suggested document and then selector as a parameter which will go the delegation way.

over 1 year ago ·

I believe I was correct in the first place, just needed to be more specific about the type of on() function to call. I edited the tip to indicate that you should use a delegated on() event as http://api.jquery.com/on/#direct-and-delegated-events explains:
"Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time."

over 1 year ago ·

On Starve i use this simple version:

jQuery(document).on('ready page:change', function() { … })

Covers both events and allows the use of an anonymous function at the same time.
Also, from my understanding page:change is the earliest event to run javascript on the site (though we're talking about nanoseconds here ;) ).

over 1 year ago ·

Perfect @glaszig, this is exactly the kind of thing I was looking for. I have edited the tip to include this information. Thank you.

over 1 year ago ·

you're welcome.

over 1 year ago ·