Alon
Alon

Reputation: 7758

Loading a javascript and running it on a specific controller and specific action in rails

I have standard rails app format.

I have these controller:

class StaticPagesController < ApplicationController

  def help
  end

  def about
  end

end

and I have the file app/assets/javascripts/static_page.js.coffee

I want 2 things:

  1. load this javascript ONLY when I am running one of the pages of the static_pages

  2. be able to make different js calls depending on the specific action inside the controller:

the javascript file should look like:

//general javascript code
if (isThisHelpPage) {
   //Run some help page code
}

if (isThisAboutPage) {
   //Run some about page code
}

I assume that the first issue should be solved somehow with the app/assets/javascripts/application.js file by adding some rule like:

//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .

//= (isItStaticController? render static_page.js.coffee)

But I want to know how can I do that?

Upvotes: 1

Views: 438

Answers (4)

alup
alup

Reputation: 2981

load this javascript ONLY when I am running one of the pages of the static_pages

First of all, asset pipeline compiles and creates static asset files offline, meaning that you can not create bundles dynamically based on the controller name during the serving of a user request (in production).

However, you can use a separate <%= javascript_include_tag "static_page_manifest.js" %> statement instead of (including the application.js) in the corresponding layout file (of StaticPages controller) to define certain javascripts for the specific views. For example, you can have a app/assets/javascripts/static_page_manifest.js:

//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .

//= static_page

Alternatively, if you want dynamic javascript module inclusion, you could use requirejs (requirejs-rails) etc.

be able to make different js calls depending on the specific action inside the controller

This could be achieved by checking an body tag id with a value depending on controller and action names! For example:

<body id="static_pages_<%= controller.action_name %>">
...

And then you can check this id value in the javascript bundle to execute certain code snippets.

Upvotes: 0

zykadelic
zykadelic

Reputation: 1159

The best practice here is to use the content_for helper method. Kind of like described by @Adam, but you probably want to put a <%= yield :script_files %> in the bottom of your layout file, and then call

<% content_for :script_files do %>
  <%= javascript_include_tag 'your_js_file' %>
<% end %>

from within the action you want it to be. You can do this even cleaner by following a similar approach as mentioned by Ryan Bates in an early Railscast:

module ApplicationHelper
  def javascripts(paths)
    content_for :script_files do
      javascript_include_tag(paths.is_a?(Array) ? paths.join(',') : paths)
    end
  end
end

You can then, from your action view, just call <% javascripts 'your_js_file' %> and have that file included.

Upvotes: 0

deefour
deefour

Reputation: 35360

I'll explain how I handle this. In my ApplicationController I have a method that runs from a before_filter on every request.

def prepare_common_variables
  controller_name = self.class.name.gsub(/Controller$/, '')
  if !controller_name.index('::').nil?
    namespace, controller_name = controller_name.split('::')
  end

  @default_body_classes = ["#{controller_name.underscore}_#{action_name} ".downcase.strip]
  @default_body_classes = ["#{namespace.underscore}_#{@default_body_classes.join}".strip] if !namespace.nil?
end

In app/views/layouts/application.html.erb I have the following

<body class="<%= yield :body_classes %> <%= @default_body_classes.join(' ') %>">

For your StaticPagesController, when the help action runs, this would generate the following <body> tag:

<body class="static_pages_help">

Next, I have a method like this in my app/assets/javascripts/application.js.erb

Array.prototype.diff = function(a) {
  return this.filter(function(i) {return !(a.indexOf(i) > -1);});
};

var DEEFOUR = (function (deefour) {
  deefour.Utility = (function (utility) {
    utility.hasBodyClass = function() {
      var args = Array.prototype.slice.call(arguments);
      if (args.length === 0 || $('body').get(0).attr('class') == "") return false;

      return args.diff($('body').get(0).attr('class').split(/\s/)).length == 0;
    };

    return utility;
  }(deefour.Utility || {}));

  return deefour;
}(DEEFOUR || {}));

Finally, in my equivalent of your app/assets/javascripts/static_page.js.coffee I will have something like this

$(function(){
  if (!DEEFOUR.Utility.hasBodyClass('static_pages_help')) return;

  // code for your help page
});

$(function(){
  if (!DEEFOUR.Utility.hasBodyClass('static_pages_about')) return;

  // code for your about page
});

This is nice because in your view

<% content_for :body_classes, :some_custom_class %>

or within a specific action

@default_body_classes << "some_other_custom_class"

you can conditionally add specific classes to match against in your Javascript.

// *both* 'static_pages_help' and 'some_other_class' are required
if (!DEEFOUR.Utility.hasBodyClass('static_pages_help') || !DEEFOUR.Utility.hasBodyClass('some_other_class')) return;

hasBodyClass(...) accepts an arbitrary # of arguments; just list them out. This is useful for things like a new and create action which you want the same Javascript to run for when a form fails to submit.

if (!DEEFOUR.Utility.hasBodyClass('some_controller_new', 'some_controller_create')) return;

It should be noted, prepare_common_variables needs a bit of tweaking as it only allows for a single namespace like SomeNamespace::TheController and not more like SomeNamespace::AnotherNamespace::TheController.

Upvotes: 0

apchester
apchester

Reputation: 1144

Unfortunately you can't add conditionals to the application js file as it is precompiled when the app is deployed. There are two potential approaches that I can see working for you here:

1) Place all of your javascript in a single file as you have above then set the page in your code like this:

<script> page = "Help" </script> or <script> page = "About" </script>

your script could then become:

if(page === "Help") .... else if(page === "About") ....

2) the other option would be to create separate js files for each page, and then incorporate them via a yield :head block in your layout. In your help file it would look like this:

<% content_for :head %>
  <%= javascript_include_tag 'help' %>
<% end %>

I personally favour the second approach in my apps.

Upvotes: 1

Related Questions