我试图克服Rails中动态表单字段的障碍 - 这似乎是框架无法正常处理的东西.我也在我的项目中使用jQuery.我安装了jRails,但我更愿意尽可能不显眼地编写AJAX代码.
我的表格相当复杂,两层或三层嵌套并不罕见.我遇到的问题是生成正确的表单ID,因为它们依赖于表单生成器上下文.我需要能够动态添加新字段或删除has_many
关系中的现有记录,我完全不知所措.
到目前为止,我见过的每个例子都以这种或那种方式丑陋.Ryan Bates的教程需要RJS,这会在标记中产生一些非常难看的突出javascript,并且似乎是在嵌套属性之前编写的.我已经看到了一个带有不显眼的jQuery的示例的分支,但我只是不明白它在做什么,并且无法让它在我的项目中运行.
有人可以提供一个简单的例子来说明这是如何完成的吗?在尊重控制器的RESTful约定时,这是否可行?
Andy发布了一个删除现有记录的优秀示例,任何人都可以提供使用正确属性创建新字段的示例吗?我无法弄清楚如何使用嵌套表单执行此操作.
既然没有人提供这方面的答案,即使在赏金之后,我终于设法让自己得到了这个工作.这不应该是一个傻瓜!希望这在Rails 3.0中更容易实现.
Andy的示例是直接删除记录的好方法,无需向服务器提交表单.在这种特殊情况下,我真正想要的是在更新嵌套表单之前动态添加/删除字段的方法.这是一个略有不同的情况,因为删除字段后,在提交表单之前不会实际删除它们.我可能会根据情况最终使用两者.
我的实现是基于Tim Riley在github上的复杂形式示例 fork.
首先设置模型,并确保它们支持嵌套属性:
class Person < ActiveRecord::Base has_many :phone_numbers, :dependent => :destroy accepts_nested_attributes_for :phone_numbers, :reject_if => lambda { |p| p.values.all?(&:blank?) }, :allow_destroy => true end class PhoneNumber < ActiveRecord::Base belongs_to :person end
为PhoneNumber的表单字段创建部分视图:
<%= f.text_field :description %> <%= f.text_field :number %>
接下来编写Person模型的基本编辑视图:
<% form_for @person, :builder => LabeledFormBuilder do |f| -%> <%= f.text_field :name %> <%= f.text_field :email %> <% f.fields_for :phone_numbers do |ph| -%> <%= render :partial => 'phone_number', :locals => { :f => ph } %> <% end -%> <%= f.submit "Save" %> <% end -%>
这将通过为我们可以使用javascript复制的PhoneNumber模型创建一组模板字段来工作.我们将app/helpers/application_helper.rb
为此创建辅助方法:
def new_child_fields_template(form_builder, association, options = {}) options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new options[:partial] ||= association.to_s.singularize options[:form_builder_local] ||= :f content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f| render(:partial => options[:partial], :locals => { options[:form_builder_local] => f }) end end end def add_child_link(name, association) link_to(name, "javascript:void(0)", :class => "add_child", :"data-association" => association) end def remove_child_link(name, f) f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :class => "remove_child") end
现在将这些辅助方法添加到编辑部分:
<% form_for @person, :builder => LabeledFormBuilder do |f| -%> <%= f.text_field :name %> <%= f.text_field :email %> <% f.fields_for :phone_numbers do |ph| -%> <%= render :partial => 'phone_number', :locals => { :f => ph } %> <% end -%><%= add_child_link "New Phone Number", :phone_numbers %>
<%= new_child_fields_template f, :phone_numbers %> <%= f.submit "Save" %> <% end -%>
你现在已经完成了js模板化.它将为每个关联提交一个空白模板,但:reject_if
模型中的子句将丢弃它们,只留下用户创建的字段.更新: 我重新思考了这个设计,见下文.
这不是真正的AJAX,因为除了页面加载和表单提交之外没有任何通信进入服务器,但老实说在事后我找不到办法.
事实上,这可能提供比AJAX更好的用户体验,因为在完成之前,您不必等待每个附加字段的服务器响应.
最后我们需要用javascript连接它.将以下内容添加到`public/javascripts/application.js'文件中:
$(function() { $('form a.add_child').click(function() { var association = $(this).attr('data-association'); var template = $('#' + association + '_fields_template').html(); var regexp = new RegExp('new_' + association, 'g'); var new_id = new Date().getTime(); $(this).parent().before(template.replace(regexp, new_id)); return false; }); $('form a.remove_child').live('click', function() { var hidden_field = $(this).prev('input[type=hidden]')[0]; if(hidden_field) { hidden_field.value = '1'; } $(this).parents('.fields').hide(); return false; }); });
到这个时候你应该有一个准系统的动态形式!这里的javascript非常简单,可以很容易地用其他框架完成.例如,您可以application.js
使用prototype + lowpro 轻松替换我的代码.基本的想法是你没有在你的标记中嵌入巨大的javascript函数,你不必phone_numbers=()
在模型中编写繁琐的函数.一切正常.万岁!
经过一些进一步的测试,我得出结论,模板需要移出字段.将它们保留在那里意味着它们会与表单的其余部分一起被发送回服务器,这只会在以后产生令人头疼的问题.
我已将此添加到我的布局底部:
<%= yield :jstemplates %>并修改了
new_child_fields_template
帮手:def new_child_fields_template(form_builder, association, options = {}) options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new options[:partial] ||= association.to_s.singularize options[:form_builder_local] ||= :f content_for :jstemplates do content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f| render(:partial => options[:partial], :locals => { options[:form_builder_local] => f }) end end end end现在,您可以
:reject_if
从模型中删除子句,并不再担心要发送回的模板.