スマートな操作性と、モダンなUIを提供してくれる「Choices」をRailsアプリに組み込む方法です。
今回のサンプルでは「タスクに複数のメンバーをアサインする」部分を例に解説してみたいと思います。
動作イメージ
環境とgem
- ruby 2.7.1
- rails 6.0.3
- webpackerは有効
- turbolinksは無効
- hamlit-rails
- simple_form
手順
- モデルの作成
- Choices.jsのセットアップ
- select要素とChoicesの紐付け
- おまけ:RSpecでシステムテスト
- サンプルレポジトリ
- 追記:2020/09時点のメンテナンス状況
1. モデルの作成
> rails g scaffold task title content
> rails g scaffold member name
> rails g model assignment task:references member:references
> rails db:migrate
class Task < ApplicationRecord
has_many :assignments, dependent: :destroy
has_many :members, through: :assignments
end
class Member < ApplicationRecord
has_many :assignments, dependent: :destroy
has_many :tasks, through: :assignments
end
class Assignment < ApplicationRecord
belongs_to :member
belongs_to :task
end
2. Choices.jsのセットアップ
> yarn add choices.js
3. select要素とChoicesの紐付け
js側の設定
import * as Choices from 'choices.js'
document.addEventListener('DOMContentLoaded', function () {
new Choices('#choices-multiple-remove-button', {
delimiter: ',',
maxItemCount: 5,
removeItemButton: true,
noChoicesText: '選択されていません',
itemSelectText: '選択してください',
maxItemText: (maxItemCount) => {
return `タスクにアサインできるのは ${maxItemCount} 人までです`;
},
});
});
js側の設定(スタイルシート)
@import '~choices.js/src/styles/choices'
ファイルのimport
ふたつのファイルをimportしておきます。
import './choices/init.js';
import '../src/choices.scss'
view側の設定
= simple_form_for @task do |f|
= f.error_notification
= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?
.field
= f.input :title, id: 'task_title'
.field
= f.input :content, as: :text
.field
= f.association :members,
required: true,
as: :select,
collection: Member.pluck(:name, :id),
input_html: { id:'choices-multiple-remove-button', multiple: true }
.actions
= f.button :submit
Choices.jsがマッピングするために、idをあわせておきます。
init.js
内のnew Choices('#choices-multiple-remove-button
で指定したidを設定します。複数選択なのでmultiple: true
もわすれずに。
= f.association :members,
required: true,
as: :select,
collection: Member.pluck(:name, :id),
input_html: { id:'choices-multiple-remove-button', multiple: true }
選択肢の部分は、collection: Member.pluck(:name, :id)
とすれば、あとはChoicesが面倒みてくれます。「編集時はchoices.setValue
して初期値を設定」なんてこともしなくてよいです。
selected
な項目が選ばれた状態になってくれます。
<select multiple="multiple" class="select required" required="required" aria-required="true" name="task[member_ids][]"
id="task_member_ids">
<option value="1">Alice</option>
<option selected="selected" value="2">Bob</option>
<option selected="selected" value="3">Charlie</option>
</select>
TasksControllerのStrongParameterを修正
選択した項目の値を配列で受け取れるように、以下のように修正します。
def task_params
params.require(:task).permit(:title, :content, member_ids: [])
end
変更はこれだけで完了です👍
メッセージの日本語化や、表示をカスタマイズできるオプションもありますので、色々できそうですね。
jshjohnson/Choices: A vanilla JS customisable select box/text input plugin ⚡️
4. おまけ:RSpecでシステムテスト
require 'rails_helper'
RSpec.describe 'Tasks', type: :system do
let!(:board) { create(:board) }
let!(:member) { create(:member, name: 'チョイ・スー') }
it 'タスクを追加できる', js: true do
visit new_board_task_path(board)
fill_in 'Title', with: 'Qiita記事を書く'
fill_in 'Content', with: 'Choices.jsの使い方を紹介します'
# リストを展開
first('.choices').click
# リストアイテムを選択
first('.choices__item').click
# リストを閉じるためEsc送信
find('#task_title').send_keys :escape
expect do
click_on 'Create Task'
end.to change { Task.count }.by(1)
end
end
※Rspecや、FactoryBotをつかっていますが詳細は割愛します。動かなかった場合はコメントいただければお答えできるかも。
5. サンプルレポジトリ
hirocueki/kanban-for-choices-js
6. 追記:2020/09時点のメンテナンス状況
公式レポジトリに、以下の文面がありました。
要約すると、「このライブラリをサポートする時間がなくてメンテナーを探しているので連絡ください」ということです。利用の際には、この点も含めて考慮してください