由于没有人对此提供答案,即使是悬赏,我终于设法自己解决了这个问题。这不应该是一个树桩!希望这将在Rails 3.0中更容易实现。
Andy的示例是直接删除记录的好方法,而无需向服务器提交表单。在这种情况下,我真正想要的是一种在对嵌套表单进行更新之前动态添加/删除字段的方法。这是一种稍微不同的情况,因为随着字段的删除,在提交表单之前,它们实际上并未删除。我可能最终会根据情况同时使用两者。
我已经基于Tim Riley在github上的complex-forms-
examples分支实现了我的实现。
首先设置模型,并确保它们支持嵌套属性:
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 => trueendclass PhoneNumber < ActiveRecord::base belongs_to :personend为PhoneNumber的表单字段创建局部视图:
<div > <%= f.text_field :description %> <%= f.text_field :number %></div>
接下来为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 -%>这将通过为PhoneNumber模型创建一组模板字段来实现,我们可以使用javascript复制这些模板字段。我们将
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 endenddef add_child_link(name, association) link_to(name, "javascript:void(0)", :class => "add_child", :"data-association" => association)enddef remove_child_link(name, f) f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :class => "remove_child")end现在,将这些帮助程序方法添加到edit部分:
<% 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 -%> <p><%= add_child_link "New Phone Number", :phone_numbers %></p> <%= 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用原型+
lowpro
替换我的代码。其基本思想是,您无需将巨大的javascript函数嵌入到标记中,也不必
phone_numbers=()在模型中编写繁琐的函数。一切正常。万岁!
经过一些进一步的测试,我得出结论,需要将模板移出
<form>字段。将它们保留在此处意味着它们将与表单的其余部分一起发送回服务器,这只会在以后造成麻烦。
我将其添加到布局的底部:
<div id="jstemplates"> <%= yield :jstemplates %></div
并修改了
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 endend现在,您可以
:reject_if从模型中删除这些子句,而不必担心模板被发回。



