JavaScript
dom
drag&drop

[JavaScript] ファイルのドロップイベントをJavaScriptから発火させる

問題

ファイルをドロップしたときに発火するイベントを、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);
}

参考リンク

Trigger 'drop' event while providing the file data