動機
前回の記事では、ActiveAdminで、複数のレコードを関連づけるために、フォームにチェックボックスを追加していました。 その続きになります。
そこに、選択できるレコードの数が膨大になることが考えられるので、それらを全て選択できるチェックボックスを追加して欲しい、という要望がありました。
ActiveAdminのドキュメント等を確認しましたが、そういった機能は用意されていないようですので、JavaScriptで実装することにしました。(もしありましたらご指摘お願いします)
動作環境
- ruby 3.2.2
- rails 7.0.8
- activeadmin 2.14.0
実装
前回の記事のものと同じものになります。
User
の作成・更新ページに、複数のBook
を選択できるチェックボックスを追加しています。
モデル
class User < ApplicationRecord
has_many :book_reservations, dependent: :destroy
has_many :books, through: :book_reservations
end
class Book < ApplicationRecord
has_many :book_reservations, dependent: :destroy
has_many :users, through: :users
validates :name, presence: true
end
class BookReservation < ApplicationRecord
belongs_to :user
belongs_to :book
validates :user_id, uniqueness: { scope: :book_id }
end
ActiveAdmin
ActiveAdmin.register User do
actions :all
permit_params :name, :book_ids
form do |f|
inputs do
f.input :books,
as: :check_boxes,
collection: Book.pluck(:name, :id),
include_blank: false,
input_html: { multiple: true }
div class: 'check-all-books-container' do
div class: 'check-all-books-inner-container' do
check_box_tag('check_all_books', '', false, id: 'check_all_books') +
label_tag('check_all_books', 'Select all Books')
end
end
end
f.actions
end
controller do
def update
super
return if resource.invalid?
book_ids = params[:user][:book_ids].uniq.compact_blank
if book_ids.present?
book_ids.each do |book_id|
resource.book_reservations.find_or_create_by!(book_id:)
end
end
remove_reservations = resource.book_reservations.where.not(book_id: book_ids)
remove_reservations.each(&:destroy!) if remove_reservations.present?
end
def create
super
return unless build_resource.persisted?
book_ids = params[:user][:book_ids].uniq.compact_blank
return if book_ids.blank?
book_ids.each do |book_id|
build_resource.book_reservations.create!(book_id:)
end
end
end
end
今回変更した点を抜き出してみます。
全チェックボックスにチェックを入れるチェックボックスを追加します。
JavaScriptでDOM操作を行うため、各要素にクラス・IDを指定します。
div class: 'check-all-books-container' do
div class: 'check-all-books-inner-container' do
check_box_tag('check_all_books', '', false, id: 'check_all_books') +
label_tag('check_all_books', 'Select all Books')
end
end
JavaScript
そのチェックボックスで、全チェックボックス選択できるようにするため、以下のJavaScriptを追加します。
//= require active_admin/base
document.addEventListener('DOMContentLoaded', function() {
const checkAllCheckboxes = document.querySelector('#check_all_books');
if (checkAllCheckboxes) {
const checkboxes = document.querySelectorAll('input[name="user[book_ids][]"]:not([type="hidden"])');
if (Array.from(checkboxes).every(checkbox => checkbox.checked)) {
checkAllCheckboxes.checked = true
}
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
if (Array.from(checkboxes).every(checkbox => checkbox.checked)) {
checkAllCheckboxes.checked = true
}
else {
checkAllCheckboxes.checked = false
}
})
});
checkAllCheckboxes.addEventListener('change', function() {
checkboxes.forEach(function(checkbox) {
checkbox.checked = checkAllCheckboxes.checked;
});
});
}
});
まず、全て選択するチェックボックスの要素を取得します。
const checkAllCheckboxes = document.querySelector('#check_all_books');
次に、表示されている各チェックボックスの要素は、以下のようにして取得します。
実際に表示されているチェックボックスの状態判定を行うために、type="hidden"
の要素を除外して取得しています。
const checkboxes = document.querySelectorAll('input[name="user[book_ids][]"]:not([type="hidden"])');
フォームを表示した時点で、全てのチェックボックスが選択されている(更新対象のUser
に全てのBook
が関連づいている)場合には、全て選択するチェックボックスに自動的にチェックが入ります。
if (Array.from(checkboxes).every(checkbox => checkbox.checked)) {
checkAllCheckboxes.checked = true
}
各チェックボックスの状態が変わったとき、チェックボックス全てにチェックが入っているかを確認します。
全てのチェックボックスが選択されていれば、全て選択するチェックボックスに自動的にチェックが入ります。それ以外の場合は、全て選択するチェックボックスのチェックが外れます。
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
if (Array.from(checkboxes).every(checkbox => checkbox.checked)) {
checkAllCheckboxes.checked = true
}
else {
checkAllCheckboxes.checked = false
}
})
});
全て選択するチェックボックスの状態が変わると、それに応じて全てのチェックボックスの状態が一括して変更されます。
チェックを入れると、全てのチェックボックスが選択され、チェックを外すと、全てのチェックが外れます。
checkAllCheckboxes.addEventListener('change', function() {
checkboxes.forEach(function(checkbox) {
checkbox.checked = checkAllCheckboxes.checked;
});
});
SCSS
今回追加したチェックボックスには、ActiveAdminのチェックボックスのようなデザインが適用されないため、それと同様のデザインを適用します。
.check-all-books-container {
padding: 10px;
}
.check-all-books-inner-container {
padding: 0 0 0 20%;
}
input#check_all_books {
margin-right: 0.2em;
}
label[for='check_all_books'] {
float: none;
width: 100%;
font-size: 1.0em;
font-weight: bold;
color: #5E6469;
}