Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Turbolinks and Controller-specific assets

I've modified application.html.erb to use controller specific assets:

application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>My Application</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= stylesheet_link_tag params[:controller], 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag params[:controller], 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>
... other html template ...

The problem is that, I have turbolinks installed. When I navigate through the same controller, turbolinks works. But when I switch to another controller, the turbolinks will perform a full reload. Is there any way to fix this?

like image 699
Sarun Intaralawan Avatar asked Jul 12 '14 06:07

Sarun Intaralawan


2 Answers

Turbolinks

Turbolinks takes the <body> of your HTML page, and changes it with Ajax, leaving the <head> area intact.

This only works if your <head> will remain the same - otherwise how can it be kept constant? So in the sense of changing your controller page / assets, you're going to have to go through a full page update without Turbolinks (at least if you use Turbolinks without hacking it)

I would recommend the fix for this would be to alter your controller-specific asset structure, specifically to make as few changes to the <head> area as possible.

--

Turbolinks Tracking

Upon reading the Turbolinks documentation, you may be able to benefit from removing the turbolinks-data-track option from your controller-specific assets:

<%= javascript_include_tag controller_name, 'data-turbolinks-track' => false %>

You can track certain assets, like application.js and application.css, that you want to ensure are always of the latest version inside a Turbolinks session. This is done by marking those asset links with data-turbolinks-track, like so:

<link href="/assets/application-9bd64a86adb3cd9ab3b16e9dca67a33a.css" rel="stylesheet" type="text/css" data-turbolinks-track>

If those assets change URLs (embed an md5 stamp to ensure this), the page will do a full reload instead of going through Turbolinks. This ensures that all Turbolinks sessions will always be running off your latest JavaScript and CSS.

--

controller_name

Something you'll benefit from is using the controller_name helper (in place of params[:controller]):

<%= javascript_include_tag 'application', controller_name, 'data-turbolinks-track' => true %>
like image 60
Richard Peck Avatar answered Oct 11 '22 23:10

Richard Peck


Put your controller specific js in the body tag. Since turbolinks won't touch the html body, you will reload that piece of js code every time you visit the page.

In your case

<body>
  <%= javascript_include_tag params[:controller] %>
  ...
</body>

But if you have lots of code in your controller js, you may notice that the solution above is kind of time-consuming. Because when you visit the page, that big chunk of code will get reloaded. So how to deal with it?


■ The following method may cause some of the jquery events get bound multiple times. This is a problem of turbolinks. Read this article http://staal.io/blog/2013/01/18/dangers-of-turbolinks/

A more effective solution

Basically when user first visits your site, you load all the js code, making sure controller-specific ones don't get run (wrap them in functions respectively) and save them in a global variable, say, Site. Then you trigger your contoller-specific js by write something like

<script>
$(document).on("ready page:load", function () {
  Site.load('<%= controller_name %>');
}
</script>

in body tag.

This way, the actual contoller-specific js code is only loaded once and get triggered when needed.

implementation

app/assets/javascripts/site.js

var site;
if(!window.Site) {
  site = window.Site = {};
  site.controllers = {}

  site.load = function (controller) {
    if (this.controllers.hasOwnProperty(controller)) {
      this.controllers[controller].call();
    }
  };

  site.add = function (controller, fn) {
    this.controllers[controller] = fn;
  }
}

wrap your controller js in functions and save them to Site. For example, the users.js

app/assets/javascripts/site.js

Site.add("users", function () {
  // UserController related js code
}

in your application.js load them all.

app/assets/javascripts/site.js

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require site
//= require_tree .

Then in your layout trigger the controller specific js.

app/views/layouts/application.html.erb

<body>
  <script>
  $(document).on("page:load", function () {
    Site.load('<%= controller_name %>');
  }
  </script>
  <%= yield %>
</body>

How about action-specific js code?

Just modify the site.js and the layout. And I end it up with the following snippet.

(function (global) {
  var site;

  if(global.Site) {
    return;
  }

  site = global.Site = {};
  site.controllers = {};
  site.actions = {};

  site.load = function (name) {
    var keys = name.split(".");
    var c = keys[0];
    var cs = this.controllers;
    var as = this.actions;
    var i;

    if (cs.hasOwnProperty(c)) {
      for (i =0 ; i < cs[c].length; i++) {
        cs[c][i].call();
      }
    }

    if (keys.length > 1 && as.hasOwnProperty(name)) {
      for (i =0 ; i < as[name].length; i++) {
        as[name][i].call();
      }
    }
  };

  site.add = function (name, fn) {
    var keys = name.split(".");
    var lv = keys.length > 1 ? this.actions : this.controllers;

    if (!lv.hasOwnProperty(name)) {
      lv[name] = [fn];
    } else {
      lv[name].push(fn);
    }
  };

})(window);

app/views/layouts/application.html.erb

<body>
  <script>
  $(document).on("ready page:load", function () {
    Site.load('<%= controller_name %>.<%= action_name %>');
  }
  </script>
  <%= yield %>
</body>

Related

Here is an interesting video about loading javascript, css and other assets for single page apps you may like to watch.

OSCON 2014: How Instagram.com Works; Pete Hunt

https://www.youtube.com/watch?v=VkTCL6Nqm6Y

like image 22
wenjun.yan Avatar answered Oct 12 '22 00:10

wenjun.yan