HTML サービス: サーバー機能と通信する

google.script.run は、HTML サービスページからサーバーサイドの Apps Script 関数を呼び出すことができる非同期のクライアントサイド JavaScript API です。次の例は、google.script.run の最も基本的な機能(クライアントサイドの JavaScript からサーバー上の関数を呼び出す)を示しています。

コード.gs

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

function doSomething() {
  Logger.log('I was called!');
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      google.script.run.doSomething();
    </script>
  </head>
</html>

このスクリプトをウェブアプリとしてデプロイして URL にアクセスしても、何も表示されませんが、ログを表示すると、サーバー関数 doSomething() が呼び出されたことがわかります。

サーバーサイド関数へのクライアントサイドの呼び出しは非同期です。ブラウザがサーバーに doSomething() 関数の実行をリクエストすると、ブラウザはレスポンスを待たずに次のコード行にすぐに進みます。つまり、サーバー関数呼び出しが想定どおりの順序で実行されない可能性があります。2 つの関数呼び出しを同時に行う場合、どちらの関数が先に実行されるかはわかりません。ページを読み込むたびに結果が異なる可能性があります。このような状況では、成功ハンドラ失敗ハンドラがコードのフローを制御するのに役立ちます。

google.script.run API では、サーバー関数への同時呼び出しが 10 回まで許可されます。10 個のスポットがまだ実行中の状態で 11 回目の呼び出しを行うと、10 個のスポットのいずれかが解放されるまで、サーバー関数は遅延します。実際には、この制限を考慮する必要はほとんどありません。特に、ほとんどのブラウザでは、同じサーバーに対する同時リクエストの数が 10 未満に制限されています。たとえば、Firefox では 6 個が上限です。ほとんどのブラウザは、既存のリクエストのいずれかが完了するまで、過剰なサーバー リクエストを同様に遅延させます。

パラメータと戻り値

クライアントからパラメータを使用してサーバー関数を呼び出すことができます。同様に、サーバー関数は、成功ハンドラに渡されるパラメータとして、クライアントに値を返すことができます。

有効なパラメータと戻り値は、NumberBooleanStringnull などの JavaScript プリミティブと、プリミティブ、オブジェクト、配列で構成される JavaScript オブジェクトと配列です。ページ内の form 要素もパラメータとして有効ですが、関数の一意のパラメータである必要があります。また、戻り値としては有効ではありません。form 以外の DateFunction、DOM 要素、または禁止されている型(オブジェクトや配列内の禁止されている型を含む)を渡そうとすると、リクエストは失敗します。循環参照を作成するオブジェクトも失敗し、配列内の未定義のフィールドは null になります。

サーバーに渡されたオブジェクトは元のコピーになります。サーバー関数がオブジェクトを受け取り、そのプロパティを変更しても、クライアントのプロパティは影響を受けません。

成功ハンドラ

クライアントサイドのコードはサーバー呼び出しの完了を待たずに次の行に進むため、withSuccessHandler(function) を使用すると、サーバーが応答したときに実行するクライアントサイドのコールバック関数を指定できます。サーバー関数が値を返すと、API はその値をパラメータとして新しい関数に渡します。

次の例では、サーバーが応答したときにブラウザ アラートを表示します。このコードサンプルでは、サーバーサイド関数が Gmail アカウントにアクセスするため、承認が必要です。スクリプトを承認する最も簡単な方法は、ページを読み込む前にスクリプト エディタから getUnreadEmails() 関数を手動で 1 回実行することです。また、ウェブアプリをデプロイする際に、[ユーザーがウェブアプリにアクセスする] として実行することもできます。この場合、アプリを読み込むときに認証を求めるメッセージが表示されます。

コード.gs

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

function getUnreadEmails() {
  return GmailApp.getInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(numUnread) {
        var div = document.getElementById('output');
        div.innerHTML = 'You have ' + numUnread
            + ' unread messages in your Gmail inbox.';
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

障害ハンドラ

サーバーが応答に失敗した場合やエラーをスローした場合、withFailureHandler(function) を使用すると、成功ハンドラの代わりに失敗ハンドラを指定できます。このとき、Error オブジェクト(存在する場合)が引数として渡されます。

デフォルトでは、失敗ハンドラを指定しない場合、失敗は JavaScript コンソールに記録されます。この動作をオーバーライドするには、withFailureHandler(null) を呼び出すか、何も行わない失敗ハンドラを指定します。

次の例に示すように、失敗ハンドラの構文は成功ハンドラとほぼ同じです。

コード.gs

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

function getUnreadEmails() {
  // 'got' instead of 'get' will throw an error.
  return GmailApp.gotInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onFailure(error) {
        var div = document.getElementById('output');
        div.innerHTML = "ERROR: " + error.message;
      }

      google.script.run.withFailureHandler(onFailure)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

User オブジェクト

withUserObject(object) を呼び出して、ハンドラに 2 番目のパラメータとして渡されるオブジェクトを指定することで、サーバーへの複数の呼び出しに同じ成功または失敗ハンドラを再利用できます。この「ユーザー オブジェクト」(User クラスと混同しないようにしてください)を使用すると、クライアントがサーバーに接続したコンテキストに応答できます。ユーザー オブジェクトはサーバーに送信されないため、サーバー呼び出しのパラメータと戻り値の制限なしに、関数や DOM 要素など、ほぼすべてのものにできます。ただし、ユーザー オブジェクトは new 演算子で構築されたオブジェクトにすることはできません。

この例では、2 つのボタンのいずれかをクリックすると、1 つの成功ハンドラを共有しているにもかかわらず、他のボタンは変更されずに、そのボタンがサーバーからの値で更新されます。onclick ハンドラ内では、キーワード thisbutton 自体を指します。

コード.gs

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

function getEmail() {
  return Session.getActiveUser().getEmail();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function updateButton(email, button) {
        button.value = 'Clicked by ' + email;
      }
    </script>
  </head>
  <body>
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
  </body>
</html>

フォーム

form 要素をパラメータとしてサーバー関数を呼び出すと、フォームはフィールド名をキー、フィールド値を値とする単一のオブジェクトになります。値はすべて文字列に変換されます。ただし、ファイル入力フィールドの内容は Blob オブジェクトになります。

この例では、ファイル入力フィールドを含むフォームをページを再読み込みせずに処理します。ファイルを Google ドライブにアップロードし、クライアントサイドのページにファイルの URL を出力します。onsubmit ハンドラ内では、キーワード this はフォーム自体を指します。ページ内のすべてのフォームは、読み込み時にデフォルトの送信アクションが preventFormSubmit によって無効になっていることに注意してください。これにより、例外が発生した場合にページが不正確な URL にリダイレクトされるのを防ぐことができます。

コード.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>

スクリプト ランナー

google.script.run は「スクリプト ランナー」のビルダーと考えることができます。スクリプト ランナーに成功ハンドラ、失敗ハンドラ、ユーザー オブジェクトを追加しても、既存のランナーは変更されません。代わりに、新しい動作を備えた新しいスクリプト ランナーが返されます。

withSuccessHandler()withFailureHandler()withUserObject() は、任意の組み合わせと順序で使用できます。値がすでに設定されているスクリプト ランナーで、変更関数を呼び出すこともできます。新しい値は、以前の値を上書きするだけです。

この例では、3 つのサーバー呼び出しすべてに共通の失敗ハンドラを設定していますが、成功ハンドラは 2 つ別々に設定しています。

var myRunner = google.script.run.withFailureHandler(onFailure);
var myRunner1 = myRunner.withSuccessHandler(onSuccess);
var myRunner2 = myRunner.withSuccessHandler(onDifferentSuccess);

myRunner1.doSomething();
myRunner1.doSomethingElse();
myRunner2.doSomething();

プライベート関数

名前がアンダースコアで終わるサーバー関数は、プライベートと見なされます。これらの関数は google.script から呼び出すことはできません。また、その名前がクライアントに送信されることもありません。したがって、これらを使用して、サーバーで秘密にしておく必要のある実装の詳細を隠すことができます。google.script は、ライブラリ内の関数や、スクリプトの最上位レベルで宣言されていない関数も認識できません。

この例では、関数 getBankBalance() はクライアント コードで使用できます。呼び出さなくても、ソースコードを検査するユーザーはその名前を見つけることができます。ただし、関数 deepSecret_()obj.objectMethod() はクライアントから完全に不可視です。

コード.gs

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

function getBankBalance() {
  var email = Session.getActiveUser().getEmail()
  return deepSecret_(email);
}

function deepSecret_(email) {
 // Do some secret calculations
 return email + ' has $1,000,000 in the bank.';
}

var obj = {
  objectMethod: function() {
    // More secret calculations
  }
};

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(balance) {
        var div = document.getElementById('output');
        div.innerHTML = balance;
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getBankBalance();
    </script>
  </head>
  <body>
    <div id="output">No result yet...</div>
  </body>
</html>

Google Workspace アプリケーションのダイアログのサイズ変更

Google ドキュメント、スプレッドシート、フォームのカスタム ダイアログ ボックスは、クライアントサイド コードで google.script.host メソッド setWidth(width) または setHeight(height) を呼び出すことでサイズを変更できます。(ダイアログの初期サイズを設定するには、HtmlOutput メソッドの setWidth(width)setHeight(height) を使用します)。ダイアログのサイズを変更しても、親ウィンドウの中央に再配置されることはありません。また、サイドバーのサイズを変更することもできません。

Google Workspaceでダイアログとサイドバーを閉じる

HTML サービスを使用して Google ドキュメント、スプレッドシート、フォームにダイアログ ボックスまたはサイドバーを表示する場合、window.close() を呼び出してインターフェースを閉じることはできません。代わりに、google.script.host.close() を呼び出す必要があります。例については、 Google Workspace ユーザー インターフェースとして HTML を提供するをご覧ください。

Google Workspaceでブラウザのフォーカスを移動する

ユーザーのブラウザでダイアログまたはサイドバーから Google ドキュメント、スプレッドシート、フォームのエディタにフォーカスを切り替えるには、google.script.host.editor.focus() メソッドを呼び出すだけです。このメソッドは、ドキュメント サービスのメソッド Document.setCursor(position) および Document.setSelection(range) と組み合わせると特に便利です。