HTML-Dienst: Mit Serverfunktionen kommunizieren

google.script.run ist eine asynchrone clientseitige JavaScript API, mit der serverseitige Apps Script-Funktionen von HTML-Dienstseiten aufgerufen werden können. Das folgende Beispiel zeigt die grundlegendste Funktion von google.script.run: den Aufruf einer Funktion auf dem Server über clientseitiges JavaScript.

Code.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>

Wenn Sie dieses Script als Webanwendung bereitstellen und die URL aufrufen, wird nichts angezeigt. In den Protokollen sehen Sie jedoch, dass die Serverfunktion doSomething() aufgerufen wurde.

Clientseitige Aufrufe von serverseitigen Funktionen sind asynchron: Nachdem der Browser den Server aufgefordert hat, die Funktion doSomething() auszuführen, fährt der Browser sofort mit der nächsten Codezeile fort, ohne auf eine Antwort zu warten. Das bedeutet, dass Serverfunktionsaufrufe möglicherweise nicht in der erwarteten Reihenfolge ausgeführt werden. Wenn Sie zwei Funktionsaufrufe gleichzeitig ausführen, lässt sich nicht vorhersagen, welche Funktion zuerst ausgeführt wird. Das Ergebnis kann sich jedes Mal unterscheiden, wenn Sie die Seite laden. In dieser Situation helfen Success-Handler und Failure-Handler, den Ablauf Ihres Codes zu steuern.

Die google.script.run API ermöglicht zehn gleichzeitige Aufrufe von Serverfunktionen. Wenn Sie einen elften Aufruf starten, während zehn bereits ausgeführt werden, wird die Serverfunktion verzögert, bis einer der zehn Plätze frei ist. In der Praxis sollten Sie diese Einschränkung selten berücksichtigen müssen, zumal die meisten Browser die Anzahl der gleichzeitigen Anfragen an denselben Server bereits auf weniger als 10 beschränken. In Firefox ist das Limit beispielsweise 6. Die meisten Browser verzögern überzählige Serveranfragen ebenfalls, bis eine der vorhandenen Anfragen abgeschlossen wurde.

Parameter und Rückgabewerte

Sie können eine Serverfunktion mit Parametern vom Client aufrufen. Ebenso kann eine Serverfunktion einen Wert als Parameter an einen Success-Handler zurückgeben.

Zulässige Parameter und Rückgabewerte sind JavaScript-Primitivtypen wie Number, Boolean, String oder null sowie JavaScript-Objekte und ‑Arrays, die aus Primitivtypen, Objekten und Arrays bestehen. Ein form-Element auf der Seite ist auch als Parameter zulässig, muss aber der einzige Parameter der Funktion sein. Es ist nicht als Rückgabewert zulässig. Anfragen schlagen fehl, wenn Sie versuchen, ein Date-, Function- oder DOM-Element außer einem form oder einen anderen nicht zulässigen Typ zu übergeben, einschließlich nicht zulässiger Typen in Objekten oder Arrays. Objekte, die zirkuläre Verweise erstellen, führen ebenfalls zu Fehlern. Nicht definierte Felder in Arrays werden zu null.

Hinweis: Ein an den Server übergebenes Objekt wird zu einer Kopie des Originals. Wenn eine Serverfunktion ein Objekt empfängt und seine Eigenschaften ändert, sind die Eigenschaften auf dem Client nicht betroffen.

Erfolgs-Handler

Da der clientseitige Code mit der nächsten Zeile fortfährt, ohne auf den Abschluss eines Serveraufrufs zu warten, können Sie mit withSuccessHandler(function) eine clientseitige Callback-Funktion angeben, die ausgeführt wird, wenn der Server antwortet. Wenn die Serverfunktion einen Wert zurückgibt, übergibt die API den Wert als Parameter an die neue Funktion.

Im folgenden Beispiel wird eine Browserbenachrichtigung angezeigt, wenn der Server antwortet. Für dieses Codebeispiel ist eine Autorisierung erforderlich, da die serverseitige Funktion auf Ihr Gmail-Konto zugreift. Am einfachsten können Sie das Script autorisieren, indem Sie die Funktion getUnreadEmails() einmal manuell im Skripteditor ausführen, bevor Sie die Seite laden. Alternativ können Sie beim Bereitstellen der Webanwendung festlegen, dass sie als „Nutzer, der auf die Webanwendung zugreift“ ausgeführt werden soll. In diesem Fall werden Sie beim Laden der App zur Autorisierung aufgefordert.

Code.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>

Fehler-Handler

Falls der Server nicht antwortet oder einen Fehler zurückgibt, können Sie mit withFailureHandler(function) einen Fehler-Handler anstelle eines Erfolgs-Handlers angeben. Das Error-Objekt (falls vorhanden) wird als Argument übergeben.

Wenn Sie keinen Fehler-Handler angeben, werden Fehler standardmäßig in der JavaScript-Konsole protokolliert. Wenn du das überschreiben möchtest, rufe withFailureHandler(null) auf oder gib einen Fehlerhandler an, der nichts tut.

Die Syntax für Fehler-Handler ist nahezu identisch mit der für Erfolgs-Handler, wie dieses Beispiel zeigt.

Code.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>

Nutzerobjekte

