今まで、Google Apps Script(以後、GAS)で他のWebサービスと連携したり、OCRで画像から文字列を取得してみた。

Google Apps ScriptのHTML ServiceでGoogle Calendarの予定を取得してみる

Google Apps ScriptでJPEGの画像からOCRで画像内の文字列を取得してみた


あと、Webアプリ開発として把握しておきたいことと言えば、

ファイルのアップロード等を行うためのフォームだろう。


というわけでマニュアルを読んでみた。

HTML Service: Communicate with Server Functions  |  Apps Script  |  Google Developers Forms


コードを確認する限り、

GASのHTML Serviceでは、他の言語にあるようなPOSTの同期ではなく、

非同期で書かなければいけないらしい。


というわけで

とりあえずサンプルコードを試す。


Googleドライブ内でスクリプトエディタを開き、下記のコードをコピペして保存する。


コード.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>

コード.gsとindex.htmlの関係は下記の記事に記載してある。

Google Apps ScriptのHTML Serviceを試してみた


コードの詳細を見る前にサンプルコードを実行してみる。

ウェブアプリケーションとして導入してみると


gas_upload_form


HTMLのBodyの箇所で記述したアップロードフォームと実行ボタンが表示されている。

ファイルを選択してボタンを押してみると、


gas_upload_form1


しばし待ったところで、Got it!というリンクが表示される。

このリンクをクリックすると、


gas_upload_form2


アップロードした画像の詳細画面が表示される。

Googleドライブのマイドライブ内に画像がアップロードされるようだ。




一通り動作を確認したので、コードの確認に移る。


コード.gs内で記載した

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}

はHTML側からの非同期で送信されたファイルのデータをGoogleドライブ内にアップロードして、

アップロードした先のファイルのURLを返す関数になっているようだ。

processForm関数はコード.gsのdoGet関数内では実行されていないので、HTML側での実行になる。


続いて、


index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>

HTML側のコードを見てみる。

preventFormSubmit関数はHTML表示の際に実行されるコードなので、今回は触れないでおく。

今回注目したいのはformタグ内のsubmitイベントで指定されているhandleFormSubmit関数の方で、


function handleFormSubmit(formObject) {
  google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
}
function updateUrl(url) {
    var div = document.getElementById('output');
    div.innerHTML = '<a href="' + url + '">Got it!</a>';
}

google.script.run.withSuccessHandler(関数名).[コード.gsで作成した関数]

というコードが書かれている。


GASのHTML Serviceでコード.gs側で記述した関数の呼び出し方はいくつかあるけれども、

HTML側のscriptタグ内でgoogle.script.run.[コード.gsで作成した関数]のように記述することで呼び出すことも可能で、


コード.gsで作成した関数の実行の後に他のイベントを実行させたい場合は、

google.script.run.withSuccessHandler(関数名).[コード.gsで作成した関数]

このようにコード.gsで実行したい関数より前にチェーンメソッドの形式でwithSuccessHandler(HTML側の関数名)を指定すれば良く、

コード.gs側の関数の実行で成功した場合は、

google.script.run.withSuccessHandler(関数名).[コード.gsで作成した関数]

失敗した場合は、

google.script.run.withFailureHandler(関数名).[コード.gsで作成した関数]

で、

成功と失敗の両方を登録しておきたい場合は、

google.script.run.withSuccessHandler(関数名).withFailureHandler(関数名).[コード.gsで作成した関数]

このように繋げれば良いらしい。

JavaScriptにあるPromiseの要領で使用すれば良いはず。

Promise - JavaScript | MDN

継承とプロトタイプチェーン - JavaScript | MDN


withSuccessHandler関数の引数で指定した関数の引数、

つまりはfunction updateUrl(url)のurlはコード.gsで作成したprocessForm関数の返り値を受け取ることになっている。


今回のフォームのアップロードと、

前回のGoogleドキュメントでのOCRを組み合わせれば、

HTML Service内で画像ファイルをアップロードして、同一画面上で画像内の文字列を出力するWebアプリが作れるようになるはず。