我正在尝试克服Rails中动态表单字段的障碍- 这似乎是框架无法很好地处理的。我也在我的项目中使用jQuery。我已经安装了jRails,但是我更愿意在可能的情况下毫不干扰地编写AJAX代码。
我的表单相当复杂,嵌套的两个或三个级别并不罕见。我遇到的问题是生成正确的表单ID,因为它们是如此依赖于表单构建器上下文。我需要能够动态添加新字段或删除has_many关系中的现有记录,而我完全不知所措。
has_many
到目前为止,我所看到的每个示例都以一种或另一种方式丑陋。Ryan Bates的教程要求使用RJS,这会导致标记中出现一些难看的JavaScript,而且似乎是在嵌套属性之前编写的。我已经看到了使用不引人注目的jQuery的该示例的分支,但是我只是不了解它在做什么,并且无法使其在我的项目中正常工作。
有人可以提供一个简单的示例说明如何完成此操作吗?在遵守控制器的RESTful约定的情况下,这甚至可能吗?
安迪发布了删除现有记录的绝佳示例,有人可以提供创建具有正确属性的新字段的示例吗?我还无法弄清楚如何使用嵌套表单。
由于没有人对此提供答案,即使是悬赏,我终于设法自己解决了这个问题。这不应该是一个树桩!希望这将在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 => true end class PhoneNumber < ActiveRecord::Base belongs_to :person end
为PhoneNumber的表单字段创建局部视图:
<div class="fields"> <%= 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为此创建辅助方法:
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
现在,将这些帮助程序方法添加到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模型中的子句将丢弃它们,仅保留用户创建的字段。 更新: 我已经重新考虑了这种设计,请参见下文。
: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=()在模型中编写繁琐的函数。一切正常。万岁!
application.js
phone_numbers=()
经过一些进一步的测试,我得出结论,需要将模板移出<form>字段。将它们保留在此处意味着它们将与表单的其余部分一起发送回服务器,这只会在以后造成麻烦。
<form>
我将其添加到布局的底部:
<div id="jstemplates"> <%= yield :jstemplates %> </div
并修改了new_child_fields_template助手:
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从模型中删除这些子句,而不必担心模板被发回。