在 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
正在传递给部分。根本问题是我似乎在设置无限递归:
- 部分调用
link_to_add_fields
以便我的 +
图标可以作为 "add child" link.
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());
});
我被困在这里有一段时间了...提醒一下,解决问题通常有多种方法,重要的是不要拘泥于一个想法。感谢大家的建议和意见。
我已经使用 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
正在传递给部分。根本问题是我似乎在设置无限递归:
- 部分调用
link_to_add_fields
以便我的+
图标可以作为 "add child" link. 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());
});
我被困在这里有一段时间了...提醒一下,解决问题通常有多种方法,重要的是不要拘泥于一个想法。感谢大家的建议和意见。