Justin Puda
Justin Puda

Reputation: 79

Adding/Removing Cocoon Nested Fields

I'm using the awesome gem Cocoon to generate nested fields based on a "length" value that the user can select. My problem is that I need to find a way to check how many nested fields have already been added to the form, and then only add ones that are not in the form currently. For example:

A user has the length value set at 2, there are 2 sets of nested fields form them to work with. Now the user wants to change their value to 3, this is where I run into difficulty. I'm not entirely sure how to add/remove the fields based on the changed value, without just removing all the fields and re-running the nested fields insertion. Here is some code for reference:

HTML

<div class="row px-md-4">
  <div class="col-12">
    <div class="d-flex flex-column pb-3">
      <h2 class="f-500 dark_grey_text pt-3" style="font-size:1.8em;">Itinerary Details</h2>
      <p class="f-300">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
  </div>
  <div class="col-12 py-3">
    <div class="form-group">
      <h6>Set your length</h6>
      <%= f.number_field :length, placeholder:"How many days is your adventure?", value: number_with_precision(f.object.length, precision: 2), step: 1, class: 'stepper_navtext form-control', id: 'length' %>
    </div>
  </div>

  <div class="col-12 py-3">
    <div class="form-row mb-3" id="days">
      <div class="nav nav-pills" id="tab-menu" role="tablist">

      </div>

      <div class="tab-content" id="itinerary-tab-content">

      </div>

        <div id='itineraries' class='w-100 itineraries'>
          <%= f.fields_for :itineraries do |itinerary| %>
            <%= render 'adventure/listings/forms/itinerary_fields', :f => itinerary %>
          <% end %>
          <div class='links'>
            <%= link_to_add_association 'add day', f, :itineraries, partial: 'adventure/listings/forms/itinerary_fields', class:"d-none" %>
          </div>
        </div>
    </div>
  </div>
</div>

JS

$('#length').on('focusin', function(){
    console.log("Saving value " + $(this).val());
    $(this).data('val', $(this).val());
});

$( "#length" ).change(function() {

      var prev = $(this).data('val');
      var current = $(this).val();
      console.log("Prev value " + prev);
      console.log("New value " + current);

      $('#tab-menu').empty();

      $('.nested-fields').remove();

      let days = $('#length').val();
      var i;
      var counter = current - prev;
      console.log('The difference is' + counter);
      var text_max = 40;

      for (i = 0; i < days; i++) {
        var new_id;
        $(".links").find(">:first-child").trigger("click");
      }  ...

The only solution I've found is to clear all fields from the form, and regenerate the clicks on the add association button from Cocoon. It all seems a hacky to me and I don't think I'm doing this the right way :/. Any suggestions would be greatly appreciated!

Upvotes: 0

Views: 976

Answers (1)

nathanvda
nathanvda

Reputation: 50057

You are very close imho, so I am wondering what is holding you back to actually finding the solution. So let me first explain in pseudo-code (this always helps me when some problem seems insurmountable) :

  • when the length changes
  • check how the length changed
  • if length increased: add the fields
  • if it decreased: remove the fields from the back

So in code, that could look like

$( "#length" ).change(function() 
  var prev = $(this).data('val');
  var current = $(this).val();

  if (current > prev) {
    var days_to_add = current - prev; 
    for (i = 0; i < days_to_add; i++) {
      $(".links").find(">:first-child").trigger("click");
    }  
  } else {
    var days_to_remove = prev - current; 
    for (i = 0; i < days_to_remove; i++) {
      $('#itineraries .nested-fields').last().remove();  
    }  
  }

I am not entirely sure if your prev / current is set correctly, unless you are setting data('val'), but since prev is just the currently visible days, you also as well do something like:

var prev = $("#itineraries .nested-fields:visible').length; 
var current = $this.val();

(why use :visible? because if you also allow to remove-fields while editing we have to "hide" the field instead of just remove them)

This reminds me: if you do show the link_to_remove_association it is much better to click that, or if you add it as "hidden", because that will correct keep your form correct. This only applies if you also allow to edit existing forms, because in that case we need to keep the nested-child with a _destroy flag set, so rails can then destroy it when saving the form.

Upvotes: 1

Related Questions