使用Rails 3.1,你把你的“页面特定”JavaScript代码?
据我了解,所有的JavaScript被合并到1文件。 当Rails添加//= require_tree .
时,Rails会默认执行此操作//= require_tree .
到你的application.js
清单文件的底部。
这听起来像是一个真正的生活节省,但我有点关注页面特定的JavaScript代码。 此代码是否在每个页面上执行? 我想要的最后一件事就是当我们只需要在1页上的时候就为每个页面实例化所有的对象。
另外,是不是也有冲突的代码的可能性?
或者你在页面的底部放一个小script
标签,只是调用一个方法来执行页面的JavaScript代码?
那么你不再需要require.js了吗?
谢谢
编辑 :我欣赏所有的答案…我不认为他们真的在解决问题。 其中一些是关于样式,似乎不涉及…和其他人只是提到javascript_include_tag
…我知道存在(显然…),但似乎Rails 3.1前进的方式是包装所有的Javascript到1个文件中,而不是在每个页面的底部加载单个的Javascript。
我能想出的最好的解决方案是用id
或ES class
div
标签包装某些功能。 在javascript代码中,您只需检查页面上是否存在id
或class
,如果是,则运行与之关联的javascript代码。 这样,如果动态元素不在页面上,JavaScript代码不会运行 – 即使它包含在由Sprockets打包的大量application.js
文件中。
我的上述解决方案具有如下优点:如果在100个页面中的8个页面上包含搜索框,则它将仅在这8个页面上运行。 您也不必在网站上的8页上包含相同的代码。 事实上,您再也不必在网站上包含手动脚本标记。
我认为这是我的问题的实际答案。
资产管道文档建议如何做控制器特定的JS:
例如,如果生成了一个
ProjectsController
,那么在app/assets/javascripts/projects.js.coffee
将会有一个新文件,而在app/assets/stylesheets/projects.css.scss
将会有一个新文件。 您应该将任何JavaScript或CSS独特的控制器放入其各自的资产文件中,因为这些文件可以通过<%= javascript_include_tag params[:controller] %>
或<%= stylesheet_link_tag params[:controller] %>
。
链接到:asset_pipeline
对于页面特定的JS,你可以使用Garber-Irish解决方案 。
所以你的Rails的JavaScript文件夹可能看起来像这样的两个控制器 – 汽车和用户:
javascripts/ ├── application.js ├── init.js ├── markup_based_js_execution ├── cars │ ├── init .js │ ├── index.js │ └── ... └── users └── ...
而javascripts将如下所示:
// application.js //= //= require init.js //= require_tree cars //= require_tree users
// init.js SITENAME = new Object(); SITENAME.cars = new Object; SITENAME.users = new Object; SITENAME.common.init = function (){ // Your js code for all pages here }
// cars/init.js SITENAME.cars.init = function (){ // Your js code for the cars controller here }
// cars/index.js SITENAME.cars.index = function (){ // Your js code for the index method of the cars controller }
而markup_based_js_execution将包含UTIL对象的代码和DOM准备好的UTIL.init执行。
不要忘记把它放到你的布局文件中:
<body data-controller="<%= controller_name %>" data-action="<%= action_name %>">
我也认为最好是使用类而不是data-*
属性,以获得更好的页面特定的CSS。 正如Jason Garber所说:页面特定的CSS选择器会变得非常尴尬(当你使用data-*
属性时)
我希望这会帮助你。
我看到你已经回答了你自己的问题,但是这里有另一个选择:
基本上,你正在做这个假设
//= require_tree .
是必须的。 不是。 随意删除它。 在我目前的应用程序中,第一个我正在用3.1.x做的,我做了三个不同的顶级JS文件。 我的application.js
文件只有
//= require jquery //= require jquery_ujs //= require_directory . //= require_directory ./api //= require_directory ./admin
这样,我可以创建子目录,其自己的顶级JS文件,只包括我所需要的。
关键是:
- 你可以删除
require_tree
– Rails让你改变它的假设 - 名称
application.js
没有什么特别之处 –assets/javascript
子目录中的任何文件都可以包含预处理器指令,其中//=
希望这有助于ClosureCowboy的答案并增加一些细节。
Sujal
另一种选择:创建页面或模型特定的文件,你可以在assets/javascripts/
文件夹中创建目录。
assets/javascripts/global/ assets/javascripts/cupcakes assets/javascripts/something_else_specific
您的主要application.js
清单文件可以配置为从global/
加载它的文件。 特定的页面或页面组可以有自己的清单,从他们自己的特定目录加载文件。 链轮会自动将由application.js
加载的文件与特定于页面的文件结合起来,从而使此解决方案可以正常工作。
这个技术也可以用于style_sheets/
。
我很欣赏所有的答案…我不认为他们真的在解决问题。 其中一些是关于样式,似乎不涉及…和其他人只是提到javascript_include_tag
…我知道存在(显然…),但似乎Rails 3.1前进的方式是包装所有的Javascript到1个文件中,而不是在每个页面的底部加载单个的Javascript。
我能想到的最好的解决方案是用id
或ES class
div
标签包装某些功能。 在JavaScript代码中。 然后你只要检查页面上是否有id
或class
,如果是,就运行与之关联的javascript代码。 这样,如果动态元素不在页面上,JavaScript代码不会运行 – 即使它包含在由Sprockets打包的大量application.js
文件中。
我的上述解决方案具有如下优点:如果在100个页面中的8个页面上包含搜索框,则它将仅在这8个页面上运行。 您也不必在网站上的8页上包含相同的代码。 事实上,您再也不需要在网站上再添加手动脚本标记 – 除了可能预先加载数据。
我认为这是我的问题的实际答案。
我意识到自己要晚一点来参加这个派对,但是我想提出一个我最近使用的解决方案。 不过,让我先提一提…
Rails 3.1 / 3.2(不,先生,我不喜欢)
请参阅: http : //guides.rubyonrails.org/asset_pipeline.html#how-to-use-the-asset-pipeline
为了完整起见,我在这个答案中包含了以下内容,因为这不是一个不可行的解决方案,尽管我不太在乎。
“Rails方式”是一个面向控制器的解决方案,而不是以这个问题的原始作者的请求为导向。 有控制器特定的JS文件命名的各自的控制器。 所有这些文件都放置在一个文件夹树中,该文件树在任何application.js require指令中都不包含默认值。
要包含特定于控制器的代码,以下内容将添加到视图中。
<%= javascript_include_tag params[:controller] %>
我讨厌这个解决方案,但它在那里,它很快。 据推测,你可以调用这些文件,像“people-index.js”和“people-show.js”,然后使用"#{params[:controller]}-index"
来获得一个视图导向的解决方案。 再次,快速解决,但它不适合我。
我的数据属性方式
叫我疯了,但我希望我所有的JS编译和缩小到application.js当我部署。 我不想记得在整个地方包含这些小碎片文件。
我将所有的JS加载到一个紧凑的,即将被浏览器缓存的文件中。 如果我的application.js需要在页面上被触发,我让HTML告诉我,而不是Rails。
我不使用标记类将我的JS锁定到特定的元素ID或抛弃我的HTML,而是使用名为data-jstags
的自定义数据属性。
<input name="search" data-jstag="auto-suggest hint" />
在每一页上,我使用– 在这里插入首选的JS库方法 –在DOM完成加载时运行代码。 该引导代码执行以下操作:
- 迭代DOM中标记为
data-jstag
所有元素 - 对于每个元素,将空间上的属性值拆分,创建一个标签字符串数组。
- 对于每个标记字符串,在该标记的哈希中执行查找。
- 如果找到一个匹配的键,运行与它关联的函数,将该元素作为参数传递。
所以说,我有我的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 };
引导事件将对搜索输入应用my_autosuggest_init
和my_hint_init
函数,将其转换为输入,在用户输入时显示建议列表,并在输入空白且未聚焦时提供某种输入提示。
除非某些元素标记为data-jstag="auto-suggest"
,否则自动提示代码不会触发。 但是,它总是在那里,缩小并最终缓存在我的application.js中,这是我在页面上需要的时间。
如果您需要将其他参数传递给标记的JS函数,则需要应用一些创造性。 可以添加data-paramter属性,提出某种参数语法,甚至可以使用混合方法。
即使我有一些看起来特定于控制器的复杂工作流程,我也只是在我的lib文件夹中为它创建一个文件,将它打包到application.js中,并用类似'new-thing-wizard'的标签来标记它。 当我的引导程序打到标签时,我的漂亮的花式向导将被实例化并运行。 它在需要时为该控制器的视图运行,但不以其他方式耦合到控制器。 实际上,如果我将向导编码为正确的,我可以在视图中提供所有的配置数据,因此可以稍后重新使用我的向导,以供任何需要它的控制器使用。
无论如何,这是我一直在实现页面特定的JS一段时间,它为我简单的网站设计和更复杂/丰富的应用程序都很好。 希望这里提出的两个解决方案之一,我的方式或Rails的方式,对未来遇到这个问题的任何人都有帮助。
这已经被很久以前的回答和接受了,但是基于这些答案以及我对Rails 3+的经验,我提出了自己的解决方案。
资产管道是甜蜜的。 用它。
首先,在你的application.js
文件中,移除//= require_tree.
然后在你的application_controller.rb
创建一个辅助方法:
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
然后在你的application.html.erb
布局文件中,将你的新帮手添加到现有的javascript包含中,前缀为raw
帮助程序:
<head> <title>Your Application</title> <%= stylesheet_link_tag "application", :media => "all" %> <%= javascript_include_tag "application" %> <%= raw javascript_include_view_js %> </head>
瞧,现在你可以很容易地创建视图特定的JavaScript使用相同的文件结构,你在轨道上的其他地方使用。 只需将文件粘贴到app/assets/:namespace/:controller/action.js.erb
!
希望能帮助别人!
您可以在布局文件(例如application.html.erb)中添加此行来自动加载控制器特定的JavaScript文件(在生成控制器时创建的文件):
<%= javascript_include_tag params[:controller] %>
您也可以添加一行来以每个操作为基础自动加载脚本文件。
<%= javascript_include_tag params[:controller] + "/" + params[:action] %>
只要把你的页面脚本放到一个以控制器名字命名的子目录中即可。 在这些文件中,您可以使用= require来包含其他脚本。 创建一个帮助程序来包含文件,如果它存在,这将是很好的,以避免在浏览器中的404失败。
<%= javascript_include_tag params[:controller] %>
也许你会找到pluggable_js gem作为合适的解决方案。
LoadJS的宝石是另一种选择:
LoadJS提供了一种在Rails应用程序中加载页面特定的Javascript代码的方法,而不会失去Sprockets提供的魔法。 您的所有Javascript代码将通过一个Javascript文件缩小,但其中的某些部分只会被某些页面执行。
菲利普的回答非常好。 这是使其工作的代码:
在application.html.erb中:
<body class="<%=params[:controller].parameterize%>">
假设你的控制器叫做Projects,那将会产生:
<body class="projects">
然后在projects.js.coffee中:
jQuery -> if $('body.projects').length > 0 $('h1').click -> alert 'you clicked on an h1 in Projects'
只有当你告诉Rails(链接,而不是)合并它们时,才会合并JavaScript。
这就是我解决造型问题的方法:( 借口哈姆)
%div{:id => "#{params[:controller].parameterize} #{params[:view]}"} = yield
这样我开始所有页面特定的.css.sass文件:
#post /* Controller specific code here */ &#index /* View specific code here */ &#new &#edit &#show
这样可以轻松避免任何冲突。 当涉及.js.coffee文件,你可以只是初始化的元素,如;
$('#post > #edit') -> $('form > h1').css('float', 'right')
希望这有助于一些。
您还可以将文件夹中的js分组,并继续使用资源管道根据页面选择性地加载您的javascript 。
我同意你的答案,检查是否有选择者,使用:
if ($(selector).length) { // Put the function that does not need to be executed every page }
(没有看到任何人添加实际的解决方案)
我没有看到一个真正把所有东西放在一起的答案,并为你提供了答案。 因此,我会尝试把Ryan的答案的第一部分,甚至Gal关于Backbone.js 的大胆的陈述,以一个简短而清晰的方式放入meleyal , sujal (一个ClosureCowboy )。 而且,谁知道,我甚至可能会遇到马林·莱布·科斯的要求。
示例编辑
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!"
简要描述;简介
-
删除
//= require_tree .
从application.js中只列出每个页面共享的JS。 -
上面的两行显示在application.html.erb中,告诉页面包含application.js和你的页面特定的JS。
-
在index.html.erb中显示的三行告诉你的视图寻找一些页面特定的JS,并将其包含在一个名为“:javascript”的命名的yield区域(或任何你想命名它)。 在这个例子中,控制器是“foo”,所以Rails将尝试在应用程序布局的javascript yield区域包含“foo.js”。
-
在foo.js (或任何控制器命名的)中列出特定于页面的JS。 列出常见的库,树,目录,不管。
-
保持你的自定义页面特定的JS的地方,你可以很容易地引用它除了你的其他自定义JS。 在这个例子中,foo.js需要foostuff树,所以把你的自定义JS,如foothis.js.coffee 。
-
这里没有硬性规定。 随意移动的东西,甚至可能在不同的布局创建多个不同名称的产量区域,如果需要的话。 这只是向前迈出的第一步。 (我不这样做,就像我们使用Backbone.js一样,我也可以选择将foo.js放到一个名为foo的文件夹中而不是foostuff中,但还没有决定。
笔记
你可以用CSS和<%= stylesheet_link_tag params[:controller] %>
做类似的事情,但是这超出了问题的范围。
如果我错过了一个明显的最佳实践,给我一个便条,我会conisder适应。 Rails对我来说是相当新颖的,实际上,到目前为止,由于默认情况下会导致企业发展和Rails普通程序所产生的所有流量,所以我不会感到印象深刻。
I have another solution, which although primitive works fine for me and doesn't need any fancy selective loading strategies. Put in your nornal document ready function, but then test the current windows location to see if it is the page your javascript is intended for:
$(document).ready(function() { if(window.location.pathname.indexOf('/yourpage') != -1) { // the javascript you want to execute } }
This still allows all the js to be loaded by rails 3.x in one small package, but does not generate much overhead or any conflicts with pages for which the js isn't intended.
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 %>
Move all your commom JS files to a sub-folder like 'app/assets/javascript/global' then in the application.js, modify the //= require_tree .
line to //= require_tree ./global
.
Now you are free to put your controller-specific JS on the 'app/assets/javascript/' root and they will not be included in compiled JS, being used just when you call them via = javascript_include_tag
on your controller/view.
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 🙂
Paloma project offers interesting approach to manage page specific javascript code.
Usage example from their docs:
var UsersController = Paloma.controller('Users'); // Executes when Rails User#new is executed. UsersController.prototype.new = function(){ alert('Hello Sexy User!' ); };
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/
I haven't tried this out, but it looks like the following is true:
-
if you have a content_for that is javascript (eg with real javascript within it), sprockets would not know about it and thus this would work the same way as it does now.
-
if you want to exclude a file from the big bundle of javascript, you would go into config/sprockets.yml file and modify the source_files accordingly. Then, you would just include any of the files that you excluded where needed.
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.
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
Following the lead from Ryan, here's what I have done-
application.js.coffee
$ -> 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 (controller specific coffeescript,eg controller:users, action:dashboard)
window.users_dashboard_onload = () -> alert("controller action called") window.users_onload = () -> alert("controller called")
application.html.haml
%body{:data=>{:view=>controller.controller_name, :action=>controller.action_name}}
Here's how to do it especially if you don't have to execute tons of libraries for your specific page, but only to run a few hundreds lines of JS more or less.
Since it's perfectly fine to embed Javascript code into HTML, just create under app/views shared.js directory and place there your page/pages specific code inside my_cool_partial.html.erb
<script type="text/javascript"> <!-- var your_code_goes_here = 0; function etc() { ... } --> </script>
So now from wherever you want you simply do:
= render :partial => 'shared.js/my_cool_partial'
And that's it, k?