在 Rails 部分生成动态子行

Generating dynamic child rows in Rails partial

我已经使用 nested attributes Railscast 作为指南实现了嵌套属性形式。因此,用户可以单击一个图标来动态地将 "child" 行添加到我的视图中。

不幸的是,我只能对我视图中的最后一个图标进行此操作(如图所示 here)。这个图标是在我的视图中生成的,但其他图标是在用于呈现每一行的部分中生成的。

这可以吗?如果是这样,最好的方法是什么?

这是我最近的尝试。

Sheet has_many 插槽。在 sheet 编辑视图中,我使用 sheet 表单生成器 (sheet) 来渲染我的插槽部分并将其传递给助手 link_to_add_fields,后者渲染 link 单击时将生成一个新行(这部分工作正常)。你会注意到我也试图将 sheet 传递给部分,以便我可以从那里调用 link_to_add_fields 但这是它崩溃的地方:

视图 - edit.html.haml:

= sheet.fields_for :slots do |builder|
  = render 'slots/edit_fields', f: builder, sheet:sheet
= link_to_add_fields image_tag("plus.jpg", size:"18x18", alt:"Plus"), sheet, :slots, 'slots/edit'

部分 - _edit_fields.html.haml:

- random_id = SecureRandom.uuid
.row.signup{:id => "edit-slot-#{random_id}"}
  .col-md-1
    %span.plus-icon
      = link_to_add_fields image_tag("plus.jpg", size:"18x18", alt:"Plus"), sheet, :slots, 'slots/edit'
    %span.minus-icon
      = image_tag "minus.jpg", size:"18x18", alt:"Minus"
  .col-md-2= f.text_field :label
  ... other fields ...

辅助方法:

def link_to_add_fields(name, f, association, partial)
  new_object = f.object.send(association).klass.new
  id = new_object.object_id
  fields = f.fields_for(association, new_object, child_index: id) do |builder|
    render(partial.to_s.singularize + "_fields", f: builder, name: name)
  end
  link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end

我收到 undefined local variable or method 'sheet' 从局部调用助手。基本上,我需要 sheet (父)表单生成器在每个 link 上可用,以便助手工作。或者我需要放弃这种方法并使用 AJAX (也尝试过)。

更新

稍微调试后,很明显 sheet 正在传递给部分。根本问题是我似乎在设置无限递归:

  1. 部分调用 link_to_add_fields 以便我的 + 图标可以作为 "add child" link.
  2. link_to_add_fields 呈现部分,以便在按下 + 图标时可以生成字段。

我 运行 关注的另一个问题是当原始子项被渲染时,它们在属性集合中获得顺序索引 (0, 1, 2, ...)。因此,即使我想出了一种在原始行中呈现新子行的方法,我也不确定在提交表单时如何在没有大量 jQuery 体操的情况下维持子行的顺序或东西。

undefined local variable or method 'sheet'

这是由于您渲染局部的方式造成的。

= render 'slots/edit_fields', f: builder, sheet:sheet

不足以将变量传递给部分变量。您需要:

= render partial: 'slots/edit_fields', locals: {f: builder, sheet:sheet}

这将使 f 在您的局部可用。

这听起来非常接近我几个月前必须解决的问题。

如果我没记错的话,您的用户有一个 'things' 列表,您希望您的用户能够使用显示的表单添加另一个 'thing'。

我是这样处理的。我的 'thing' 是帝国,用户可以在管理个人数据(例如更改电子邮件地址和密码)的范围内更改名称、删除帝国或添加新帝国。

<%= form_for @user do |f| %>
<%= render layout: "users/partials/fields",
                 locals: {f: f} do %>

<b>Empires</b><br>
  <% n = 0 %>
  <%= f.fields_for :empires do |empire_form, index| %>
    <% n += 1 %>
    <%= empire_form.label :name, class: 'ms-indent' %>
    <%= empire_form.text_field :name, class: 'form_control' %>
    <%= empire_form.label :_destroy, 'Delete' %>
    <%= empire_form.check_box :_destroy %>
    <br>
  <% end %>

  <!-- Empty field for new Empire -->
  <b class='ms-indent'>Name</b>
    <input class="form_control" value="" name="user[empires_attributes][<%=n+1%>][name]" id="user_empires_attributes_0_name" type="text"> <b>new</b>
  <br>

