今まで、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を試してみた
コードの詳細を見る前にサンプルコードを実行してみる。
ウェブアプリケーションとして導入してみると

HTMLのBodyの箇所で記述したアップロードフォームと実行ボタンが表示されている。
ファイルを選択してボタンを押してみると、

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

アップロードした画像の詳細画面が表示される。
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の要領で使用すれば良いはず。
継承とプロトタイプチェーン - JavaScript | MDN
withSuccessHandler関数の引数で指定した関数の引数、
つまりはfunction updateUrl(url)のurlはコード.gsで作成したprocessForm関数の返り値を受け取ることになっている。
今回のフォームのアップロードと、
前回のGoogleドキュメントでのOCRを組み合わせれば、
HTML Service内で画像ファイルをアップロードして、同一画面上で画像内の文字列を出力するWebアプリが作れるようになるはず。





