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?
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 %>
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