Using Rails 3.1, where do you put your "page specific" JavaScript code?
Solution 1
I appreciate all the answers... and I don't think they are really getting at the problem. Some of them are about styling and don't seem to relate... and others just mention javascript_include_tag
... which I know exists (obviously...) but it would appear that the Rails 3.1 way going forward is to wrap up all of your Javascript into 1 file rather than loading individual Javascript at the bottom of each page.
The best solution I can come up with is to wrap certain features in div
tags with id
s or class
es. In the javascript code. Then you just check if the id
or class
is on the page, and if it is, you run the javascript code that is associated with it. This way if the dynamic element is not on the page, the javascript code doesn't run - even though it's been included in the massive application.js
file packaged by Sprockets.
My above solution has the benefit that if a search box is included on 8 of the 100 pages, it will run on only those 8 pages. You also won't have to include the same code on 8 of the pages on the site. In fact, you'll never have to include manual script tags on your site anywhere ever again - except to maybe preload data.
I think this is the actual answer to my question.
Solution 2
The Asset Pipeline docs suggest how to do controller-specific JS:
For example, if a
ProjectsController
is generated, there will be a new file atapp/assets/javascripts/projects.js.coffee
and another atapp/assets/stylesheets/projects.css.scss
. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as<%= javascript_include_tag params[:controller] %>
or<%= stylesheet_link_tag params[:controller] %>
.
Solution 3
For the page-specific js you can use Garber-Irish solution.
So your Rails javascripts folder might look like this for two controllers - cars and users:
javascripts/
├── application.js
├── init.js
├── markup_based_js_execution
├── cars
│ ├── init .js
│ ├── index.js
│ └── ...
└── users
└── ...
And javascripts will look like this:
// application.js
//=
//= require init.js
//= require_tree cars
//= require_tree users
// init.js
SITENAME = new Object();
SITENAME.cars = new Object;
SITENAME.users = new Object;
SITENAME.common.init = function (){
// Your js code for all pages here
}
// cars/init.js
SITENAME.cars.init = function (){
// Your js code for the cars controller here
}
// cars/index.js
SITENAME.cars.index = function (){
// Your js code for the index method of the cars controller
}
and markup_based_js_execution will contain code for UTIL object, and on DOM-ready UTIL.init execution.
And don't forget to put this to your layout file:
<body data-controller="<%= controller_name %>" data-action="<%= action_name %>">
I also think that it is better to use classes instead of data-*
attributes, for the better page-specific css. As Jason Garber have mentioned: page-specific CSS selectors can get really awkward (when you use data-*
attributes)
I hope this will help you.
Solution 4
I see that you've answered your own question, but here's another option:
Basically, you're making the assumption that
//= require_tree .
is required. It's not. Feel free to remove it. In my current application, the first I'm doing with 3.1.x honestly, I've made three different top level JS files. My application.js
file only has
//= require jquery
//= require jquery_ujs
//= require_directory .
//= require_directory ./api
//= require_directory ./admin
This way, I can create subdirectories, with their own top level JS files, that only include what I need.
The keys are:
- You can remove
require_tree
- Rails lets you change the assumptions it makes - There's nothing special about the name
application.js
- any file in theassets/javascript
subdirectory can include pre-processor directives with//=
Hope that helps and adds some details to ClosureCowboy's answer.
Sujal
Solution 5
Another option: to create page- or model-specific files, you could create directories inside your assets/javascripts/
folder.
assets/javascripts/global/
assets/javascripts/cupcakes
assets/javascripts/something_else_specific
Your main application.js
manifest file could be configured to load its files from global/
. Specific pages or groups of pages could have their own manifests which load files from their own specific directories. Sprockets will automatically combine the files loaded by application.js
with your page-specific files, which allows this solution to work.
This technique can be used for style_sheets/
as well.
Fire Emblem
Updated on June 03, 2020Comments
-
Fire Emblem almost 4 years
To my understanding, all of your JavaScript gets merged into 1 file. Rails does this by default when it adds
//= require_tree .
to the bottom of yourapplication.js
manifest file.This sounds like a real life-saver, but I am a little concerned about page-specific JavaScript code. Does this code get executed on every page? The last thing I want is for all of my objects to be instantiated for every page when they are only needed on 1 page.
Also, isn't there potential for code that clashes too?
Or do you put a small
script
tag at the bottom of the page that just calls into a method that executes the javascript code for the page?Do you no longer need require.js then?
Thanks
EDIT: I appreciate all the answers... and I don't think they are really getting at the problem. Some of them are about styling and don't seem to relate... and others just mention
javascript_include_tag
... which I know exists (obviously...) but it would appear that the Rails 3.1 way going forward is to wrap up all of your JavaScript into 1 file rather than loading individual JavaScript at the bottom of each page.The best solution I can come up with is to wrap certain features in
div
tags withid
s orclass
es. In the JavaScript code, you just check if theid
orclass
is on the page, and if it is, you run the JavaScript code that is associated with it. This way if the dynamic element is not on the page, the JavaScript code doesn't run - even though it's been included in the massiveapplication.js
file packaged by Sprockets.My above solution has the benefit that if a search box is included on 8 of the 100 pages, it will run on only those 8 pages. You also won't have to include the same code on 8 of the pages on the site. In fact, you'll never have to include manual script tags on your site anywhere ever again.
I think this is the actual answer to my question.
-
jrhorn424 about 12 years+1 This is great to know for a newbie like me. I'd give it +2 if I could.
-
TheAries about 12 yearsOne little detail: there's this notion in your answer that once the js is browser cached, it has no impact. This isn't quite true. The browser does indeed avoid the download, if the js file is properly cached, but it still compiles the code on every page render. So, you have to balance tradeoffs. If you have a lot of JS in aggregate, but only some is used per page, you might be able to improve page times by breaking the JS apart.
-
TheAries about 12 yearsFor more on the practical effects of that compilation step I'm talking about, see 37 Signals' explanation of how pjax impacted Basecamp Next: 37signals.com/svn/posts/…
-
Ryan about 12 yearsThat's a fair point. After reading the article and looking back on projects where I've used the above solution, I realize I wrote essentially the same "send the changed HTML" solution they mention in the article. The frequent re-compiling of JS hadn't been an issue in my projects because of that. The compilation step is something I'll keep in mind when I'm working on less "desktop application" oriented sites.
-
Marnen Laibow-Koser almost 12 years@sujal Exactly. The Rails core team is notorious for abysmal JavaScript management. Feel free to ignore their suggestions and just use the good parts of the asset pipeline. :)
-
Marnen Laibow-Koser almost 12 yearsDownvoting for "Call me crazy, but I want ALL of my JS compiled and minified into application.js when I deploy." You really don't want this, as it makes the user load JavaScript he doesn't need and it makes your handlers look for attributes that aren't even going to be there. Having everything in app.js is tempting, and Rails certainly makes it easy, but the proper thing to do is to modularizing JavaScript better.
-
Ryan almost 12 yearsYou're entitled to a different opinion... and technically entitled to downvote over a difference of opinion. However, it would be nice to see some justification as to why one large and cached file is inferior to forcing multiple HTTP requests to grab modularized JS. Furthermore, you are mistaken regarding the handler search procedure. The tag values are NOT searched. Only one search is ever performed and it pulls all elements that have a data-jstag attribute. It doesn't search by tag name, it just finds all elements that have tags and then instantiates only the needed objects.
-
Marnen Laibow-Koser almost 12 years@Ryan Caching is a thorny issue either way. I'd rather have the client fetch a bunch of independent, separately cacheable JS files: if page 1 needs modules A and B, and page 2 needs modules B and C, then a request to page 2 after page 1 only needs to get module C. But I will admit that this is made more difficult by JS not having any good native way to include one client-side script from within another. Also, thanks for the correction on the
data-jstag
handling. That probably helps performance, but doesn't address my basic issue with this method. As @sujal said, it's lots of unused code. -
zsljulius over 11 yearsThis is the most elegant way to do it. But also, you will need to remove the line //= require_tree . from the application.js.coffee
-
jackyalcine over 11 yearsNo way, that's a crapload of JavaScript to load for one page. Doesn't even matter if it's cached.
-
Nishant over 11 yearsWont this cause problems after assets are precompiled and at run time the
<%= raw ... %>
will return a 404 ? -
Admin about 11 yearsThis looks like it might be able to answer the question. Could you please add more to the answer to flesh it out?
-
Bill Garrison about 11 yearsI totally agree with this method. The other methods seem very clunky and still end up loading a giant js file. The project im working on has almost 2mb worth of JS files / plugins etc AFTER being combined / minified.
-
Ross Hambrick about 11 yearsI'm fairly new to Rails, but it seems to me that this should be the default behavior.
-
Benjamin Crouzier almost 11 yearsIn my experience, using this technique with css breaks turbolinks. So it might be the case with js too. (turbolinks is included by default in rails 4)
-
elsurudo over 10 yearsIf you depend on other libraries, such as Bootstrap, this could present problems as well, as they wouldn't be included. This is definitely one of those moments when I am disappointed in Rails.
-
elsurudo over 10 yearsThanks a lot for this advice. I not have several "top-level" JS files, depending on the module of my app. Works well.
-
Sujimichi over 10 yearsFor action specific control I have this in my layout, as not every action for every controller has specific JS.
page_specific_js = "#{params[:controller]}_#{params[:action]}"
and then;javascript_include_tag page_specific_js if Rails.application.assets.find_asset page_specific_js
-
Deborah over 10 yearsI think the asset pipeline is un-sweet, since it creates a bunch of files that often shouldn't be used. So for me relying on the asset pipeline is creating a dependency on an inefficient system.
-
Marnen Laibow-Koser over 10 years@DeborahSpeece When does the asset pipeline create files that shouldn't be used? Are you confusing the asset pipeline (good) with
require_tree /
(bad)? -
DusanV over 10 yearsDo the controller specific actions still get minified? Are they added to the single js file that is created by sprockets, or elk this lead to multiple requests for asset files?
-
Marnen Laibow-Koser over 10 years@Jason They still get processed by the pipeline, and included as separate, modular, independently cacheable files.
-
Marnen Laibow-Koser over 10 yearsDownvoting. This is ridiculously convoluted -- not to mention insecure (due to the
eval
) if your HTML gets compromised by a cracked server or a malicious userscript. -
Marnen Laibow-Koser over 10 yearsDownvoting. Inline JavaScript is never a good idea. Even if it's glue code, it should be in an external file.
-
Marnen Laibow-Koser over 10 yearsDownvoting. Inline JavaScript is never advisable. HTML should only contain markup. JS and CSS should be in separate, reusable files.
-
zelanix almost 10 years+1 The important point here for me is that you can replace
//= require_tree .
with//= require_directory .
so you can keep all existing files where they are and create new directories for page specific files. -
martincarlin87 about 9 yearsthis looks like the way to go, I'm going to see if I can implement it in my own app, thanks for the detailed answer.
-
Bhargav Rao about 8 yearsWhilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.
-
yekta almost 8 yearsThe very fact that there are multiple suggested ways to DoItRight (i.e. make anything work) is evidence to me that this is not documented well andor the entire approach is flawed. I also take issue with the statement that "this is the most elegant way to do it". Magic is generally not good and I honestly feel the entire asset pipeline is full of magic. Conventions however can be good, but I feel that line is crossed too much with the asset pipeline and this is a great example of it.
</end-of-rant>
.