問題
ファイルをドロップしたときに発火するイベントを、JavaScriptから発火させたい。
以下のような使い方ができる関数fireDropEvent
を作れればよいとしよう。
fireDropEvent(document.documentElement, [new File(["Hello."], "hello.txt")]);
各種APIのドキュメントを読んですぐに思いつくのは以下のような実装だろう。
function fireDropEvent(domElement, files) {
const dataTransfer = new DataTransfer();
// (ここに、dataTransfer に files を登録する何らかのコードを入れる)
domElement.dispatchEvent(new DragEvent("drop", {dataTransfer}));
}
しかし、現状DataTransfer
にファイルを登録するAPIは存在しないので、上のような方式の実装は不可能である。
どうすればよいか。
対処法
ダックタイピングを使う。
function fireDropEvent(domElement, files) {
// DataTransferの偽物を定義する
class FakeDataTransfer {
constructor(files) {
this.dropEffect = "none";
this.effectAllowed = "all";
this.files = files;
this.types = ["Files"];
}
addElement() {
// do nothing
}
clearData() {
// do nothing
}
getData() {
return "";
}
setData() {
// do nothing
}
setDragImage() {
// do nothing
}
}
// あとはFakeDataTransferを本物のDataTransferといかにして差し替えるかが問題だが、
// 以下の実装は怒られてしまう
//
// ダメな実装例1:
// const event = new DragEvent("drop", {dataTransfer: new FakeDataTransfer(files)});
// DragEventのコンストラクタはDataTransferのインスタンスしか受け付けないので不可
//
// ダメな実装例2:
// const event = new DragEvent("drop");
// event.dataTransfer = new FakeDataTransfer(files);
// dataTransferへの代入は不可
// なので、ECMAScript 5のdefinePropertyという機能を使って上書きする
const event = new DragEvent("drop");
Object.defineProperty(event, "dataTransfer", {value: new FakeDataTransfer(files)});
domElement.dispatchEvent(event);
}