Using Rails 3.1, where do you put your “page speci

2018-12-31 14:27发布

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 your application.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 with ids or classes. In the JavaScript code, 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.

I think this is the actual answer to my question.

29条回答
一个人的天荒地老
2楼-- · 2018-12-31 15:05

ryguy's answer is a good answer, even though its been downvoted into negative points land.

Especially if you're using something like Backbone JS - each page has its own Backbone view. Then the erb file just has a single line of inline javascript that fires up the right backbone view class. I consider it a single line of 'glue code' and therefore the fact that its inline is OK. The advantage is that you can keep your "require_tree" which lets the browser cache all the javascript.

in show.html.erb, you'll have something like:

<% provide :javascript do %>
  <%= javascript_include_tag do %>
    (new app.views.ProjectsView({el: 'body'})).render();
  <% end %>
<% end do %>

and in your layout file, you'll need:

<%= yield :javascript %>
查看更多
低头抚发
3楼-- · 2018-12-31 15:06

This has been answered and accepted long ago, but I came up with my own solution based on some of these answers and my experience with Rails 3+.

The asset pipeline is sweet. Use it.

First, in your application.js file, remove //= require_tree.

Then in your application_controller.rb create a helper method:

helper_method :javascript_include_view_js //Or something similar

def javascript_include_view_js
    if FileTest.exists? "app/assets/javascripts/"+params[:controller]+"/"+params[:action]+".js.erb"
        return '<script src="/assets/'+params[:controller]+'/'+params[:action]+'.js.erb" type="text/javascript"></script>'
    end
end

Then in your application.html.erb layout file, add your new helper among the existing javascript includes, prefixed with the raw helper:

<head>
    <title>Your Application</title>
    <%= stylesheet_link_tag "application", :media => "all" %>
    <%= javascript_include_tag "application" %>
    <%= raw javascript_include_view_js %>
</head>

Voila, now you can easily create view-specific javascript using the same file structure you use everywhere else in rails. Simply stick your files in app/assets/:namespace/:controller/action.js.erb!

Hope that helps someone else!

查看更多
几人难应
4楼-- · 2018-12-31 15:08

I realize I'm coming to this party a bit late, but I wanted to throw in a solution that I've been using lately. However, let me first mention...

The Rails 3.1/3.2 Way (No, sir. I don't like it.)

See: http://guides.rubyonrails.org/asset_pipeline.html#how-to-use-the-asset-pipeline

I'm including the following for the sake of completeness in this answer, and because it's not an unviable solution... though I don't care much for it.

The "Rails Way" is a controller-oriented solution, rather than being view-oriented as the original author of this question requested. There are controller-specific JS files named after their respective controllers. All of these files are placed in a folder tree that is NOT included by default in any of the application.js require directives.

To include controller-specific code, the following is added to a view.

<%= javascript_include_tag params[:controller] %>

I loathe this solution, but it's there and it's quick. Presumably, you could instead call these files something like "people-index.js" and "people-show.js" and then use something like "#{params[:controller]}-index" to get a view-oriented solution. Again, quick fix, but it doesn't sit well with me.

My Data Attribute Way

Call me crazy, but I want ALL of my JS compiled and minified into application.js when I deploy. I don't want to have to remember to include these little straggler files all over the place.

I load all of my JS in one compact, soon-to-be browser cached, file. If a certain piece of my application.js needs to be fired on a page, I let the HTML tell me, not Rails.

Rather than locking my JS to specific element IDs or littering my HTML with marker classes, I use a custom data attribute called data-jstags.

<input name="search" data-jstag="auto-suggest hint" />

On each page, I use - insert preferred JS library method here - to run code when the DOM has finished loading. This bootstrapping code performs the following actions:

  1. Iterate over all elements in the DOM marked with data-jstag
  2. For each element, split the attribute value on space, creating an array of tag strings.
  3. For each tag string, perform a lookup in a Hash for that tag.
  4. If a matching key is found, run the function that is associated with it, passing the element as a parameter.

So say I have the following defined somewhere in my application.js:

function my_autosuggest_init(element) {
  /* Add events to watch input and make suggestions... */
}

function my_hint_init(element) {
  /* Add events to show a hint on change/blur when blank... */
  /* Yes, I know HTML 5 can do this natively with attributes. */
}

var JSTags = {
  'auto-suggest': my_autosuggest_init,
  'hint': my_hint_init
};

The bootstrapping event is going to apply the my_autosuggest_init and my_hint_init functions against the search input, turning it into an input that displays a list of suggestions while the user types, as well as providing some kind of input hint when the input is left blank and unfocused.

Unless some element is tagged with data-jstag="auto-suggest", the auto-suggest code never fires. However, it's always there, minified and eventually cached in my application.js for those times that I need it on a page.

If you need to pass additional parameters to your tagged JS functions, you'll have to apply some creativity. Either add data-paramter attributes, come up with some kind of parameter syntax, or even use a hybrid approach.

Even if I have some complicated workflow that seems controller-specific, I will just create a file for it in my lib folder, pack it into application.js, and tag it with something like 'new-thing-wizard'. When my bootstrap hits that tag, my nice, fancy wizard will be instantiated and run. It runs for that controller's view(s) when needed, but is not otherwise coupled to the controller. In fact, if I code my wizard right, I might be able to provide all configuration data in the views and therefore be able to re-use my wizard later for any other controller that needs it.

Anyway, this is how I've been implementing page specific JS for a while now, and it has served me well both for simple site designs and for more complex/rich applications. Hopefully one of the two solutions I've presented here, my way or the Rails way, is helpful to anyone who comes across this question in the future.

查看更多
冷夜・残月
5楼-- · 2018-12-31 15:08

Step1. remove require_tree . in your application.js and application.css.

Step2. Edit your application.html.erb(by rails default) in layout folder. Add "params[:controller]" in the following tags.

<%= stylesheet_link_tag    'application', params[:controller], media: 'all', 'data-turbolinks-track' => true %>

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

Step3. Add a file in config/initializers/assets.rb

%w( controller_one controller_two controller_three ).each do |controller|
  Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.js.coffee", "#{controller}.css", "#{controller}.scss"]
end

references: http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/

查看更多
临风纵饮
6楼-- · 2018-12-31 15:09

Though you have several answers here, I think your edit is probably the best bet. A design pattern that we use in our team that we got from Gitlab is the Dispatcher pattern. It does something similar to what you're talking about, however the page name is set in the body tag by rails. For example, in your layout file, just include something like (in HAML):

%body{'data-page' => "#{controller}:#{action}" }

Then only have one closure and a switch statement in your dispatcher.js.coffee file in your javascripts folder like so:

$ ->
  new Dispatcher()

class Dispatcher
  constructor: ->
    page = $('body').attr('data-page')
    switch page
      when 'products:index'
        new Products() 
      when 'users:login'
        new Login()

All you need to do in the individual files (say products.js.coffee or login.js.coffee for example) is enclose them in a class and then globalize that class symbol so you can access it in the dispatcher:

class Products
  constructor: ->
    #do stuff
@Products = Products

Gitlab has several examples of this that you might want to poke around with in case you're curious :)

查看更多
大哥的爱人
7楼-- · 2018-12-31 15:11

I did it previously using this method: http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/ . Super-easy, relies on controllers to select the proper js to load.

查看更多
登录 后发表回答