Skip to content

Nested Fields

Dylan Fisher edited this page Apr 6, 2022 · 21 revisions

has_many sorted by position

# rails g forest:block AccordionBlock

class AccordionBlock < BaseBlock
  has_many :accordion_block_items, -> { order(:position) }, dependent: :destroy, inverse_of: :accordion_block
  accepts_nested_attributes_for :accordion_block_items, reject_if: :all_blank, allow_destroy: true
  validates_associated :accordion_block_items

  def self.permitted_params
    [
      accordion_block_items_attributes:
        [:_destroy, :id, :position, :title, :text]
    ]
  end
end
# rails g model AccordionBlockItem accordion_block:references position:integer title:string text:text

class AccordionBlockItem < ApplicationRecord
  belongs_to :accordion_block, inverse_of: :accordion_block_items, touch: true
end

Stacked layout

<%# app/views/blocks/accordion_block/_edit.html.erb %>

<div class="row small-gutters">
  <div class="col">
    <div class="sortable-field-set">
      <% if f.object.errors[:slideshow_block_items].present? && f.object.slideshow_block_items.blank? %>
        <div class="col-12 bg-danger text-white mb-3 p-3 rounded">
          Slideshow block items <%= f.object.errors[:slideshow_block_items].to_sentence %>
        </div>
      <% end %>
      <%= f.simple_fields_for :slideshow_block_items do |record| %>
        <%= render 'blocks/slideshow_block/slideshow_block_item_fields', f: record %>
      <% end %>
      <%= link_to_add_association (bootstrap_icon('plus-lg', embedded: true) + ' Add item'),
                                  f,
                                  :slideshow_block_items,
                                  partial: 'blocks/slideshow_block/slideshow_block_item_fields',
                                  class: 'btn btn-secondary mb-3',
                                  data: {
                                    association_insertion_node: 'this',
                                    association_insertion_method: 'before'
                                  } %>
    </div>
  </div>
</div>
<%# /app/views/blocks/accordion_block/_accordion_block_item_fields.html.erb %>

<div class="nested-fields sortable-field">
  <div class="card">
    <h3 class="card-header sortable-field-set__handle h5 d-flex align-items-baseline">
      <div class="nested-fields__header-label">
        <%= f.object.try(:title).presence || 'Slideshow item' %>
      </div>
      <%= link_to_remove_association (bootstrap_icon('x-lg', embedded: true) + ' Remove item'), f, class: 'nested-fields__remove-fields-button btn btn-outline-secondary ml-auto' %>
    </h3>
    <div class="card-body">
      <div class="row small-gutters">
        <div class="col">
          <%= f.association :media_item, as: :image %>
        </div>
      </div>
    </div>
  </div>
  <div class="sortable-field-set__position">
    <%= f.input :position, as: :hidden %>
  </div>
</div>

Inline layout

For simpler nested field structures, use the following pattern to add inline nested fields. This example also automatically instantiates the nested field field by calling the build method on it.

<%# /app/views/blocks/button_block/_edit.html.erb %>

<div class="row small-gutters">
  <div class="col">
    <% if f.object.errors[:button_block_items].present? && f.object.button_block_items.blank? %>
      <div class="bg-danger text-white mb-3 p-3 rounded">
        button block items <%= f.object.errors[:button_block_items].to_sentence %>
      </div>
    <% end %>
    <div class="row small-gutters button-block-cocoon-node sortable-field-set">
      <%= f.simple_fields_for :button_block_items, (f.object.button_block_items.build if f.object.button_block_items.blank?) do |record| %>
        <%= render 'blocks/button_block/button_block_item_fields', f: record %>
      <% end %>
    </div>
    <%= link_to_add_association (bootstrap_icon('plus-lg', embedded: true) + ' Add button'),
                                f,
                                :button_block_items,
                                partial: 'blocks/button_block/button_block_item_fields',
                                class: 'btn btn-secondary mb-3',
                                data: {
                                  association_insertion_traversal: 'prev',
                                  association_insertion_node: '.button-block-cocoon-node',
                                  association_insertion_method: 'append'
                                } %>
  </div>
</div>
<%# /app/views/blocks/button_block/_button_block_item_fields.html.erb %>

<div class="col-md-4 nested-fields sortable-field">
  <div class="card">
    <h3 class="card-header sortable-field-set__handle h5 d-flex align-items-baseline">
      <div class="nested-fields__header-label">
        <%= f.object.try(:label).presence || 'Button' %>
      </div>
      <%= link_to_remove_association (bootstrap_icon('x-lg', embedded: true)), f, class: 'nested-fields__remove-fields-button btn btn-outline-secondary ml-auto', title: 'Remove this item' %>
    </h3>
    <div class="card-body">
      <div class="row small-gutters">
        <div class="col">
          <%= f.input :title %>
        </div>
      </div>
    </div>
  </div>
  <div class="sortable-field-set__position">
    <%= f.input :position, as: :hidden %>
  </div>
</div>

Clone this wiki locally