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
を使っている点です。
FileReader
の readAsDataURL
メソッドを使えば、Base64エンコードされたデータの文字列が手に入ります。
readAsDataURL
メソッドでは、データの読み込みが完了した時点で、 onloadend
イベントが発火するので、そこでGAS側のコードを叩いてやれば良いです。
mimeTypeもブラウザ側で取得していますが、対応しないファイルも多々あると思うので、その辺はGAS側のコードなりをいじくって調整する必要があります。
終わりに
ローカルファイルを扱う方法で、意外とネットに載っていない情報を書いてみました。
この方法のほうが、何かと汎用性も高いように感じます。
また、このアップローダーを使って、以下のような商品も販売したりしています。
興味のある方は、ぜひ購入・応援のほど、よろしくお願いいたします。