Skip to content

Instantly share code, notes, and snippets.

@mdchaney
Created May 17, 2017 22:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdchaney/8d8de28b6776ca3d14630e8771267a3c to your computer and use it in GitHub Desktop.
Save mdchaney/8d8de28b6776ca3d14630e8771267a3c to your computer and use it in GitHub Desktop.
Help for dynamic subforms in Rails
<!--
In this file, the fields can be set to "required", but that will
cause issues. Specifically, when someone clicks "Remove" the fields
will be hidden and unless the "required" prop is set to false the form
will not submit. Also note that when showing this line if the object
is marked_for_destruction? it should be hidden. The javascript below
will handle both these issues.
-->
<tr class="line_item subform fields" id="line_item_<%= f.object.id %>">
<td>
<%= f.text_field :field_1 %>
</td>
<td>
<%= f.text_field :field_2 %>
</td>
<td>
<%= remove_child_link 'Remove', f %>
</td>
</tr>
<!--
Note that this belongs in a form where "f" is the form variable.
In this case, we are dealing with a child model called LineItem.
Make careful note of the pluralization of "line_item" below - some
must be plural and others singular. See the _line_item.html.erb
file for an example of the actual subform. Also note that you
must allow for "id" and "_destroy" fields in your strong parameters
for line_items_attributes.
-->
<fieldset>
<legend>Line Items</legend>
<table id='line_items_tbl'>
<thead>
<tr><th>Field 1</th><th>Field 2</th><th>Actions</th></tr>
</thead>
<tbody id="line_items">
<%= f.fields_for :line_items do |li_form|%>
<%= render partial: "line_item", locals: { f: li_form } %>
<% end %>
</tbody>
</table>
<%= add_child_button 'Add A Line Item', f, :line_items, partial: 'line_item' %>
</fieldset>
module ApplicationHelper
def remove_child_link(name, f, options = {})
confirm = options.delete(:confirm)
confirm = confirm.nil? ? true : confirm
f.hidden_field(:_destroy) + link_to(name, '#', onclick: "if (#{confirm ? 'false' : 'true'} || confirm('Do you really want to remove this item?')) remove_fields(this); return false;")
end
def add_child_link(name, f, method, options = {})
fields = new_child_fields(f, method, options)
link_to(name, '#', onclick: "insert_fields(this, \"#{method}\", \"#{escape_javascript(fields)}\"); return false;")
end
def new_child_fields(form_builder, method, options = {})
options[:object] ||= form_builder.object.class.reflect_on_association(method).klass.new
options[:partial] ||= method.to_s.singularize
options[:form_builder_local] ||= :f
form_builder.fields_for(method, options[:object], :child_index => "new_#{method}") do |f|
render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })
end
end
end
// Requires jquery
// helpers for nested forms
function insert_fields(link, method, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + method, "g")
if (content.match(/^\s*<tr\b/)) {
// Add it to a table
$(content.replace(regexp,new_id)).appendTo($(link).parents('fieldset').children('table:last'));
} else if (content.match(/^\s*<li\b/)) {
// Add it to a ul
$(content.replace(regexp,new_id)).appendTo($(link).parents('fieldset').children('ul:last'));
} else {
$(content.replace(regexp,new_id)).insertAfter($(link).parents('fieldset').children(':last'));
}
}
function remove_fields(link) {
$('input[type=hidden][name*="[_destroy]"]', $(link).parent()).val('true');
if ($(link).parents('.fields').size()>0) {
$(link).parents('.fields').first().hide();
$('input, select, textarea',$(link).parents('.fields').first()).prop('required',false);
}
}
function hide_removed_records() {
$('input[type=hidden][value=true][name$="[_destroy]"], input[type=checkbox][checked][value=1][name$="[_destroy]"]').each(function() {
if ($(this).parents('.fields').size()>0) {
$(this).parents('.fields').first().hide();
$('input',$(link).parents('.fields').first()).prop('required',false);
}
});
}
$(document).ready(function() {
hide_removed_records();
});
@mdchaney
Copy link
Author

This code is used to handle add/remove for child records in a Ruby on Rails form. As an example, I'm using a child model called LineItem. Each file here is necessary. You must modify the form and subform to suit your particular data model, and include the relevant code in your ApplicationHelper as well as in your javascript directory. This code absolutely relies on jQuery.

@mdchaney
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment