Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails cocoon gem: populate multiple fields

I'm using the cocoon gem on my rails app and I have two nested fields inside a form (categories, subcategories). Initially I'm showing the first one only and the second one is hidden. Each time the first select fields has subcategories the second field gets populated and appears.

The nested fields:

<div class="nested-fields">

  <%= form.input :category, collection: @categories, as: :grouped_select, group_method: :children, :label => false, :include_blank => true, input_html: { id: "first_dropdown" } %>

<div class="show_hide">
  <%= form.input :subcategories, label: false, :collection => [], :label_method => :name, :value_method => :id, required: true, input_html: { multiple: true, id: "second_dropdown" } %>
</div>

  <div class="links">
    <%= link_to_remove_association "Remove", form %>
  </div>
</div>

This is the code to initially hide the second field

$('#first_dropdown').keyup(function() {

    if ($(this).val().length == 0) {
        $('.show_hide').hide();
    }
}).keyup();

This is the code to show the second select input when the first select input has subcategories:

def select_item
  category = Category.find params[:category_id]
  render json: category.children
end


$('#first_dropdown').on('change', function() {
    $.ajax({
        url: 'categories/select_item?category_id=' + $(this).val(),
        dataType: 'json',
        success: function(data) {
            let childElement = $('#second_dropdown');
            if( data.length === 0 ) {
                $('.show_hide').hide();
            } else {
                $('.show_hide').show();
            }
            childElement.html('');
            data.forEach(function(v) {
                let id = v.id;
                let name = v.name;
                childElement.append('<option value="' + id + '">' + name + '</option>');
            });
        }
    });
});

Everything works well for the initial opened field. But when I add more fields and select a value inside any of the first select fields it populates all of the second fields according to that value. I think it's because I'm using specific id's for this and when I add more fields, all of them end up having the same id's. How can I set this up so I properly populate the second select field each time I add more nested fields to the form?

like image 236
Dev Avatar asked Mar 07 '20 10:03

Dev


Video Answer


1 Answers

I'd give your selects classes instead of ids:

<div class="nested-fields">
  <%= form.input :category, collection: @categories, as: :grouped_select, group_method: :children,
                 label: false, include_blank: true, input_html: { class: "first_dropdown" } %>

  <% if f.object.category && f.object.category.sub_categories.length > 0 %>
    <div class="show_hide">
      <%= form.input :subcategories, label: false, collection: form.object.category.subcategories, label_method: :name,
                     value_method: :id, required: true, input_html: { multiple: true, class: "second_dropdown" } %>
    </div>
  <% else %>
    <div class="show_hide" style="display: none;">
      <%= form.input :subcategories, label: false, collection: [], label_method: :name,
                     value_method: :id, required: true, input_html: { multiple: true, class: "second_dropdown" } %>
    </div>
  <% end %>
  <div class="links">
    <%= link_to_remove_association "Remove", form %>
  </div>
</div>

then find the second select relative to the first one adding this to a page specific js file:

$(document).on('turbolinks:load cocoon:after-insert', function(){
  $('.first_dropdown').off('change').on('change', function(){
    let $this = $(this);
    let $second = $this.closest('.nested-fields').find('.second_dropdown');
    $second.children().remove();
    $second.closest('.show_hide').hide();
    if ($this.val() !== '') {
      $.ajax({
        url: 'categories/select_item?category_id=' + $(this).val(),
        dataType: 'json',
        success: function(data){
          if (data.length > 0) {
            $second.append('<option value=""></option>');
            data.forEach(function(v) {
              let id = v.id;
              let name = v.name;
              $second.append('<option value="' + id + '">' + name + '</option>');
            });
            $second.closest('.show_hide').show();
          }
        }
      })
    }
  });
});
like image 55
Ben Trewern Avatar answered Oct 22 '22 00:10

Ben Trewern