我试图克服Rails中动态表单字段的障碍——这似乎是框架无法很好地处理的问题.我还在我的项目中使用jQuery.我已经安装了Jrail,但我更愿意在可能的情况下以低调的方式编写AJAX代码.

我的体型相当复杂,两到三个层次的筑巢并不罕见.我遇到的问题是生成正确的表单ID,因为它们非常依赖于表单生成器上下文.我需要能够在has_many个关系中动态添加新字段或删除现有记录,我完全不知所措.

到目前为止,我看到的每一个例子都在某种程度上是丑陋的.Ryan Bates的tutorial需要RJS,这会导致标记中出现一些非常难看的突兀javascript,而且似乎是在嵌套属性之前编写的.我已经看到了一个简单的jQuery示例,但我不明白它在做什么,也无法在我的项目中运行.

有人能提供一个简单的例子来说明如何做到这一点吗?在遵守控制器的RESTful约定的情况下,这是可能的吗?


Andy发布了一个删除现有记录的极好示例,有人能提供一个创建具有正确属性的新字段的示例吗?我一直不知道如何使用嵌套表单来实现这一点.

推荐答案

由于没有人对此给出答案,即使是在获得赏金后,我最终还是设法自己解决了这个问题.这不应该是个树桩!希望在Rails 3.0中更容易做到这一点.

Andy的例子是一种直接删除记录的好方法,无需向服务器提交表单.在这种情况下,我真正想要的是一种在更新嵌套表单之前动态添加/删除字段的方法.这是一种稍微不同的情况,因为当字段被删除时,在表单提交之前,它们实际上不会被删除.根据具体情况,我可能最终会使用这两种方法.

我的实现基于github上的Tim Riley's complex-forms-examples 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的表单字段创建局部视图:

<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中为此创建帮助器方法:

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 -%>
  <p><%= add_child_link "New Phone Number", :phone_numbers %></p>
  <%= new_child_fields_template f, :phone_numbers %>
  <%= f.submit "Save" %>
<% end -%>

现在已经完成了js模板制作.它将 for each 关联提交一个空白模板,但模型中的:reject_if子句将放弃它们,只留下用户创建的字段.Update: I've rethought this design, see below.

这不是真正的AJAX,因为除了页面加载和表单提交之外,服务器上没有任何通信,但我真的找不到一种事后处理的方法.

事实上,这可能比AJAX提供更好的用户体验,因为在完成之前,您不必等待每个附加字段的服务器响应.

最后,我们需要用javascript将其连接起来.将以下内容添加到"public/javascripts/应用程序"中.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非常简单,可以很容易地用其他框架完成.例如,你可以轻松地用prototype+lowpro替换我的application.js代码.其基本思想是,您不需要在标记中嵌入巨大的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
  end
end

现在,您可以从模型中删除:reject_if个子句,不再担心模板被发回.

Ruby-on-rails相关问答推荐

Trailblazor 操作 -> 集合 :attribute(has_many 通过关联) 与填充器 -> 如何填充 Reform::Form 形式的 slugs 数组?

Rails 和 jsonb 类型jsonb不存在

将日期时间转换为月、日和年?

如何在 Ruby on Rails 中创建一个锚点并重定向到这个特定的锚点

在 Rails 中将毫秒转换为格式化日期

rails:防止闪烁消息显示两次

如何避免 RSpec 3.0 中 stub_chain 的弃用警告?

OpenSSL::SSL::SSLError: SSL_connect SYSCALL returned=5 errno=0 state=SSLv3 read server hello A

在公共 Rails 应用程序中将敏感数据存储在哪里?

是否有与 PHP 的 isset() 等效的 Rails?

Rails 4 查找没有子元素的父母

在 rake 任务期间关闭观察者的简单方法?

保存后重定向到索引而不是显示

是否可以使用 Rails 迁移重命名索引?

确认后如何进行设计重定向

在 Rails 协会中未找到名为的协会可能拼写错误的问题

如何创建迁移以仅在索引存在时删除索引,而不是在不存在时抛出异常?

如何在 rake 任务中强制 RAILS_ENV?

将新管理员添加到活动管理员

没有UTC的Rails 3默认日期时间格式