简体   繁体   中英

Dynamically generating three levels deep nested forms in Rails

I'm attempting to created a deeply nested form with dynamically generated content using the examples laid out in this question. However when I try to do it more than one level deep, I run into issues because the content generation happens more than once in the first generated form and causes nothing to happen when I try to add fields from the second level of generated content.

simplified model layout,

Task
  has_one VendorUpload
    has_many VendorShippingLogs
      has_many VendorShippingLogProducts

the Vendor Shipping Log Products is where I'm stuck because that form is attempting to generate its content within the Vendor Shipping Log content that is itself dynamically generated and I'm unsure how to code it into its own Helper. Attached code,

Models -->

class Task < ApplicationRecord
  has_one :vendor_upload, :dependent => :destroy
  has_many :vendor_shipping_logs, :through => :vendor_upload
  has_many :vendor_shipping_log_products, :through => :vendor_shipping_logs

  accepts_nested_attributes_for :vendor_upload, :reject_if => :upload_type_blank, :allow_destroy => true

end

class VendorUpload < ApplicationRecord

  belongs_to :task
  has_many :vendor_shipping_logs, inverse_of: :vendor_upload, :dependent => :destroy
  has_many :vendor_shipping_log_products, :through => :vendor_shipping_logs

  accepts_nested_attributes_for :vendor_shipping_logs

end

class VendorShippingLog < ApplicationRecord

  belongs_to :vendor_upload
  has_many :vendor_shipping_log_products, inverse_of: :vendor_shipping_log, :dependent => :destroy

  accepts_nested_attributes_for :vendor_shipping_log_products

end

class VendorShippingLogProduct < ApplicationRecord

  belongs_to :vendor_shipping_log

end

tasks_controller.rb

def new
  @task = Task.new
  @task.build_vendor_upload.vendor_shipping_logs.build.vendor_shipping_log_products.build

end
private
  def task_params
    params.require(:task).permit!
  end

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_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

  def add_child_link(name, association)
    link_to(name, "javascript:void(0)", :class => "add_child btn btn-success btn-sm fa fa-plus-circle", :style => "margin-left: 15px;margin-top: 15px;", :"data-association" => association)
  end

  def remove_child_link(name, f)
    f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :style => "height: 30px; margin-left: 5px;", :class => "remove_child btn btn-danger fa fa-close")
  end

new.html.erb -->

<%= form_for @task, url: tasks_path, method: :post, :html => {:multipart => true } do |f| %>
  <%= f.fields_for :vendor_upload do |ff| %>
    <%= ff.fields_for :vendor_shipping_logs do |fff| %>
      <%= render "vendor_shipping_log", :f => fff %>
    <% end %>
    <p><%= add_child_link " Add Customer", :vendor_shipping_logs %></p>
    <%= new_child_fields_template f, :vendor_shipping_logs %>
  <% end %>
<% end %>
<div id="jstemplates">
  <%= yield :jstemplates %>
</div>

_vendor_shipping_log.html.erb -->

<%= f.fields_for :vendor_shipping_log_products do |ff| %>
  <%= render "vendor_shipping_log_product", :f => ff %>
<% end %>
<%= new_child_fields_template f, :vendor_shipping_log_products %>
<p><%= add_child_link " Add Product", :vendor_shipping_log_products %></p>

<%= remove_child_link "", f %>

_vendor_shipping_log_product.html.erb -->

<div class="fields">
  <div class="form-group form-group-sm">
    <%= f.label :item_id, "Item ID:", class: 'col-sm-3 control-label' %>
    <div class="col-sm-9">
      <div style="display: inline-flex;">
        <%= f.text_field :item_id, class: "form-control" %>
      </div>
    </div>
  </div>
  <%= remove_child_link "", f %>
</div>

application.js -->

$(function() {
  $('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;
  });
});
$(function(){
  $(document).on("click", 'a.remove_child', function() {
    var hidden_field = $(this).prev('input[type=hidden]')[0];
    if(hidden_field) {
      hidden_field.value = '1';
    }
    $(this).parents('.fields').hide();
    return false;
  });
});

The code here basically mimics everything from the example and works fine for one level deep nested attributes, but anything beyond that simply doesn't work. I don't get any feedback when trying to click the "Add Product" button from the code above.

I hope I laid out everything here clearly enough as it's a difficult problem to explain as a novice. Would appreciate any points in the right direction!

This might be worth a try:

$(function() {
  $(document).on('click', 'a.add_child', function() {
    var association, new_id, regexp, template;
    association = $(this).attr('data-association');
    template = $('#' + association + '_fields_template').html();
    regexp = new RegExp('new_' + association, 'g');
    new_id = (new Date).getTime();
    $(this).parent().before(template.replace(regexp, new_id));
    $();
    return false;
  });
});

Normal click events in jQuery don't bind to dynamically generated content. You should use on .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM