Using Rails 3.1, where do you put your “page specific” JavaScript code?

前端 未结 29 1760
一生所求
一生所求 2020-11-22 11:08

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 appl

相关标签:
29条回答
  • 2020-11-22 11:24

    I combined some answers into:

    Application helper:

    module ApplicationHelper
      def js_page_specific_include
        page_specific_js = params[:controller] + '_' + params[:action]
        if Rails.application.assets.find_asset(page_specific_js).nil?
          javascript_include_tag 'application', 'data-turbolinks-track' => true
        else
          javascript_include_tag 'application', page_specific_js, 'data-turbolinks-track' => true
        end
      end
    end
    

    layouts/application.html.haml:

     <!DOCTYPE html>
    %html{lang: 'uk'}
      %head   
        = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
       bla-bla-bla
        = js_page_specific_include   
       bla-bla-bla  
    
    0 讨论(0)
  • 2020-11-22 11:26

    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.

    0 讨论(0)
  • 2020-11-22 11:26

    This is how i solved the styling issue: (excuse the Haml)

    %div{:id => "#{params[:controller].parameterize} #{params[:view]}"}
        = yield
    

    This way i start all the page specific .css.sass files with:

    #post
      /* Controller specific code here */
      &#index
        /* View specific code here */
      &#new
      &#edit
      &#show
    

    This way you can easily avoid any clashes. When it comes to .js.coffee files you could just initialize elements like;

    $('#post > #edit') ->
      $('form > h1').css('float', 'right')
    

    Hope this helped some.

    0 讨论(0)
  • 2020-11-22 11:26

    I don't see an answer that really puts it all together and lays it out for you. Thus, I'll try to put meleyal, sujal (a la ClosureCowboy), the first part of Ryan's answer, and even Gal's bold statement about Backbone.js... all together in a way that is short and clear. And, who knows, I might even meet Marnen Laibow-Koser's requirements.

    Example edits

    assets/javascripts/application.js

    //= require jquery
    //= require jquery_ujs
    //= require lodash.underscore.min
    ...
    


    views/layouts/application.html.erb

      ...
      </footer>
    
      <!-- Javascripts ================================================== -->
      <!-- Placed at the end of the document so the pages load faster -->
      <%= javascript_include_tag "application" %>
      <%= yield :javascript %>
    
    </body>
    </html>
    


    views/foo/index.html.erb

    ...
    <% content_for :javascript do %>
      <%= javascript_include_tag params[:controller] %>
    <% end %>
    


    assets/javascripts/foo.js

    //= require moment
    //= require_tree ./foostuff
    


    assets/javascripts/foostuff/foothis.js.coffee

    alert "Hello world!"
    


    Brief description

    • Remove //= require_tree . from application.js and list only the JS that each page shares.

    • The two lines shown above in application.html.erb tell the page where to include application.js and your page-specific JS.

    • The three lines shown above in index.html.erb tells your view to look for some page-specific JS and include it at a named yield region called ":javascript" (or whatever you want to name it). In this example, the controller is "foo" so Rails will attempt to include "foo.js" at the :javascript yield region in the application layout.

    • List your page-specific JS in foo.js (or whatever the controller is named). List common libraries, a tree, directories, whatever.

    • Keep your custom page-specific JS someplace where you can easily reference it apart from your other custom JS. In this example, foo.js requires the foostuff tree so put your custom JS there, such as foothis.js.coffee.

    • There are no hard rules here. Feel free to move things around and perhaps even create multiple yield regions of various names in various layouts if needed. This just shows one possible first step forward. (I don't do it exactly like this given our use of Backbone.js. I might also choose to drop foo.js down into a folder called foo instead of foostuff but haven't decided that yet.)

    Notes

    You can do similar things with CSS and <%= stylesheet_link_tag params[:controller] %> but this is beyond scope of the question.

    If I missed a glaring best practice here, send me a note and I'll conisder adapting. Rails is fairly new to me and, honestly, I'm not terribly impressed so far with the chaos it brings by default to enterprise development and all the traffic the average Rails program generates.

    0 讨论(0)
  • 2020-11-22 11:26

    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 %>
    
    0 讨论(0)
  • 2020-11-22 11:28

    The LoadJS gem is another option:

    LoadJS provides a way to load page-specific Javascript code in a Rails app without loosing the magic provided by Sprockets. All your Javascript code will continue by minified in one Javascript file but some portions of it will only be executed for certain pages.

    https://github.com/guidomb/loadjs

    0 讨论(0)
提交回复
热议问题