Sie können denselben Erfolg- oder Fehler-Handler für mehrere Aufrufe des Servers wiederverwenden. Rufen Sie dazu withUserObject(object) auf, um ein Objekt anzugeben, das als zweiter Parameter an den Handler übergeben wird. Mit diesem „Nutzerobjekt“ (nicht zu verwechseln mit der Klasse User) kannst du auf den Kontext reagieren, in dem der Client den Server kontaktiert hat. Da Nutzerobjekte nicht an den Server gesendet werden, können sie fast alles sein, einschließlich Funktionen, DOM-Elemente usw., ohne die Einschränkungen für Parameter und Rückgabewerte für Serveraufrufe. Nutzerobjekte können jedoch nicht mit dem Operator new erstellt werden.

In diesem Beispiel wird durch Klicken auf eine der beiden Schaltflächen diese Schaltfläche mit einem Wert vom Server aktualisiert, während die andere Schaltfläche unverändert bleibt, obwohl sie denselben Erfolgs-Handler teilen. Im onclick-Handler bezieht sich das Keyword this auf die button selbst.

Code.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>

Formulare

Wenn Sie eine Serverfunktion mit einem form-Element als Parameter aufrufen, wird das Formular zu einem einzelnen Objekt mit Feldnamen als Schlüsseln und Feldwerten als Werten. Alle Werte werden in Strings konvertiert, mit Ausnahme des Inhalts von Dateieingabefeldern, die zu Blob-Objekten werden.

In diesem Beispiel wird ein Formular mit einem Dateieingabefeld verarbeitet, ohne die Seite neu zu laden. Die Datei wird in Google Drive hochgeladen und die URL für die Datei wird dann auf der clientseitigen Seite ausgegeben. Im onsubmit-Handler bezieht sich das Keyword this auf das Formular selbst. Beim Laden aller Formulare auf der Seite wird die standardmäßige Sendeaktion durch preventFormSubmit deaktiviert. So wird verhindert, dass die Seite bei einer Ausnahme zu einer falschen URL weitergeleitet wird.

Code.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>

Scriptausführer

Sie können sich google.script.run als Builder für einen „Script Runner“ vorstellen. Wenn Sie einem Script Runner einen Erfolgs- oder Fehler-Handler oder ein Nutzerobjekt hinzufügen, ändern Sie den vorhandenen Runner nicht. Stattdessen erhalten Sie einen neuen Script Runner mit neuem Verhalten.

Sie können eine beliebige Kombination und Reihenfolge von withSuccessHandler(), withFailureHandler() und withUserObject() verwenden. Sie können auch eine der Änderungsfunktionen für einen Script-Runner aufrufen, für den bereits ein Wert festgelegt wurde. Der neue Wert überschreibt einfach den vorherigen Wert.

In diesem Beispiel wird für alle drei Serveraufrufe ein gemeinsamer Fehler-Handler festgelegt, aber zwei separate Erfolgs-Handler:

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

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

Private Funktionen

Serverfunktionen, deren Namen auf einen Unterstrich enden, gelten als privat. Diese Funktionen können nicht von google.script aufgerufen werden und ihre Namen werden nie an den Client gesendet. So können Sie Implementierungsdetails ausblenden, die auf dem Server geheim gehalten werden müssen. google.script kann auch keine Funktionen in Bibliotheken und Funktionen sehen, die nicht auf der obersten Ebene des Scripts deklariert sind.

In diesem Beispiel ist die Funktion getBankBalance() im Clientcode verfügbar. Ein Nutzer, der Ihren Quellcode untersucht, kann ihren Namen ermitteln, auch wenn Sie sie nicht aufrufen. Die Funktionen deepSecret_() und obj.objectMethod() sind für den Client jedoch völlig unsichtbar.

Code.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>

Größe von Dialogfeldern in Anwendungen anpassen

Die Größe von benutzerdefinierten Dialogfeldern in Google Docs, Google Tabellen oder Google Formularen kann durch Aufrufen der Methoden google.script.host setWidth(width) oder setHeight(height) im clientseitigen Code geändert werden. Um die ursprüngliche Größe eines Dialogfelds festzulegen, verwenden Sie die HtmlOutput-Methoden setWidth(width) und setHeight(height). Dialogfelder werden beim Ändern der Größe nicht im übergeordneten Fenster neu zentriert. Seitenleisten können nicht geändert werden.

Dialogfelder und Seitenleisten in schließen

Wenn Sie den HTML-Dienst verwenden, um in Google Docs, Google Tabellen oder Google Formularen ein Dialogfeld oder eine Seitenleiste anzuzeigen, können Sie die Benutzeroberfläche nicht durch Aufrufen von window.close() schließen. Stattdessen müssen Sie google.script.host.close() aufrufen. Ein Beispiel finden Sie im Abschnitt zum Ausliefern von HTML als Benutzeroberfläche.

Browserfokus in verschieben

Wenn Sie den Fokus im Browser des Nutzers von einem Dialogfeld oder einer Seitenleiste zurück zum Editor von Google Docs, Google Tabellen oder Google Formulare wechseln möchten, rufen Sie einfach die Methode google.script.host.editor.focus() auf. Diese Methode ist besonders nützlich in Kombination mit den Methoden Document.setCursor(position) und Document.setSelection(range) des Document Service.