Turbolinks: Javascript not executing

Created on 13 Feb 2016  Â·  29Comments  Â·  Source: turbolinks/turbolinks

After upgrading to Turbolinks 5, the javascript in the new page is no longer executing. (Only the javascript in the original fully loaded page is executing). Has javascript execution in turbolinks 5 changed?

Most helpful comment

@svesely is correct – event names have changed. However, I don't think turbolinks:render is the best event to listen to if you want to attach behavior after the page loads.

In most cases you'll only need to listen for turbolinks:load. It fires once on the initial page load in response to DOMContentLoaded, and again on every Turbolinks visit, whether it’s triggered by clicking a link, clicking the "back" or "forward" buttons, or by calling Turbolinks.visit().

In Turbolinks Classic it's likely you're doing something like this:

$(document).on "ready page:load", ->
  # Install behavior, bind event listeners, etc.

In Turbolinks 5 this becomes simply:

$(document).on "turbolinks:load", ->
  # Install behavior, bind event listeners, etc.

Note that Turbolinks caches each page immediately before it's replaced. If you've made changes to the DOM on turbolinks:load, they'll be reflected in the cache. If these changes are non-idempotent, you'll need to ensure you're not applying them again when a cached page is loaded. For example, you might uninstall behavior and unwind non-idempotent changes on turbolinks:before-cache (your chance to prepare your DOM for cache storage), or you might use data- attributes to denote elements have already been transformed.

All 29 comments

@coffeebite It looks like the event names have changed from a 'page' namespace to 'turbolinks'

See if $(document).on('turbolinks:visit',function(){...} works for you.

More info: https://github.com/turbolinks/turbolinks/tree/docs#observing-navigation-events

@svesely is correct – event names have changed. However, I don't think turbolinks:render is the best event to listen to if you want to attach behavior after the page loads.

In most cases you'll only need to listen for turbolinks:load. It fires once on the initial page load in response to DOMContentLoaded, and again on every Turbolinks visit, whether it’s triggered by clicking a link, clicking the "back" or "forward" buttons, or by calling Turbolinks.visit().

In Turbolinks Classic it's likely you're doing something like this:

$(document).on "ready page:load", ->
  # Install behavior, bind event listeners, etc.

In Turbolinks 5 this becomes simply:

$(document).on "turbolinks:load", ->
  # Install behavior, bind event listeners, etc.

Note that Turbolinks caches each page immediately before it's replaced. If you've made changes to the DOM on turbolinks:load, they'll be reflected in the cache. If these changes are non-idempotent, you'll need to ensure you're not applying them again when a cached page is loaded. For example, you might uninstall behavior and unwind non-idempotent changes on turbolinks:before-cache (your chance to prepare your DOM for cache storage), or you might use data- attributes to denote elements have already been transformed.

Thanks guys. What I was referring to was this:

In the past, I could have a script tag in the middle of the body like this:

<script type="text/javascript">
  alert("Hello");
</script>

And it would execute when the page loaded via turbolinks. Now not any more.

Hi @coffeebite. Ah, yes. I understand now. Unfortunately Turbolinks 5 doesn't yet support script tag execution. It's on our list of missing features and we intend to add it before the final release.

Thanks.

@coffeebite, I wondered if you wouldn't mind elaborating a bit on your need for inline script execution. The main reason for its omission is that we haven't needed it at Basecamp. Thinking now about implementing it, a few questions come up as to how it should behave. Real-world examples, if you have them, would be helpful.

Thanks!

@packagethief This is an old example but might still be relevant: https://github.com/turbolinks/turbolinks-classic/issues/173

<script type="text/javascript">
  window.foo = <%= @foo.to_json %>;
</script>

@packagethief

We have a page where we rotate between different embedded content (say between youtube embed, flickr embed etc).

Some of these embeds e.g. a twitter widget, are just plain old javascript.

Here is an example page

http://padlet.com/demo_simon/goqx5gh33b/wish/4046565

Thanks @aaronvb and @coffeebite. Exactly the kind of examples I was looking for.

@packagethief I believe react-rails needs it in order to work.

For simple cases, I wonder if adding an event listener doing something like this during turbolinks:render will work...

+1 for executing <script> tags. Have a use case to render maps (e.g. google or leaflet), which require <script> tags

also we need jquery on ready to load too for new / not yet cached pages?

Even new <script> tags referencing external scripts in the <head> of the page are not executed. So this problem is not limited to inline scripts or scripts in <body>.

Let's say I'm currently on /a and navigate to /b. /b contains some JS that did not exist on /a.

/a:

<head>
<script src="foo.js">
</head>

/b:

<head>
<script src="foo.js">
<script src="bar.js">
</head>

When navigating from /a to /b, Turbolinks correctly ends up with this in the DOM:

<head>
<script src="foo.js">
<script src="bar.js">
</head>

… but bar.js is never fetched, let alone executed.

Is this expected? Or perhaps this is a problem on my side?

@wimleers In the docs it states Turbolinks will reload the body and merge head, which I assume by design shouldn't reload anything in head unless you reload the page. By adding data-turbolinks-track="reload" to your script tag, Turbolinks will reload the page if it detects a change.

I ran into this issue while using a content_for yield in head.

@aaronvb I'm not talking about _reloading_, I'm talking about _loading at all_. i.e. if different pages have different sets of scripts. Is this so uncommon in the Rails world?

(I'm coming at this from the Drupal world.)

@wimleers No, that isn't uncommon. I think it's a valid use case.

@wimleers Sorry, when I said reloading I meant reloading any sources in head including old and new. So in your case the new JS source wont be loaded.

During rendering, Turbolinks replaces the current <body> element outright and merges the contents of the<head> element. - https://github.com/turbolinks/turbolinks#navigating-with-turbolinks

Also check out https://github.com/turbolinks/turbolinks#reloading-when-assets-change - this is how I got it to at least load the new JS source when the page changes.

You’re not missing anything—we currently don’t load or evaluate any <script> tags beyond the ones processed by the browser on the initial page load.

We’re working on a general solution and will have one ready for the 5.0.0 release.

Ok, cool :)

are they any work around for this moment?
thank you :)

