160

Rails 3 has some unobtrusive JavaScript which is pretty cool.

But I was wondering what the best way is to include additional JavaScript for a particular page.

For example, where I might have previously done:

<%= f.radio_button :rating, 'positive', :onclick => "$('some_div').show();" %>

We can now make it unobtrusive with something like

<%= f.radio_button :rating, 'positive' %>

# then in some other file
$('user_rating_positive').click(function() {
  $('some_div').show();
}

So I guess my question is where/how to include that JavaScript? I don’t want to fill up the application.js file because this JavaScript is only applicable to this one view. Should I include a custom JavaScript file for each page somehow, or stick it in an instance variable that the header looks for?

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
Brian Armstrong
  • 19,707
  • 17
  • 115
  • 144
  • possible duplicate of [Using Rails 3.1, where do you put your "page specific" javascript code?](http://stackoverflow.com/questions/6167805/using-rails-3-1-where-do-you-put-your-page-specific-javascript-code) – Kate Gregory Jan 11 '13 at 01:24
  • For those that want a full understanding of how this works and what is best please read http://railsapps.github.io/rails-javascript-include-external.html . This is by far the best documentation I have seen on this topic. It is a great read not only for Rails but for anyone dealing in web dev. This is why it is best to do things the rails way. I will surely be using Unholy Rails for my future questions. WOW. – DutGRIFF Jan 22 '14 at 22:22
  • 2
    @KateGregory that one is newer. – Ciro Santilli OurBigBook.com Jul 14 '14 at 09:39

10 Answers10

155

What I like to do is include the per-view Javascript in a content_for :head block and then yield to that block in your application layout. For example

If it's pretty short then:

<% content_for :head do %>
  <script type="text/javascript">
    $(function() {
      $('user_rating_positve').click(function() {
        $('some_div').show();
      }
    });
  </script>
<% end %>

or, if longer, then:

<% content_for :head do %>
  <script type="text/javascript">
    <%= render :partial => "my_view_javascript"
  </script>
<% end %>

Then, in your layout file

<head>
  ...
  <%= yield :head %>
</head>
Justin Tanner
  • 14,062
  • 17
  • 82
  • 103
bjg
  • 7,457
  • 1
  • 25
  • 21
  • 40
    Why not use the `<%= javascript_include_tag "my_javascipt_file" %>` ? – AJP Dec 17 '11 at 18:09
  • 5
    @AJP I guess then it defeats the purpose of asset pipeline – lulalala Mar 06 '12 at 06:36
  • 2
    @lulalala but doesn't the code in the answer do that anyway but in a messier fashion? – AJP Mar 06 '12 at 10:07
  • 8
    @AJP it is messier, but it has one less http request to make than your answer. – lulalala Mar 07 '12 at 01:35
  • 4
    I liked this article http://railsapps.github.com/rails-javascript-include-external.html – Ziggy Mar 29 '13 at 20:33
  • @lulalala but it can be cached by browser in case of extracting to the file – freemanoid Nov 11 '13 at 15:46
  • Awesome solution. It's very easy to façade via a helper as well. – deprecated Nov 19 '13 at 15:25
  • Is using yield better than storing your JS in a partial and using `render`? – KnownColor Jan 23 '14 at 11:41
  • AJP's comment is reflected in the Rails docs: http://guides.rubyonrails.org/asset_pipeline.html#controller-specific-assets and explained also in benz001's answer here: http://stackoverflow.com/questions/9245228/how-do-i-use-controller-specific-stylesheets-in-rails-3-2-1 – tgf Aug 01 '15 at 00:54
104

If you want to include javascript just on one page, you can include it on the page inline of course, however if you want to group your javascript and take advantage of the asset pipeline, minified js etc, it's possible to do so and have extra js assets which are combined and only loaded on specific pages by splitting your js into groups which only apply in certain controllers/views/sections of the site.

Move your js in assets into folders, with a separate manifest file for each, so if you had an admin js library that is only used on the backend, you might do this:

  • assets
    • javascripts
      • admin
        • ...js
      • admin.js (manifest for admin group)
      • application.js (manifest for app global group)
      • global
        • ...js

in the existing application.js

//= require jquery
//= require jquery_ujs
//= require_tree ./global // requires all js files in global folder

in a new admin.js manifest file

//= require_tree ./admin // requires all js files in admin folder

Make sure this new js manifest is loaded by editing config/production.rb

config.assets.precompile += %w( admin.js )

Then adjust your page layout so that you can include some extra js for the page head:

<%= content_for :header %>   

Then in views where you want to include this specific js group (as well as the normal application group) and/or any page-specific js, css etc:

<% content_for :header do %>
  <%= javascript_include_tag 'admin' %>  
<% end %>

You can of course do the same thing with css and group it in a similar way for applying only to certain areas of the site.

Kenny Grant
  • 9,360
  • 2
  • 33
  • 47
  • If you keep all your manifests in the same place you can tell rails to precompile everything in that folder. That way you only need to edit production.rb once and don't keep track of what you've added to it. – Undistraction Nov 13 '12 at 22:51
  • Didn't know you could do that - so you could have a 'manifests' folder which contained your manifests, as well as the individual js groups? I might try this out. I do wish they had a slightly more sane setup by default as it seems they assume you will just glob all js into one big file imported everywhere. – Kenny Grant Feb 22 '13 at 10:53
  • odd, I get a Sprockets::FileNotFound error if I use `//= require_tree ./global`, but not if I use `//= require_directory ./global` using Rails 3.2.12. – qix Mar 08 '13 at 18:40
  • 2
    It looks like you will end up with many script tags. I thought the goal with the asset pipeline was to have just one script tag? I would just write the javascripts so that they are page-specific. – Ziggy Mar 29 '13 at 20:54
  • @Ziggy You'll have one script tag on most of your pages. If you have another asset group which has large files and contains js (and css) which is not required on other pages (say you have editing in admin using several js plugins which is not used in the front end), it can be useful to have at least two asset groups and still use the asset pipeline to manage them. – Kenny Grant Apr 02 '13 at 08:27
  • 1
    I think I understand. Avoiding additional script tags is pretty important to page load time though. – Ziggy Apr 07 '13 at 20:27
12

These answers helped me a ton! If anyone wants a little more...

  1. You need to put javascripts into manifests if you want them precompiled. However, if you require every javascript file from application.js.coffee then all the javacsripts will be loaded every time you navigate to a different page, and the purpose of doing page-specific javascripts will be defeated.

Therefore, you need to create your own manifest file (e.g. speciifc.js) that will require all the page-specific javascript files. Also, modify require_tree from application.js

app/assets/javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require_tree ./global

app/assets/javascripts/specific.js

//= require_tree ./specific

Then in your environments/production.rb add this manifest to the precompiled list with the config option,

config.assets.precompile += %w( specific.js )

Done! All the shared javascripts that should always be loaded will be placed in app/assets/javascripts/global folder, and the page-spcific javascripts in app/assets/javascripts/specific. You can simply call the page-specific javascripts from the view like

<%= javascript_include_tag "specific/whatever.js" %> //.js is optional.

This is sufficient, but I wanted to make a use of javascript_include_tag params[:controller] too. When you create controllers, an associated coffeescript file is generated in app/assets/javascripts like other people mentioned. There are truly controller-specific javascripts, which are loaded only when the user reaches the specific controller view.

So I created another manifest controller-specific.js

app/assets/javascripts/controller-specific.js

//= require_directory .

This will include all the automatically-generated coffeescripts associated with controllers. Also, you need to add it to the precompiled list.

config.assets.precompile += %w( specific.js controller-specific.js )

Maximus S
  • 10,759
  • 19
  • 75
  • 154
  • Thanks a lot for the detailed answer. Where should I put this line `javascript_include_tag params[:controller]` . Currently my application.html.erb has this line `javascript_include_tag 'application'` . Should I replace this with the other line ? – simha Jul 22 '13 at 19:18
  • 1
    Also, should i put this line `<%= javascript_include_tag "specific/whatever.js" %>` in a `content_for :header` block ? – simha Jul 22 '13 at 19:19
  • 1
    It's odd to me that you need to mess with `config.assets.precompile`. Isn't everything in `app/assets/javascripts/*.js` precompiled automatically? – AlexChaffee Aug 01 '14 at 16:02
  • @AlexChaffee Only files available automatically are `application.css` and `application.js`. Others have to be either included in other files, or listed using `config.assets.precompile`. [Read more here](https://www.reinteractive.net/posts/116-12-tips-for-the-rails-asset-pipeline) – mc9 Nov 20 '14 at 03:21
  • The manifest declaration has been moved. I'm running rails 4.2.0. and found this comment in the production.rb file: # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb – curt Jan 09 '15 at 03:55
11

I prefer the following...

In your application_helper.rb file

def include_javascript (file)
    s = " <script type=\"text/javascript\">" + render(:file => file) + "</script>"
    content_for(:head, raw(s))
end

and then in your particular view (app/views/books/index.html.erb in this example)

<% include_javascript 'books/index.js' %>

... seems to work for me.

cailinanne
  • 8,332
  • 5
  • 41
  • 49
9

If you don't want to use the asset pipeline or the complex work arounds to get that necessary page specific javascript (I sympathise), the simplest and most robust way, which achieves the same as the answers above but with less code is just to use:

<%= javascript_include_tag "my_javascipt_file" %>

Note: this does require one more http request per include tag than the answers which use content_for :head

Community
  • 1
  • 1
AJP
  • 26,547
  • 23
  • 88
  • 127
  • `javascript_include_tag` was exactly what I was looking for, cheers AJP – Tom McKenzie Aug 31 '12 at 07:12
  • where do you put "my_javascipt_file"? In assets/javascripts? But if so, wouldn't that get picked up automatically with a `//= require .` in the application.js? – qix Mar 08 '13 at 18:32
  • @Linus yep, `//= require .` would bring that script into any page on your site so you'd have to do something like Kenny Grant (use `//= require_tree ./global` ) or bjg suggested. – AJP Mar 09 '13 at 16:52
  • 1
    This was by far the easiest method for me, I changed `//= require_tree .` to `//= require_directory .` in application.js and then created a sub-directory in assets\javascripts. Note that you have to include the new directory in config\initializers\assets.rb to precompile your js by adding the line: `Rails.application.config.assets.precompile += %w( new_dir/js_file.js )` – Jonesy Jun 11 '16 at 08:07
7

Take a look at pluggable_js gem. You may find this solution easier to use.

peresleguine
  • 2,343
  • 2
  • 31
  • 34
3

My understanding is that the asset pipeline is meant to decrease page load-time by mashing all your js together into one (minified) file. While this may seem repugnant on the surface, it is actually a feature that already exists in popular languages like C and Ruby. Things like "include" tags are meant to prevent multiple inclusion of a file, and to help programmers organize their code. When you write and compile a program in C, all that code is present in every part of your running program, but methods are only loaded into memory when that code is being used. In a sense, a compiled program does not include anything to guarantee that the code is nicely modular. We make the code modular by writing our programs that way, and the operating system only loads into memory the objects and methods we need for a given locality. Is there even such a thing as "method-specific inclusion"? If your rails app is restful, this is essentially what you are asking for.

If you write your javascript so that it augments the behaviour of HTML elements on the page, then those functions are 'page-specific' by design. If there is some complicated code that you wrote in such a way that it will execute regardless of its context, maybe consider binding that code to an html element anyway (you could use the body tag, as described in the Garber-Irish method). If a function executes conditionally, the performance will probably be smaller than all those extra script tags.

I am thinking of using the paloma gem, as described in the rails apps project. Then you can make your javascript page-specific by including page-specific functions in a paloma callback:

Paloma.callbacks['users']['new'] = function(params){
    // This will only run after executing users/new action
    alert('Hello New Sexy User');
}; 

You use rails, so I know you love gems :)

Ziggy
  • 21,845
  • 28
  • 75
  • 104
3

You shouldn't be loading your JS or CSS files outside of the asset pipeline because you lose out on important features that make Rails so great. And you don't need another gem. I believe in using as few gems as possible, and using a gem isn't necessary here.

What you want is known as "Controller Specific Javascript" ("Action Specific Javascript is included at the bottom). This allows you to load a specific JavaScript file for a specific CONTROLLER. Trying to connect your Javascript to a View is kind of... backwards and doesn't follow the MVC design pattern. You want to associate it with your Controllers or actions inside your Controllers.

Unfortunately, for whatever reason, Rails devs decided that by default, every page will load every JS file located in your assets directory. Why they decided to do this instead of enabling "Controller Specific Javascript" by default I will never know. This is done through the application.js file, which includes the following line of code by default:

//= require_tree .

This is known as a directive. It's what sprockets uses to load every JS file in the assets/javascripts directory. By default, sprockets automatically loads application.js and application.css, and the require_tree directive loads every JS and Coffee file in their respective directories.

NOTE: When you scaffold (if you aren't scaffolding, now is a good time to start), Rails automatically generates a coffee file for you, for that scaffold's controller. If you want it to generate a standard JS file instead of a coffee file, then remove the coffee gem that is enabled by default in your Gemfile, and your scaffold will create JS files instead.

Ok, so the first step to enabling "Controller Specific Javascript" is to remove the require_tree code from your application.js file, OR change it to a folder within your assets/javascripts directory if you still need global JS files. I.E.:

//= require_tree ./global

Step 2: Go into your config/initializers/assets.rb file, and add the following:

%w( controllerone controllertwo controllerthree ).each do |controller|
  Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
end

Insert the Controller names that you want.

Step 3: Replace the javascript_include_tag in your application.html.erb file with this (note the params[:controller] part:

<%= javascript_include_tag 'application', params[:controller], 'data-turbolinks-track': 'reload' %>

Restart your server and viola! The JS file that was generated with your scaffold will now only load when that controller is called.

Need to load a specific JS file on a specific ACTION in your controller, I.E. /articles/new ? Do this instead:

application.html.erb:

<%= javascript_include_tag "#{controller_name}/#{action_name}" if AppName::Application.assets.find_asset("#{controller_name}/#{action_name}") %>

config/initializers/assets.rb:

config.assets.precompile += %w(*/*)

Then add a new folder with the same name as you controller in your assets/javascripts folder and put your js file with the same name as your action inside. It will then load it on that specific action.

Erick Maynard
  • 731
  • 6
  • 18
1

Ok so maybe this is like the worst work around ever but i creat a controller method that just rendered out the .js file

Controller

def get_script
   render :file => 'app/assessts/javascripts/' + params[:name] + '.js'
end
def get_page
   @script = '/' + params[:script_name] + '.js?body=1'
   render page
end

View

%script{:src => @script, :type => "text/javascript"}

if for some reason we don't want to do this then let me know.

Bill
  • 131
  • 1
  • 1
  • 6
1

The preferred way to add JS is in footer, so you can do this way:

show.html.erb:

<% content_for :footer_js do %>
   This content will show up in the footer section
<% end %>

layouts/application.html.erb

<%= yield :footer_js %>
Syed
  • 15,657
  • 13
  • 120
  • 154