📮

File APIを使用して画面内でファイルデータを保持する

2023/10/09に公開

目次

  1. 概要
  2. File APIについて
  3. 実装
  4. まとめ

概要

今回はWeb APIの1つであるFile APIを使用して画面内にファイルデータを保持したいと思います。

inputタグはファイルを選択した状態で再度ファイルを選択すると、その前のファイルは選択が保持されずクリアされてしまいます。
しかし、ファイルデータを保持したままファイルの選択を行いたいことがあると思います。

File APIについて

File APIはMDNのドキュメントにある通り、WebアプリケーションがファイルにアクセスするためのAPIとなります。
https://developer.mozilla.org/ja/docs/Web/API/File_API

今回フォーカスするのははFileオブジェクトとFileListオブジェクトになります。
inputタグから選択したファイルはFileListオブジェクトにFileオブジェクトとして複数格納されています。

これらを使用し、inputタグから読み込んだファイルを保持し、ファイル名を一覧で表示する画面を作成します。
また、ボタンを押すとファイルのダウンロードのリンクを表示する処理も作成します。

実装

HTMLにてファイルを読み込むinputタグとダウンロードするためのリンクを表示するボタンを配置します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div>
      <input type="file" id="file" name="file" multiple />
      <div id="fileList"></div>
    </div>
    <div>
      <button type="button" id="download">
        ダウンロードファイルを表示する
      </button>
      <div id="downloadFileList"></div>
    </div>
    <script type="module" src="/src/fileAPI.ts"></script>
  </body>
</html>

HTML内で操作する要素を指定します。

const inputFileElement = () =>
  document.querySelector<HTMLInputElement>("#file");
const buttonDownloadElement = () =>
  document.querySelector<HTMLButtonElement>("#download");
const divFileListElement = () =>
  document.querySelector<HTMLDivElement>("#fileList");
const divDownloadFileListElement = () =>
  document.querySelector<HTMLDivElement>("#downloadFileList");

保持するファイルを格納する状態の宣言を行います。

let files: File[] = [];

次のコードはinputタグから読み込んだファイルを保持し、ファイル名を一覧表示するコードになります。

const selectFileEvent = (event: Event): void => {
  const target = event.target as HTMLInputElement;
  const fileList = target.files;

  if (fileList === null) return;

  for (let i = 0; i < fileList.length; i++) {
    const file = fileList.item(i);

    if (file === null) continue;

    let li = document.createElement("li");
    li.textContent = file.name;
    divFileListElement()?.appendChild(li);

    files = [...files, file];
  }
};

inputFileElement()?.addEventListener("change", selectFileEvent);

FileListオブジェクトは配列ではないため、FileListオブジェクトのプロパティであるlengthからinputタグで選択したファイルの数を取得し、for文を使用します。
個々のファイルデータはFileListオブジェクトのitem()メソッドでインデックスを指定することで取得することが出来ます。

次のコードは、保持したファイルのダウンロードリンクを表示するコードになります。

const showDownloadURLEvent = (): void => {
  divDownloadFileListElement()
    ?.querySelectorAll("li")
    ?.forEach((elem) => elem.remove());

  files.map((file) => {
    let anchor = document.createElement("a");
    anchor.href = URL.createObjectURL(file);
    anchor.download = file.name;
    anchor.textContent = file.name;

    let li = document.createElement("li");
    li.appendChild(anchor);

    divDownloadFileListElement()?.appendChild(li);
  });
};

buttonDownloadElement()?.addEventListener("click", showDownloadURLEvent);

ダウンロードのリンクはURL.createObjectURL()メソッドを使用して作成しています。
また、ボタンを押すたびにリンクとなる要素を全て削除して再度表示しています。

まとめ

今回はFile APIを使用して画面内でファイルのデータを一時的に保持する処理を実装しました。
File APIを使用することでinputタグのみの場合よりもファイル選択の自由度が増したと思います。

今回は紹介しませんでしたが、ここで保持したデータはリクエストデータとしても使用出来ます。

https://developer.mozilla.org/ja/docs/Web/API/File_API

今回作成したtypescriptのコードの全体は以下になります。

fileAPI.ts
// HTML内で操作する要素の指定
const inputFileElement = () =>
  document.querySelector<HTMLInputElement>("#file");
const buttonDownloadElement = () =>
  document.querySelector<HTMLButtonElement>("#download");
const divFileListElement = () =>
  document.querySelector<HTMLDivElement>("#fileList");
const divDownloadFileListElement = () =>
  document.querySelector<HTMLDivElement>("#downloadFileList");

// 状態の宣言
let files: File[] = [];

// ファイルを保持してファイル名を表示する関数
const selectFileEvent = (event: Event): void => {
  const target = event.target as HTMLInputElement;
  const fileList = target.files; // FileList

  if (fileList === null) return;

  for (let i = 0; i < fileList.length; i++) {
    const file = fileList.item(i);

    if (file === null) continue;

    let li = document.createElement("li");
    li.textContent = file.name;
    divFileListElement()?.appendChild(li);

    files = [...files, file];
  }
};

// 保持したファイルのダウンロードリンクを表示する関数
const showDownloadURLEvent = (): void => {
  divDownloadFileListElement()
    ?.querySelectorAll("li")
    ?.forEach((elem) => elem.remove());

  files.map((file) => {
    let anchor = document.createElement("a");
    anchor.href = URL.createObjectURL(file);
    anchor.download = file.name;
    anchor.textContent = file.name;

    let li = document.createElement("li");
    li.appendChild(anchor);

    divDownloadFileListElement()?.appendChild(li);
  });
};

// イベントリスナーの設定
inputFileElement()?.addEventListener("change", selectFileEvent);
buttonDownloadElement()?.addEventListener("click", showDownloadURLEvent);

Discussion