🚖

JavaScript Tips - アップロードしたローカルファイルの中身を読み込んで表示する方法

2021/01/25に公開

はじめに

今回は, 昨日のエントリー

https://zenn.dev/phi/articles/javascript-donwload-file

とは逆に, アップロードしたローカルのファイルを読み込んで表示する方法をサンプルを交えて紹介します.

読み込み自体は簡単なんですが, 標準の FileReader だとちょっとレガシーな書き方になったしまうのでラップして Promise オブジェクトを返すようにしてみました.

こうすることで使う側は async/await でスッキリ書くことができます.
よかったら参考にしてください.

サンプルプログラム

ローカルにあるテキストファイルをアップロードすると, 中身を読み込んで内容をテキストエリアに反映します.

https://runstant.com/phi/projects/e7166f34

コード

index.html
<body>
  <h1>${title}</h1>
  <p>${description}</p>
  
  <input id='$inputFile' type='file' />
  <div>
    <textarea id='$input' rows='8' cols='64'>hoge foo bar</textarea>
  </div>
  <a id='$btn' class='btn' download='sample.txt'>入力した内容を保存</a>

  <script>${script}</script>
</body>
main.js
window.onload = function() {
  // ダウンロードボタンを押した際のイベントを登録
  $btn.onclick = () => {
    // blob オブジェクトを生成
    var content = $input.value;
    var blob = new Blob([content], { type: 'text/plain' });
    
    // download の href に object url を設定
    $btn.href = window.URL.createObjectURL(blob);
  };
  
  $inputFile.onchange = async (e) => {
    // ファイルオブジェクトを取得
    var file = e.currentTarget.files[0];
    if (!file) return ;
    
    // 中身を取得
    var text = await fetchAsText(file);
    
    // テキストエリアに代入
    $input.value = text;
  };
};

// ファイルから内容をテキストとして取得する Promise を返す
var fetchAsText = (file) => {
  return new Promise((resolve) => {
    var fr = new FileReader();
    fr.onload = (e) => {
      // 読み込んだ結果を resolve する
      resolve(e.currentTarget.result);
    };
    // 読み込む
    fr.readAsText(file);
  });
};

解説

ファイルアップロード用の input 要素を用意しよう

type 属性に file を指定した input 要素を用意しましょう.

<input id='$inputFile' type='file' />

これでクリックするとファイル選択モーダルが出るようになります!

ファイルの中身を読み込んで返す関数を定義しよう

ファイル読み込みを普通に書いてしまうとネストが深くなってしまうので, 使う側のコードがスッキリするよう関数化して Promise オブジェクトを返すようにしましょう.

やってることはシンプルで, File オブジェクトを受け取ったらその内容を FileReader 経由で読み込んで, 読み込んだ内容を resolve するようにしています.

// ファイルから内容をテキストとして取得する Promise を返す
var fetchAsText = (file) => {
  return new Promise((resolve) => {
    var fr = new FileReader();
    fr.onload = (e) => {
      // 読み込んだ結果を resolve する
      resolve(e.currentTarget.result);
    };
    // 読み込む
    fr.readAsText(file);
  });
};

これで使う側は

var text = await fetchAsText(file);

という感じでスッキリ使うことができるようになります.

ファイルが選択されたら内容を読み込んでテキストエリアに反映しよう

最後に, ファイルを選んだときに先程定義した fetchAsText() 関数を実行するようにしましょう.

ファイルを選択すると inputchange イベントが発火するので, その中で選択した File オブジェクトを fetchAsText() に渡して実行し, 結果の text をテキストエリアの value に代入しています.

$inputFile.onchange = async (e) => {
  // ファイルオブジェクトを取得
  var file = e.currentTarget.files[0];
  if (!file) return ;
  
  // 中身を取得
  var text = await fetchAsText(file);
  
  // テキストエリアに代入
  $input.value = text;
};

これで, ファイルを選択した際, 中身をテキストエリアに反映するようになります.

おわりに

昔の仕様で作られた機能は Promise 対応していないので結構回りくどい書き方をしないといけなかったりするんですよね💦

Web Audio の decodeAudioData とかは, 最近 Promise 対応してくれてだいぶスッキリ変えるようになったりしてるので, FileReader もいずれは Promise 対応してくれることを期待しています.

それまでは, 一通りラップしたライブラリを作って凌ぐのもありかもですね!

今回のエントリーも, 昨日のエントリー 同様, 管理ツールなんかで csv ファイルを読み込んで反映する際なんかによく使う tips です.

出力と読み込みができればわりとライトなツールをサーバーレスで作れたりもするのでぜひ活用して遊んでみてください.

Discussion