Link Search Menu Expand Document

14. 批次編輯 (Bulk Editing)

14-1 需求說明

在後台編輯資料時,如果資料很多,有沒有什麽 UI 可以一次修改或刪除多筆呢? 這一章將實作透過核選方塊來做批次刪除和修改。

image

勾選要修改哪些活動,按下最下麵的按鈕就會批次修改。

14-2 批次刪除

先來做批次刪除,首先需要修改 config/routes.rb,加上一個新的路由來做批次動作:

    resources :events do
      resources :tickets, :controller => "event_tickets"

+      collection do
+        post :bulk_update
+      end
    end

修改 app/views/admin/events/index.html.erb,用 form_tag 包住整個 table,並加上核選方塊和批次刪除的按鈕:

+  <%= form_tag bulk_update_admin_events_path, :class => "form-inline" do %>
   <table class="table">
   <tr>
+    <th><%= check_box_tag "全選", "1", false, :id => "toggle_all" %></th>

   # 略

   <% @events.each do |event| %>
    <tr>
+     <td>
+       <%= check_box_tag "ids[]", event.id %>
+     </td>

   # 略

   </table>

+    <p><%= submit_tag "批次刪除", :class => "btn btn-danger", :data => { :confirm => "Are you sure?" } %></p>
+  <% end %>

+  <script>
+    // 這個 javascript 會綁事件在 #toggle_all 核選方塊上,來做全選效果
+    $("#toggle_all").click(function(){
+      if ( $(this).prop("checked") ) {
+        $("input[name='ids[]']").prop("checked", true);
+      } else {
+        $("input[name='ids[]']").prop("checked", false);
+      }
+    })
+  </script>

修改 app/controllers/admin/events_controller.rb,循環 params[:ids] 陣列找出每個 event 然後刪除。

+  def bulk_update
+    total = 0
+    Array(params[:ids]).each do |event_id|
+      event = Event.find(event_id)
+      event.destroy
+      total += 1
+    end
+
+    flash[:alert] = "成功完成 #{total} 筆"
+    redirect_to admin_events_path
+  end

image

這樣就可以批次刪除了。

Pro Tip 小技巧:關於 Array(params[:ids] 這個用法,如果是 Array([1,2,3]) 會等同於 [1,2,3] 沒變,但是 Array[nil] 會變成 [] 空陣列,這可以讓 .each 方法不會因為 nil.each 而爆錯。如果不這樣處理,在沒有勾選任何活動就送出的情況,就會爆出 NoMethodError 錯誤。除非你額外檢查 params[:id] 如果是 nil 就返回,但不如用 Array 來的精巧。

注意本來的 admin layout 中的 flash 樣式有點問題,請修改 app/views/layouts/admin.html.erb 修正一下:

-    <div class="container">
+    <div class="container" style="padding-top: 60px">

-      <p class="notice"><%= notice %></p>
-      <p class="alert"><%= alert %></p>

+      <% if notice %>
+        <p class="notice alert-success"><%= notice %></p>
+      <% end %>

+      <% if alert %>
+        <p class="alert alert-danger"><%= alert %></p>
+      <% end %>

14-3 批次修改

接下來實作批次修改狀態。注意到我們已經包了一個 formtable 外面了:

  • 一個 form 的送出 URL 位置是固定的,會進到同一個 action。這裡會 POST 送出到 bulk_update_admin_events_path
  • form 裡面不能再包 form

那我們要怎麽區別到底是要刪除還是修改?所幸,按鈕的文字也會送出變成參數。請修改 app/views/admin/events/index.html.erb,加上批次修改的按鈕,以及狀態的下拉選單:

   </table>

-   <p><%= submit_tag "批次刪除", :class => "btn btn-danger", :data => { :confirm => "Are you sure?" } %></p>

+   <p>
+   <%= select_tag :event_status, options_for_select( Event::STATUS.map{ |s| [t(s, :scope => + "event.status"), s] }), :class => "form-control" %>
+
+   <%= submit_tag t(:bulk_update), :class => "btn btn-primary" %>
+   <%= submit_tag t(:bulk_delete), :class => "btn btn-danger", :data => { :confirm => "Are you + sure?" } %>
+   </p>

  <% end %>

修改 config/locales/zh-CN.yml

   "zh-CN":
+    bulk_update: 批次編輯
+    bulk_delete: 批次刪除

如果要做英文版,請再編輯 en.yml 加上英文翻譯即可

如果觀察一下 HTML 源碼,可以看到按鈕也是有參數的:

image

接著修改 app/controllers/admin/events_controller.rb,根據 params[:commit] 參數,就可以判斷用戶當初是按下刪除還是修改了:


     Array(params[:ids]).each do |event_id|
       event = Event.find(event_id)
-      event.destroy
-      total += 1
+
+      if params[:commit] == I18n.t(:bulk_update)
+        event.status = params[:event_status]
+        if event.save
+          total += 1
+        end
+      elsif params[:commit] == I18n.t(:bulk_delete)
+        event.destroy
+        total += 1
+      end
     end

image

Pro Tip 小技巧:這裡我改用了 I18n 來顯示按鈕字串,這倒不是因為一定要支援多語言,而是因為文案可能會改。如果你按鈕寫死 <%= submit_tag '批次修改' 的話,那麽在 controller 中也需要寫成 if params[:commit] == '批次修改'。將來那一天要改字,就要記得兩個地方都要改到。但是如果用 I18n 來處理,就之後只要記得改翻譯檔一個地方就好了。這個原則就叫做 DRY: Don’t repeat yourself,這是一個寫好程序的基本原則。


Copyright © 2010-2021 Wen-Tien Chang All Rights Reserved.