0%

動態CRUD回饋商品資料(stimulus js)

新增、修改並儲存giveback資料

本篇內容是基於專案進行過程的紀錄,有一些前置內容可參考這篇:
如何在一個form裡面寫入兩個model的資料?(nested_form)

  1. 為避免畫面太過雜亂,我先將project的_form.html.erb裡面原本的giveback form內容拉出來render

    1
    2
    3
    4
    #views/project/_form.html.erb
    <%= form.fields_for :givebacks do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #views/project/_giveback_field.html.erb
    <div class='m-3'>
    <%= form.label :title, '回饋商品' %>
    <%= form.text_field :title, class:'border-2'%>
    </div>
    <div class='m-3'>
    <%= form.label :price, '價格' %>
    <%= form.text_field :price, class:'border-2' %>
    </div>
  2. 修改_giveback_field.html.erb,建立一個div把form包起來,新增一個class以便後續js的操作,這邊選擇用content_tag是因為後面還要在裡面寫判斷。

    https://apidock.com/rails/ActionView/Helpers/TagHelper/content_tag

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #views/project/_giveback_field.html.erb
    <%= content_tag :div, class: "nested_field" do %>
    <div class='m-3'>
    <%= form.label :title, '回饋商品' %>
    <%= form.text_field :title, class:'border-2'%>
    </div>
    <div class='m-3'>
    <%= form.label :price, '價格' %>
    <%= form.text_field :price, class:'border-2' %>
    </div>
    <% end %>
  3. 在project的_form.html.erb裡面建立一個template標籤的giveback空表格,之後可以用stimulus js去抓它,塞進頁面裡面達到新增giveback表單的目的。

    1
    2
    3
    4
    5
    6
    #views/project/_form.html.erb
    <template>
    <%= form.fields_for :givebacks, Giveback.new, child_index: 'new_record' do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>
    </template>
    • 這邊會選用template的標籤,是因為它在網頁載入時不會被渲染,但可以使用js把它實體化。

    • child_index:讓這個template標籤裡面的資料有不一樣的id跟name來區別它跟其他已產生的giveback form,後面我們會再用js去改變這個值,讓它是獨一無二的,可作為它的id,讓rails知道他是新的資料然後寫進資料庫,而其他的已經存在的物件,我們可以在資料庫找到它,所以只需要更新資料就可以了。
      Setting child_index while using nested attributes mass assignment

  4. 接下來就可以用一個div把這些fields_for的表單包進去,然後加上data-controller,交給stimulus來監管

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #views/project/_form.html.erb
    <div data-controller='nested-form' >

    <template data-nested-form-target='template'>
    <%= form.fields_for :givebacks, Giveback.new, child_index: 'new_record' do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>
    </template>

    <%= form.fields_for :givebacks do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>

    </div>
  5. 設定完controller,接下來就是我們要操控的target,既然要動態新增回饋商品,就需要一個按鈕來做這件事,另外最後再給它一個action,監聽它的點擊事件,然後去找相對應的controller及action。

    1
    2
    3
    4
    #views/project/_form.html.erb
    <div class='mb-3' data-nested-form-target='links'>
    <%= link_to '新增回饋商品', '#', data: { action: 'click->nested-form#add_new_giveback' } %>
    </div>
  6. 接下來我們就正式要開始進入stimulus了,首先在javascript資料夾裡面建立nested_form_controller.js(注意controller名字的慣例, 在html上是寫nested-form),然後寫下要控制的target

    1
    2
    3
    4
    #javascript/controllers/nested_form_controller.js
    export default class extends Controller {
    static targets = [ "links", "template" ]
    }
  7. 加上監聽click事件之後執行的方法add_new_giveback,先停止它的預設事件(點擊後跳頁),接著抓到templateTarget(命名慣例),把我們之前塞進去的child_index改掉,使用當下的時間替代掉new_record(不一定要取這個名字,可以自己任意命名),如此一來,每個點擊後加進來的資料的name跟id都不會重複,rails可以以此辨別是否為新的資料。

    1
    2
    3
    4
    5
    add_new_giveback(event) {
    event.preventDefault()

    let content = this.templateTarget.innerHTML.replace(/new_record/g, new Date().getTime())
    }

  8. 接著把這個新的表單塞到按鈕的上方。

    1
    2
    3
    4
    5
    6
    add_new_giveback(event) {
    event.preventDefault()

    let content = this.templateTarget.innerHTML.replace(/new_record/g, new Date().getTime())
    this.linksTarget.insertAdjacentHTML('beforebegin', content)
    }

    到這邊已經可以新增、修改並儲存giveback的資料了,接下來就是刪除的部分。

刪除giveback資料

跟新增修改時一樣,在頁面上的giveback資料有分成已經在資料庫的,以及還未進資料庫的,我們就從後者開始處理。

  1. 首先一樣先寫一個刪除資料的按鈕,然後監聽點擊事件,然後在後面外層的div下判斷,看這些資料是否還沒被儲存,如果是就回傳true。
    https://apidock.com/rails/ActiveRecord/Base/new_record%3F

    1
    2
    3
    4
    <%= content_tag :div, class: "nested_field", data: { new_record: form.object.new_record? } do %>
    ····(略)····
    <%= link_to '刪除回饋商品', '#', class:'mb-3 text-red-400', data: { action: 'click->nested-form#remove_giveback'}%>
    <% end %>

  2. 回到js這邊寫方法,一樣先停止預設行為,然後找到表單最外層的div,如果判斷它還未被寫進資料庫,點擊刪除鍵之後直接整個remove掉。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    remove_giveback(event) {
    event.preventDefault()

    let wrapper = event.target.closest('.nested_field')
    if (wrapper.dataset.newRecord == 'true'){
    wrapper.remove()
    }else{
    }
    }
  3. 接著處理以及存在的資料,還記得我們在上一篇原本是使用checkbox來對_destoy這個屬性給true或false嗎?如果是true,當我們按下送出表單後,它就會被從資料庫移除,用js寫的概念也一樣,我們先在後面給他一個_destroy的隱藏欄位。

    1
    2
    <%= link_to '刪除回饋商品', '#', class:'mb-3 text-red-400', data: { action: 'click->nested-form#remove_giveback'}%>
    <%= form.hidden_field :_destroy %>
  4. 在js這邊我們先選到那個隱藏欄位並把值改成true,然後先把它隱藏起來,當按下送出資料的按鈕時,才真的把它刪除。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    remove_giveback(event) {
    event.preventDefault()

    let wrapper = event.target.closest('.nested_field')
    if (wrapper.dataset.newRecord == 'true'){
    wrapper.remove()
    }else{
    wrapper.querySelector("input[name*='_destroy']").value = 'true'
    wrapper.style.display = 'none'
    }
    }