Stimulusの習作: ドラッグ・アンド・ドロップ(クライアント側)

目的

Stimulusの習作としてリスト項目の入れ替え操作を行います。今回は高機能なライブラリを用いずに、HTML ドラッグ&ドロップ APIを用いて実現しました。尚、続けてバックエンドと通信したいと考えているため、Railsを用いて画面を生成しておきます。下記画面の赤枠内の項目が操作対象になります。

画像1

環境

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)'




この記事が気に入ったらサポートをしてみませんか?