Stimulusの習作: ドラッグ・アンド・ドロップ(クライアント側)
目的
Stimulusの習作としてリスト項目の入れ替え操作を行います。今回は高機能なライブラリを用いずに、HTML ドラッグ&ドロップ APIを用いて実現しました。尚、続けてバックエンドと通信したいと考えているため、Railsを用いて画面を生成しておきます。下記画面の赤枠内の項目が操作対象になります。
環境
macOS 10.15.4
Ruby 2.6.5
Rails 6.0.2.2
Yarn 1.22.4
Node 13.12.0
stimulus@1.1.1
リポジトリ
参照
ドラッグ&ドロップ API
2020/02/28 HTML ドラッグ&ドロップ API - Web API | MDN
2010/09/30 ネイティブ HTML5 ドラッグ&ドロップ - HTML5 Rocks
Stimulus
2019/01/07 Stimulus- A modest JavaScript framework for the HTML you already have.
2018/03/09 Stimulus.js Tutorial- How Do I Drag and Drop Items in a List? • Blogging On Rails
Rails
2011/10/13 147 Sortable Lists (revised) - RailsCasts
2019/10/18 rails / jquery-ujs wiki ajax
HTML
下記の内容で操作対象のリストを生成します。
data-controllerで、項目を操作するStimulusのコントローラと接続する。
data-actionで、ドラッグ&ドロップ APIイベントをコントローラに渡す。
draggable="true"で、要素をドラッグ&ドロップ可能にする。
data-book-idで、項目を識別可能にする。
出力例は次のようになります。
<ul
data-controller="drag-item"
data-action="dragstart->drag-item#dragstart
dragover->drag-item#dragover
drop->drag-item#drop
dragend->drag-item#dragend">
<li draggable="true" data-book-id="1">1 Ring of Bright Water</li>
<li draggable="true" data-book-id="2">2 The Little Foxes</li>
<li draggable="true" data-book-id="3">3 Things Fall Apart</li>
<li draggable="true" data-book-id="4">4 Waiting for the Barbarians</li>
<li draggable="true" data-book-id="5">5 The Curious Incident of the Dog in the Night-Time</li>
</ul>
このHTMLを生成します。
app/helpers/books_helper.rb
module BooksHelper
def propagate_events(controller, actions)
actions.inject('') do |result, action|
result << " #{action}->#{controller}##{action}"
end.lstrip
end
end
app/views/books/index.html.erb
<% controller = "drag-item" %>
<% actions = %w(dragstart dragover drop dragend) %>
<% action = propagate_events(controller, actions) %>
<%= tag.ul data: { controller: controller, action: action } do %>
<% @books.each do |book| %>
<%= tag.li draggable: true, data: { book_id: book.id } do %>
<%= book.title %>
<% end %>
<% end %>
<% end %>
コントローラ
コントローラは下記のイベントに対応します。
dragstart
dragover
drop
app/javascript/controllers/drag_item_controller.js
dragstartイベント
Book IDをdataTransferに設定する。
moveでイベントを発生させる。
import { Controller } from 'stimulus'
export default class extends Controller {
get dragKey() {
return 'application/drag-key'
}
dragstart(event) {
const bookId = event.target.getAttribute('data-book-id')
event.dataTransfer.setData(this.dragKey, bookId)
event.dataTransfer.effectAllowed = 'move'
}
dragoverイベント
ブラウザのデフォルトの動作を止めてdropイベントを発生させる。
dragover(event) {
event.preventDefault()
}
dropイベント
ドラッグした要素(src)とドロップ先(dst)のBook IDを取得する。
srcとdstの相対位置を比較する。
srcがdstの前の位置(リストの上)なら、dstの後ろにsrcを挿入(移動)する。
逆にsrcがdstの後ろの位置なら、dstの前にsrcを挿入(移動)する。
drop(event) {
const srcId = event.dataTransfer.getData(this.dragKey)
const src = this.element.querySelector(`[data-book-id='${srcId}']`);
const dst = event.target
const pos = dst.compareDocumentPosition(src)
if (pos & src.DOCUMENT_POSITION_PRECEDING) {
event.target.insertAdjacentElement('afterend', src);
} else if (pos & src.DOCUMENT_POSITION_FOLLOWING) {
event.target.insertAdjacentElement('beforebegin', src);
}
event.preventDefault()
}
以上です。
付録
アプリの新規作成
rails new stimulus_tutorial_drag_and_drop --webpack=stimulus
cd stimulus_tutorial_drag_and_drop
cat app/javascript/controllers/index.js
git add .
git commit -m 'rails new APP_NAME --webpack=stimulus'
足場の生成
bin/rails g scaffold book title row_order:integer
bin/rails db:migrate
git add .
git commit -m 'bin/rails g scaffold book title row_order:integer'
バリデーションの追加
app/models/book.rb
validates :title, :row_order, uniqueness: true
git add .
git commit -m 'validates :title, :row_order, uniqueness: true'
ダミーデータの生成
Gemfile
gem 'faker'
bundle install
db/seeds.rb
5.times do |i|
Book.create!(title: Faker::Book.title, row_order: i)
end
bin/rails db:seed
git add .
git commit -m 'Book.create!(title: Faker::Book.title, row_order: i)'