i made a work around,
but this does not detect any new js being added to force a reload.
maybe you may add this functionality.

my admin layout call this:
EZB.init();

So i only add jQuery(document).on('ready'),
and the rest is handled by my coffeescript:

EZB.turbolink_loaded = false
EZB.init = ->
  if EZB.turbolink_loaded
    return
  jQuery(document).on('turbolinks:load', (e)->
    EZB.render_init()
  )
  document.addEventListener("turbolinks:request-start", (event)=>
    xhr = event.data.xhr
    xhr.setRequestHeader("X-Request-Turbo", 1)
  )
  return

EZB.render_init = ->
  console.log('render init')
  //todo your other scripting such as bootstrap tabs, selectize or select2 scripts
  if EZB.turbolink_loaded
    # not 1st time, loaded via turbo link
    $('script').each((e)->
      dom = $(this)
      # console.log('src', dom.attr('src'))
      if (!dom.attr('src'))
        content = this.innerHTML
        # console.log('evaluating', content)
        eval(content)

    )
  else
    EZB.turbolink_loaded = true

by using this method, jquery on ready will not be called inside render_init, but its called naturally the first page it load / when page refreshes.
when turbolink open another page or back button is pressed, turbolink_loaded is set, therefore all plain scripts will be executed. perhaps someone can further improve it by getting only script within the body to execute, and leave those in header (assumed not changed) as ignored.

changing line below to { html: @response, ... } will fix issue unless you have cache enabled.
btw i don't understand why there is snapshot?
https://github.com/turbolinks/turbolinks/blob/master/src/turbolinks/visit.coffee#L75

@attenzione I do not recommend making that change. The html option is only intended for rendering error responses.

We’re tracking development over in #65, for anyone who is interested in following along.

Off course I'll wait release, I just experimenting 😊

Script element support is now available in Turbolinks 5.0.0.beta4.

@svesely is correct – event names have changed. However, I don't think turbolinks:render is the best event to listen to if you want to attach behavior after the page loads.

In most cases you'll only need to listen for turbolinks:load. It fires once on the initial page load in response to DOMContentLoaded, and again on every Turbolinks visit, whether it’s triggered by clicking a link, clicking the "back" or "forward" buttons, or by calling Turbolinks.visit().

In Turbolinks Classic it's likely you're doing something like this:

$(document).on "ready page:load", ->
  # Install behavior, bind event listeners, etc.

In Turbolinks 5 this becomes simply:

$(document).on "turbolinks:load", ->
  # Install behavior, bind event listeners, etc.

Note that Turbolinks caches each page immediately before it's replaced. If you've made changes to the DOM on turbolinks:load, they'll be reflected in the cache. If these changes are non-idempotent, you'll need to ensure you're not applying them again when a cached page is loaded. For example, you might uninstall behavior and unwind non-idempotent changes on turbolinks:before-cache (your chance to prepare your DOM for cache storage), or you might use data- attributes to denote elements have already been transformed.

@packagethief I know this thread is super old at this point, but I am encountering problems with this and hoping you might be able to help. I updated my scoped js file to use $(document).on("turbolinks:load", function()... instead of standard jQuery, but the scripts are still not firing on the page load. I have exhausted resources I could find to try to understand why. Everything is working locally, but on production (Heroku) the behavior is absent. Thanks in advance.

@jraczak Having the same issue, were you able to resolve it?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nateberkopec picture nateberkopec  Â·  23Comments

lalusaud picture lalusaud  Â·  13Comments

jaredgalanis picture jaredgalanis  Â·  23Comments

tadiou picture tadiou  Â·  42Comments

jakehockey10 picture jakehockey10  Â·  31Comments