<% end %>
  <%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>

有趣的一点是获取现有帝国数量的计数器(我不记得为什么我不只使用大小或长度)并在 html 输入字段中使用 'n+1'确保新帝国包含在提交的表单中,并带有正确的 ID。请注意 html 输入标签的使用,Rails 没有等效项。

[只是根据 DickieBoys 的评论补充说明。这种 'n+1' 方法有效,但可能会被随机数替换并且仍然有效。我打算稍后再试验一下,但它确实有效,所以我并不着急。]

部分'fields'是这样的,注意中间的'yield'。

<%= f.label :name %>
<%= f.text_field :name, class: 'form-control', autofocus: true %>

<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>

<%= yield %>

<%= f.label :password %>
<%= f.password_field :password, class: 'form-control', value: "" %>

<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>

'fields' 部分包含一个包含我的帝国代码的 do-end 块。部分包括此块,其中声明 'yield'.

最后,更新控制器代码。

def update
    @user = User.find(params[:id])
    updated = false
    begin
      updated = @user.update_attributes(user_params)
    rescue ActiveRecord::DeleteRestrictionError
      flash[:warning] = "Empire contains ships. Delete these first."
    end 
    if updated
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end

我不会假装这是唯一的解决方案,甚至不一定是最好的。但是花了几天时间才弄明白(Google 并且 Whosebug 让我失望了!)并且从那时起一直可靠地工作。关键实际上是放弃尝试寻找 Rails 解决方案来添加新帝国,而只使用 html 和新帝国的新 id。你也许可以改进它。希望对你有帮助。

我最终用 jQuery 解决了这个问题。不是超级优雅但非常有效。基本上,我只是为 + 图标添加了一个单击处理程序,它构建了新行并将其插入到需要的地方(基本上只是破解了复制 HTML 所必需的 jQuery Rails).如果我的用例在将来帮助其他人,这里是 jQuery(请参阅原始 post 中的 imgur link 以供参考):

//click handler to add edit row when user clicks on the plus icon
$(document).on('click', '.plus-icon', function (event) {

  //build a new row
  var one_day = 24 * 3600 * 1000;
  var d = new Date();
  var unique_id = d.getTime() % one_day + 10000;            // unique within a day and offset past original IDs
  var new_div =
    $('<div/>', {'class': 'row signup'}).append(
      $('<div/>', {'class': 'col-md-1'}).append(
        $('<span/>', {'class': 'plus-icon'}).append(
          '<img alt="Plus" src="/assets/plus.jpg" width="18" height="18" />'
        )
      ).append(
        $('<span/>', {'class': 'minus-icon'}).append(
          '<img alt="Minus" src="/assets/minus.jpg" width="18" height="18" />'
        )
      )
    ).append(
      $('<div/>', {'class': 'col-md-2'}).append(
        '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][label]" id="sheet_slots_attributes_'+unique_id+'_label">'
      )
    ).append(
      $('<div/>', {'class': 'col-md-2'}).append(
        '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][name]" id="sheet_slots_attributes_'+unique_id+'_name">'
      )
    ).append(
      $('<div/>', {'class': 'col-md-2'}).append(
        '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][email]" id="sheet_slots_attributes_'+unique_id+'_email">'
      )
    ).append(
      $('<div/>', {'class': 'col-md-2'}).append(
        '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][phone]" id="sheet_slots_attributes_'+unique_id+'_phone">'
      )
    ).append(
      $('<div/>', {'class': 'col-md-2'}).append(
        '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][comments]" id="sheet_slots_attributes_'+unique_id+'_comments">'
      )
    );
  var new_input = 
    '<input id="sheet_slots_attributes_'+unique_id+'_id" type="hidden" name="sheet[slots_attributes]['+unique_id+'][id]" value="'+unique_id+'">';

  //insert new row before clicked row
  $(new_div).insertBefore($(this).parent().parent());
  $(new_input).insertBefore($(this).parent().parent());
});

我被困在这里有一段时间了...提醒一下,解决问题通常有多种方法,重要的是不要拘泥于一个想法。感谢大家的建议和意见。