使用Rails 3.1,您在哪里放置“特定于页面”的JavaScript代码?

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

据我所知,所有的javascript都被合并到一个文件中。当Rails将//= require_tree .添加到application.js清单文件的底部时,默认情况下会执行此操作。

这听起来像是一个真正的节省程序,但我有点担心特定于页面的JavaScript代码。此代码是否在每一页上执行?我最不希望的是,当我的所有对象只需要在一个页面上使用时,它们就可以为每个页面实例化。

另外,是否也存在代码冲突的可能性?

或者在页面底部放置一个小的script标记,该标记只调用一个为页面执行javascript代码的方法?

那么,您不再需要require.js了吗?

谢谢

编辑:我很感激所有的答案…我认为他们并没有真正解决这个问题。其中一些是关于造型的,似乎不相关…其他人只提到了javascript_include_tag…我知道它存在(显然是…),但是看起来Rails3.1前进的方式是将所有的javascript打包成一个文件,而不是在每个页面的底部加载单独的javascript。

我能想到的最佳解决方案是用ids或classes在div标签中包装某些特性。在javascript代码中,只需检查idclass是否在页面上,如果在页面上,则运行与其关联的javascript代码。这样,如果动态元素不在页面上,javascript代码就不会运行——即使它已经包含在由链轮打包的大量application.js文件中。

我上面的解决方案的好处是,如果在100页中的8页中包含一个搜索框,那么它将只在这8页中运行。你也不必在网站的8页上包含相同的代码。事实上,您将永远不需要在您的站点上的任何地方再次包含手动脚本标记。

我认为这是我问题的实际答案。


资产管道文档建议如何执行特定于控制器的JS:

For example, if a ProjectsController is generated, there will be a new file at app/assets/javascripts/projects.js.coffee and another at app/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] %>.

链接到:资产管线


对于特定于页面的JS,可以使用Garber Irish解决方案。

因此,对于两个控制器(汽车和用户),您的rails javascripts文件夹可能如下所示:

1
2
3
4
5
6
7
8
9
10
javascripts/
├── application.js
├── init.js
├── markup_based_js_execution
├── cars
│   ├── init .js
│   ├── index.js
│   └── ...
└── users
    └── ...

JavaScripts将如下所示:

1
2
3
4
5
6
// application.js

//=
//= require init.js
//= require_tree cars
//= require_tree users
1
2
3
4
5
6
7
8
9
// init.js

SITENAME = new Object();
SITENAME.cars = new Object;
SITENAME.users = new Object;

SITENAME.common.init = function (){
  // Your js code for all pages here
}
1
2
3
4
5
// cars/init.js

SITENAME.cars.init = function (){
  // Your js code for the cars controller here
}
1
2
3
4
5
// cars/index.js

SITENAME.cars.index = function (){
  // Your js code for the index method of the cars controller
}

基于标记的执行将包含用于Util对象的代码,并在dom ready Util.init执行时包含这些代码。

别忘了把它放到你的布局文件里:

1
<body data-controller="<%= controller_name %>" data-action="<%= action_name %>">

我还认为,为了获得更好的页面特定的CSS,最好使用类而不是data-*属性。正如Jason Garber所提到的:特定于页面的CSS选择器会变得非常笨拙(当您使用data-*属性时)

我希望这对你有帮助。


我知道你已经回答了自己的问题,但还有一个选择:

基本上,你假设

1
//= require_tree .

是必需的。不是这样。请随意取下它。在我当前的应用程序中,我第一次使用3.1.x,坦白地说,我制作了三个不同的顶级JS文件。我的application.js文件只有

1
2
3
4
5
//= require jquery
//= require jquery_ujs
//= require_directory .
//= require_directory ./api
//= require_directory ./admin

通过这种方式,我可以创建子目录,以及它们自己的顶级JS文件,这些子目录只包含我需要的内容。

