Rails で input[type=file] の入力欄にファイルをドラッグ&ドロップして選択する機能を追加しようとネットを調べてみたのですが、script 要素にべた書きの実装例しか見つからなかったので、Stimulus を使った実装をしてみました。
環境
- Ruby 3.1.1
- Ruby on Rails 7.0.2
View
既存のページには、data-controller 属性と data-action 属性と data-*-target 属性を追加するだけなので、ほとんど手を入れずに済みます。
Stimulus 側に file_drop という名前のコントローラを作るので、data-*-target 属性の値や名前もそれに合わせます。
...
<div data-controller="file-drop"
data-action="dragover->file-drop#dragover dragleave->file-drop#dragleave drop->file-drop#drop">
<%= form.label :image, style: "display: block" %>
<%= form.file_field :image, 'data-file-drop-target': 'fileUpload' %>
</div>
...
Controller
file_drop_controller.js 中の targets
で指定した値(fileUpload
)から自動的に fileUploadTarget
というプロパティが生成されます。
これは自動で生成されるため、targets の値に file-upload
のようなケバブケースは使えません。
'data-file-drop-target': 'file-upload'
って書きたかったのですが諦めました。
data-action 属性でイベントとそれに対応するコントローラ#メソッドを指定したものが、ここで呼ばれます。
script 要素にべた書きだと element.addEventListener('dragover', () => {})
としていたところを、ちょっとだけわかりやすく書けるようになります。
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ['fileUpload']
dragover(e) {
e.preventDefault()
// dragover したときに border の色を変える
this.fileUploadTarget.classList.add('border-primary')
}
dragleave(e) {
e.preventDefault()
this.fileUploadTarget.classList.remove('border-primary')
}
drop(e) {
e.preventDefault()
this.fileUploadTarget.classList.remove('border-primary')
const files = e.dataTransfer.files
if (typeof files[0] !== 'undefined') {
this.fileUploadTarget.files = files
}
}
}
あとは作成したコントローラを読み込むだけ。
import { application } from './application'
import FileDropController from './file_drop_controller'
application.register('file-drop', FileDropController)