เพิ่มฟิลด์ใหักับ nested attributes แบบ dynamic ด้วย stimulus

💡 ได้กลับมาแก้โปรเจคเก่าที่มีการใช้งาน Nested Attributes ก็ปรากฏว่ามันมีบัคในส่วนของการกดเพิ่มฟิลด์ให้กับ Nested Attributes ซึ่งพอมานั่งคิดๆ วิเคราะห์ดูถ้าตอนนั้นได้รู้จักกับ Stimulus และนำมาใช้ในโปรเจคก็คงจะช่วยให้พัฒนาได้ง่าย และรวดเร็วขึ้น

สำหรับโจทย์ในวันนนี้จะเป็นการบันทึกข้อมูลหนังสือ โดยหนังสือหนึ่งเล่มสามารถมีผู้แต่งได้หลายคน ซึ่งฟอร์มในการบันทึกจะทำการสร้างหนังสือ และรายชื่อผู้แต่งไปพร้อมๆ กัน

class Book < ActiveRecord::Base
  has_many :authors

  accepts_nested_attributes_for :authors, reject_if: :all_blank, allow_destroy: true
end

โมเดล Author จะถูกสร้างก็ต่อเมื่อค่าที่ส่งเข้ามาต้องไม่เป็นค่าว่าง และอนุญาตให้ลบข้อมูลได้ผ่าน attribute ที่ชื่อ _destroy

app/views/books/_form.html.erb

  <h2>Authors:</h2>
  <div class="authors" data-controller="nested">
    <template data-target="nested.template">
      <%= render 'authors/form', model: Author.new, form: form, child_index: 'NEXT_ID' %>
    </template>

    <div data-target="nested.parent">
      <%= render 'authors/form', model: nil, form: form, child_index: nil %>
    </div>
    <%= link_to "Add Author", "#", data: { action: "nested#add_associate" }, class: "btn" %>
  </div>

จะสังเกตเห็นได้ว่ามีการใช้ ในการเก็บแม่แบบสำหรับการสร้างฟอร์มแบบ dynamic

app/javascripts/controllers/nested_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "template", "parent" ]

  add_associate(event) {
    event.preventDefault()

    var content = this.templateTarget.innerHTML.replace(/NEXT_ID/g, new Date().getTime())
    this.parentTarget.insertAdjacentHTML('beforeend', content)
  }
}

ทันทีที่เกิดเหตุการณ์ add_associate โปรแกรมจะทำการดึงฟอร์มแม่แบบที่จะสร้างขึ้นมาและแทนที่ข้อความ NEXT_ID ด้วย timestamp และนำไปใส่เพิ่มในจุดที่ระบุไว้

app/views/authors/_form.html.erb

  <%= form.fields_for :authors, model, child_index: child_index, class: "-form" do |author_fields|%>
    <div class="form-group">
      <%= author_fields.label :full_name, class: "form-label col-2" %>
      <%= author_fields.text_field :full_name, class: "form-input col-7" %>
      <label class="form-checkbox col-2 mx-2">
        <%= author_fields.check_box :_destroy, class: "form-checkbox" %>
        <i class="form-icon"></i> Delete
      </label>
    </div>
  <% end %>

ตัวอย่าง

References