【GAS】V8ランタイムをOFFにせず、ローカルファイルをGASでアップロードする方法

Google Apps Script

GASでローカルファイルを扱いたいときがたまにあります。
そんなとき、htmlでアップロードのフォームを用意して、スプレッドシートのUIにしたり、Webアプリケーションにしたりすると思うのですが、
検索で引っかかる記事のほとんどが、V8ランタイムをOFFにした上で、フォームの中身をそのまま送信するというものでした。
しかし、これではES6の構文も使えないですし、元々V8ランタイムをONにして開発していたものに、このような機能を追加するということになった場合、何かと不便です。

なので、この記事では、V8ランタイムをOFFにせず、ローカルファイルをGASでアップロードする方法を書いていきたいと思います。

やり方を一言で説明すると

ブラウザ側でファイルのデータをBase64エンコードしてから送信し、GAS側でデコードしてやればよいです。

具体的な方法

ファイル構成は以下の画像のようになります。

ブラウザ側のコード

以下は uploader.html のコードです

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
    #drop {
      height: 140px;
      border: #D4D4D4;
      border-style: dashed;
      border-width: 3px;
      padding: 100px;
      text-align: center;
    }
    </style>
  </head>
  <body>
    <form action="" method="post" enctype="multipart/form-data">
      <div id="container">
        <div id="drop" ondragover="onDragOver(event)" ondrop="onDrop(event)">
          <div id="chart" style="cursor: auto;">
            <p class="drag-drop-info">ここにファイルをドロップ</p>
            <p>または</p>
            <input type="file" value="ファイルを選択" name="geodataFile" onchange="selectFiles(files);" multiple>
          </div>
        </div>
      </div>
    </form>
    <?!= HtmlService.createHtmlOutputFromFile('js').getContent(); ?>
  </body>
</html>

JavaScriptの処理部分は別ファイルにして、 HtmlService.createHtmlOutputFromFile('js').getContent(); で呼び出しています。

以下は js.html のコードになります。

<script>

const reader = new FileReader();
let fileCounter = 0;
let files = [];

//読み込み後の処理
reader.onloadend = function(e){
  google.script.run
  .withFailureHandler(function(err) {
    alert("\n\n" + err.message + "\n\nファイル名:" + files[fileCounter].name);
    fileCounter++;
    postNextFile();
  }).withSuccessHandler(function() {
    fileCounter++;
    postNextFile();
  }).onUpload(reader.result, files[fileCounter].name, files[fileCounter].type);
};

function onDragOver(event) {
  event.preventDefault();
}

function onDrop(event){ 
  event.preventDefault();

  for (let i = 0, l = event.dataTransfer.files.length; i < l; i++) {
    files.push(event.dataTransfer.files[i]);
  }

  postNextFile();
}

function selectFiles(selectedFiles) {
  for (let i = 0, l = selectedFiles.length; i < l; i++) {
    files.push(selectedFiles[i]);
  }

  postNextFile();
}

function postNextFile(){
  if (fileCounter < files.length){
    document.getElementById('container').innerHTML = `ファイルのアップロード中です<br>ファイル名:${files[fileCounter].name}`;
    reader.readAsDataURL(files[fileCounter]);
  } else {
    // Webアプリの場合なら
    document.getElementById('container').innerHTML = 'アップロードが完了しました';

    // スプレッドシートとかで、アップロードUIを自動的に閉じたいのであれば、以下コメントアウト
    // google.script.host.close();

    fileCounter = 0;
  }
}
</script>

GAS側のコード

今回はWebアプリケーションとして動作させます。ので、アップロードするためのHTMLを返す doGet とアップロードされたファイルを処理するための onUpload 関数を用意しています。

function doGet(e) {
  return HtmlService.createTemplateFromFile('uploader').evaluate();
}

function onUpload(data, fileName, mimeType) {
  const blob = Utilities.newBlob(Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)), mimeType).setName(fileName);

  // あとはこのblobを煮るなり焼くなりしてください。
  // 例として、Driveの任意のフォルダにアップロードするサンプルです。
  const folderId = "your_folder_id";
  const folder = DriveApp.getFolderById(folderId);
  folder.createFile(blob);
}

ちょこっとだけ解説

ポイントは、ファイルをフォームでそのまま送信せずに、 FileReader を使っている点です。
FileReaderreadAsDataURL メソッドを使えば、Base64エンコードされたデータの文字列が手に入ります。
readAsDataURL メソッドでは、データの読み込みが完了した時点で、 onloadend イベントが発火するので、そこでGAS側のコードを叩いてやれば良いです。

mimeTypeもブラウザ側で取得していますが、対応しないファイルも多々あると思うので、その辺はGAS側のコードなりをいじくって調整する必要があります。

終わりに

ローカルファイルを扱う方法で、意外とネットに載っていない情報を書いてみました。
この方法のほうが、何かと汎用性も高いように感じます。

また、このアップローダーを使って、以下のような商品も販売したりしています。

複数CSV同時インポートツール | GASスタンド
複数CSV同時インポートツール スプレッドシート自体にCSVをインポートする機能はありますが、1ファイルずつのみとなり、そこまで高機能ではありません。 本ツールはCSVを同時に複数ファイル扱うためのUIとCSVインポート機能を提供します。 特徴 ・ 設定は何もせずに使えます ・ 複数のCSVを同時に扱うことができます ...

興味のある方は、ぜひ購入・応援のほど、よろしくお願いいたします。

タイトルとURLをコピーしました