关键是:

  • 您可以删除require_tree-Rails允许您更改它所做的假设
  • 名称application.js没有什么特别之处——assets/javascript子目录中的任何文件都可以包含带有//=的预处理指令。
  • 希望这对关闭牛仔的答案有所帮助并增加了一些细节。

    苏加尔


    另一种选择是:要创建特定于页面或模型的文件,可以在assets/javascripts/文件夹中创建目录。

    1
    2
    3
    assets/javascripts/global/
    assets/javascripts/cupcakes
    assets/javascripts/something_else_specific

    您的主要application.js清单文件可以配置为从global/加载其文件。特定的页面或页面组可以有自己的清单,这些清单可以从自己的特定目录加载文件。链轮会自动将application.js加载的文件与页面特定的文件组合在一起,从而使此解决方案能够工作。

    这种技术也可用于style_sheets/


    我很感激所有的答案…我认为他们并没有真正解决这个问题。其中一些是关于造型的,似乎不相关…其他人只是提到了EDOCX1[0]我知道它存在(显然是…),但是看起来Rails3.1前进的方式是将所有的javascript打包成一个文件,而不是在每个页面的底部加载单独的javascript。

    我能想到的最佳解决方案是用ids或classes在div标签中包装某些特性。在javascript代码中。然后检查idclass是否在页面上,如果在页面上,则运行与之相关的javascript代码。这样,如果动态元素不在页面上,javascript代码就不会运行——即使它已经包含在由链轮打包的大量application.js文件中。

    我上面的解决方案的好处是,如果在100页中的8页中包含一个搜索框,那么它将只在这8页中运行。你也不必在网站的8页上包含相同的代码。事实上,您将永远不需要在站点的任何地方再次包含手动脚本标记-除了可能预加载数据。

    我认为这是我问题的实际答案。


    我意识到我来这个派对有点晚了,但我想加入一个我最近一直使用的解决方案。不过,让我先提一下……

    轨道3.1/3.2路(不,先生。我不喜欢。)

    请参阅:http://guides.rubyonrails.org/asset_pipeline.html如何使用资产管道

    为了完整起见,我将以下内容包括在这个答案中,因为这不是一个不可行的解决方案…虽然我不太在乎。

    "Rails Way"是一个面向控制器的解决方案,而不是像这个问题的原始作者所要求的那样面向视图。有以各自的控制器命名的特定于控制器的JS文件。所有这些文件都放在文件夹树中,默认情况下,任何application.js require指令都不包含该文件夹树。

    要包含控制器特定的代码,将向视图添加以下内容。

    1
    <%= javascript_include_tag params[:controller] %>

    我讨厌这个解决方案,但它存在而且很快。您可以将这些文件称为"people index.js"和"people show.js",然后使用"#{params[:controller]}-index"之类的文件来获得面向视图的解决方案。再说一次,速战速决,但我觉得不舒服。

    我的数据属性方式

    称我疯狂,但我希望在部署时,所有的JS都被编译并缩小到application.js中。我不想忘记把这些零散的文件放在这里。

    我将所有的JS加载到一个紧凑的文件中,很快就会被浏览器缓存。如果我的application.js中的某一部分需要在页面上激发,我会让HTML告诉我,而不是Rails。

    我使用一个名为data-jstags的自定义数据属性,而不是将JS锁定到特定的元素ID,或者将HTML与标记类混淆在一起。

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

    在每个页面上,我都使用-在这里插入首选JS库方法-在DOM完成加载后运行代码。此引导代码执行以下操作:

  • 遍历用data-jstag标记的DOM中的所有元素
  • 对于每个元素,在空间上拆分属性值,创建一个标记字符串数组。
  • 对于每个标记字符串,在哈希中查找该标记。
  • 如果找到匹配的键,则运行与其关联的函数,将元素作为参数传递。
  • 假设我在application.js中的某个地方定义了以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    };

    bootstrapping事件将对搜索输入应用my_autosuggest_initmy_hint_init函数,将其转换为一个在用户键入时显示建议列表的输入,并在输入为空且未聚焦时提供某种输入提示。

    除非某个元素被标记为data-jstag="auto-suggest",否则自动建议代码永远不会触发。但是,它总是在我的application.js中存在、缩小并最终缓存,以满足我在页面上需要它时的需要。

    如果需要向标记的JS函数传递其他参数,则必须应用一些创造力。或者添加数据参数属性,想出某种参数语法,或者甚至使用混合方法。

    即使我有一些看起来是特定于控制器的复杂工作流,我也只需要在lib文件夹中为它创建一个文件,将其打包到application.js中,并用类似"新事物向导"的东西标记它。当我的引导程序点击那个标签时,我的漂亮的、奇特的向导将被实例化并运行。它在需要时为控制器的视图运行,但不与控制器耦合。事实上,如果我正确地编写向导代码,我可能能够在视图中提供所有配置数据,因此以后可以为任何其他需要它的控制器重新使用我的向导。

    不管怎样,这就是我已经实现特定于页面的JS一段时间了,它为简单的站点设计和更复杂/丰富的应用程序提供了很好的服务。希望我在这里介绍的两种解决方案之一,我的方法或Rails方法,对将来遇到这个问题的任何人都有帮助。


    这在很久以前就已经得到了回答和接受,但是我根据这些答案中的一些和我在Rails3+上的经验提出了自己的解决方案。

    资产管道是甜蜜的。用它。

    首先,在您的application.js文件中,删除//= require_tree.

    然后在您的application_controller.rb中创建一个助手方法:

    1
    2
    3
    4
    5
    6
    7
    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">'
        end
    end

    然后在您的application.html.erb布局文件中,在现有的javascript包括中添加新的助手,前缀为raw助手:

    1
    2
    3
    4
    5
    6
    <head>
        Your Application
        <%= stylesheet_link_tag"application", :media =>"all" %>
        <%= javascript_include_tag"application" %>
        <%= raw javascript_include_view_js %>
    </head>

    现在,您可以使用与Rails中其他地方相同的文件结构轻松创建视图特定的JavaScript。把你的文件放在app/assets/:namespace/:controller/action.js.erb里就行了!

    希望能帮助别人!


    也许你会发现可插拔的js gem是合适的解决方案。


    1
    <%= javascript_include_tag params[:controller] %>


    您可以在布局文件中添加此行(例如application.html.erb)以自动加载控制器特定的javascript文件(生成控制器时创建的文件):

    1
    <%= javascript_include_tag params[:controller] %>

    您还可以添加一行,以根据每个操作自动加载脚本文件。

    1
    <%= javascript_include_tag params[:controller] +"/" + params[:action] %>

    只需将页面脚本放入以控制器名称命名的子目录。在这些文件中,您可以使用=require包含其他脚本。最好创建一个助手,仅在文件存在时包含该文件,以避免在浏览器中出现404失败。


    loadJS gem是另一个选项:

    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


    菲利普的回答很好。以下是使其工作的代码:

    在application.html.erb中:



    假设您的控制器被称为项目,它将生成:



    然后在projects.js.coffee中:

    1
    2
    3
    4
    jQuery ->
      if $('body.projects').length > 0  
         $('h1').click ->
           alert 'you clicked on an h1 in Projects'


    这就是我解决造型问题的方法:(请原谅哈姆语)

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

    这样我就可以用以下命令启动所有特定于页面的.css.sass文件:

    1
    2
    3
    4
    5
    6
    7
    #post
      /* Controller specific code here */
      &#index
        /* View specific code here */
      &#new
      &#edit
      &#show

    这样你就可以很容易地避免任何冲突。当涉及到.js.coffee文件时,您可以初始化以下元素:

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

    希望这对一些人有所帮助。


    只有当您告诉Rails(而不是链轮)合并它们时,才会合并JavaScripts。


    您还可以将JS分组到文件夹中,并继续使用资产管道根据页面选择性地加载JavaScript。


    我看不出一个真正能把所有这些都结合起来并为你安排好的答案。因此,我将尝试将Meleyal、Sujal(洛杉矶闭幕牛仔)、Ryan回答的第一部分,甚至Gal关于主干网的大胆声明。以一种简洁明了的方式。而且,谁知道呢,我甚至可以满足玛琳·莱博·科瑟的要求。

    实例编辑

    资产/javascripts/application.js

    1
    2
    3
    4
    //= require jquery
    //= require jquery_ujs
    //= require lodash.underscore.min
    ...

    < BR>视图/布局/application.html.erb

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      ...
      </footer>

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

    </body>
    </html>

    < BR>视图/foo/index.html.erb

    1
    2
    3
    4
    ...
    <% content_for :javascript do %>
      <%= javascript_include_tag params[:controller] %>
    <% end %>

    < BR>资产/javascripts/foo.js

    1
    2
    //= require moment
    //= require_tree ./foostuff

    < BR>资产/javascripts/foostuff/foothis.js.coffee

    1
    alert"Hello world!"

    < BR>

    简要说明

    • 从application.js中删除//= require_tree .,只列出每个页面共享的js。

    • 上面application.html.erb中显示的两行告诉页面在哪里包含application.js和特定于页面的js。

    • 上面index.html.erb中显示的三行告诉您的视图查找一些特定于页面的JS,并将其包含在一个名为":javascript"(或任何您想命名的JS)的指定收益区域中。在本例中,控制器是"foo",因此Rails将尝试在应用程序布局中的:javascript yield区域包含"foo.js"。

    • 在foo.js中列出特定于页面的js(或控制器的名称)。列出公共库、树、目录等。

    • 将您的自定义页面特定JS放在一个可以轻松引用它的地方,而不必使用其他自定义JS。在本例中,foo.js需要foostuff树,因此将定制的js放在那里,如foo this.js.coffee。

    • 这里没有硬性规定。如果需要的话,可以随意移动,甚至可以在不同的布局中创建多个不同名称的产量区域。这只是一个可能的第一步。(考虑到我们使用了backbone.js,我并不是这样做的。我也可以选择将foo.js放到一个名为foo的文件夹中,而不是foostuff,但还没有决定。)

    笔记

    您可以使用css和<%= stylesheet_link_tag params[:controller] %>执行类似的操作,但这超出了问题的范围。

    如果我错过了这里一个精彩的最佳实践,请给我发一张便条,我会配合的。Rails对我来说是相当新的,老实说,到目前为止,它给企业开发和平均Rails程序生成的所有流量带来的默认混乱并没有给我留下深刻印象。


    我同意您的回答,要检查选择器是否存在,请使用:

    1
    2
    3
    if ($(selector).length) {
        // Put the function that does not need to be executed every page
    }

    (没有看到有人添加实际的解决方案)


    我还有另一个解决方案,虽然原始的方法对我来说很好,而且不需要任何花哨的选择性加载策略。放入noral document ready函数,然后测试当前的Windows位置,看看它是否是您的javascript的目标页面:

    1
    2
    3
    4
    5
    $(document).ready(function() {
       if(window.location.pathname.indexOf('/yourpage') != -1) {
              // the javascript you want to execute
       }
    }

    这仍然允许Rails3.x在一个小包中加载所有的JS,但是不会产生太多的开销,也不会与JS不打算加载的页面发生任何冲突。


    Ryguy的答案是一个很好的答案,尽管它被否决成了消极的观点。

    尤其是如果您使用的是主干JS——每个页面都有自己的主干视图。然后,erb文件只有一行内联javascript,它将触发右主干视图类。我认为它是一行"粘合代码",因此它的内联是可以的。优点是你可以保留你的"需求树",让浏览器缓存所有的javascript。

    在show.html.erb中,您将看到如下内容:

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

    在布局文件中,您需要:

    1
    <%= yield :javascript %>


    将所有Commom JS文件移动到"app/assets/javascript/global"这样的子文件夹中,然后在application.js中将//= require_tree .行修改为//= require_tree ./global

    现在,您可以将特定于控制器的JS放在"app/assets/javascript/"根目录下,它们不会包含在编译后的JS中,只在您通过控制器/视图上的= javascript_include_tag调用它们时使用。


    尽管你有几个答案,我认为你的编辑可能是最好的选择。我们在团队中使用的一个设计模式是Dispatcher模式,它是我们从Gitlab获得的。它的作用类似于您所说的,但是页面名称是由rails在body标签中设置的。例如,在布局文件中,只包含如下内容(在haml中):

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

    然后在javascripts文件夹中的dispatcher.js.coffee文件中只有一个闭包和一个switch语句,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $ ->
      new Dispatcher()

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

    在单个文件(例如,products.js.coffeelogin.js.coffee中,您所需要做的就是将它们包含在一个类中,然后将该类符号全球化,这样您就可以在调度器中访问它:

    1
    2
    3
    4
    class Products
      constructor: ->
        #do stuff
    @Products = Products

    Gitlab有几个这样的例子,如果你好奇的话,你可能会想四处看看。


    Paloma项目提供了管理页面特定的javascript代码的有趣方法。

    文档中的用法示例:

    1
    2
    3
    4
    5
    6
    var UsersController = Paloma.controller('Users');

    // Executes when Rails User#new is executed.
    UsersController.prototype.new = function(){
       alert('Hello Sexy User!' );
    };

    步骤1。移除需要的树。在application.js和application.css中。

    步骤2。在布局文件夹中编辑application.html.erb(按rails默认)。在以下标记中添加"params[:controller]"。

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

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

    步骤3。在config/initializers/assets.rb中添加文件

    1
    2
    3
    %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

    参考文献:http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/


    首先:从application.js中删除EDOCX1[0]第二:你所有的JS代码都必须在/app/assets/javascritpt上分配,所有的CSS代码都必须在/app/assets/stylesheets上分配。


    我以前用过这个方法:http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/。超级简单,依靠控制器选择要加载的适当JS。


    我把一些答案组合成:

    应用程序帮助程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    布局/application.html.haml:

    1
    2
    3
    4
    5
    6
    7
     <!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

    我还没试过,但看起来是这样的:

    • 如果您有一个内容是javascript(例如其中包含真正的javascript),那么链轮将不知道它,因此这与现在的工作方式相同。

    • 如果你想从大量的javascript中排除一个文件,你可以进入config/sprockets.yml文件并相应地修改源文件。然后,您将只包括在需要时排除的任何文件。


    在Ryan的带领下,我做了以下工作-

    application.js.咖啡

    1
    2
    3
    4
    5
    $ ->
        view_method_name = $("body").data("view") +"_onload"
        eval("#{view_method_name}()") if eval("typeof #{view_method_name} == 'function'")
        view_action_method_name = $("body").data("view") +"_"+$("body").data("action")+"_onload"
        eval("#{view_action_method_name}()") if eval("typeof #{view_action_method_name} == 'function'")

    users.js.coffee(特定于控制器的coffeescript,例如controller:users,action:dashboard)

    1
    2
    3
    4
    window.users_dashboard_onload = () ->
        alert("controller action called")
    window.users_onload = () ->
        alert("controller called")

    application.html.haml应用程序

    1
    %body{:data=>{:view=>controller.controller_name, :action=>controller.action_name}}


    这里介绍了如何实现这一点,特别是当您不必为特定页面执行大量的库,而只需要或多或少地运行几百行JS时。

    由于将javascript代码嵌入HTML中是非常好的,所以只需在app/views shared.js目录下创建,并将您的页面/页面特定代码放在mycoolu partial.html.erb中。

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    <!--
      var your_code_goes_here = 0;
      function etc() {
         ...
      }
    -->

    所以现在从你想去的地方开始,你只要做:

    1
      = render :partial => 'shared.js/my_cool_partial'

    就这样,K?