Service HTML: communiquer avec les fonctions du serveur

google.script.run est une API JavaScript côté client asynchrone qui permet aux pages de services HTML d'appeler des fonctions Apps Script côté serveur. L'exemple suivant montre les fonctionnalités de base de google.script.run : appeler une fonction sur le serveur à partir de JavaScript côté client.

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>

Si vous déployez ce script en tant qu'application Web et que vous accédez à son URL, vous ne verrez rien. Toutefois, si vous consultez les journaux, vous constaterez que la fonction de serveur doSomething() a été appelée.

Les appels côté client vers les fonctions côté serveur sont asynchrones: après que le navigateur a demandé au serveur d'exécuter la fonction doSomething(), le navigateur passe immédiatement à la ligne de code suivante sans attendre de réponse. Cela signifie que les appels de fonction serveur risquent de ne pas s'exécuter dans l'ordre attendu. Si vous effectuez deux appels de fonction en même temps, vous ne pouvez pas savoir quelle fonction s'exécutera en premier. Le résultat peut varier à chaque chargement de la page. Dans ce cas, les gestionnaires de réussite et les gestionnaires d'échecs permettent de contrôler le flux de votre code.

L'API google.script.run autorise 10 appels simultanés aux fonctions du serveur. Si vous effectuez un 11e appel pendant que 10 sont en cours d'exécution, la fonction serveur sera différée jusqu'à ce que l'un des 10 emplacements soit libéré. En pratique, vous devez rarement penser à cette restriction, d'autant que la plupart des navigateurs limitent déjà le nombre de requêtes simultanées au même serveur avec un nombre inférieur à 10. Dans Firefox, par exemple, la limite est de 6. De même, la plupart des navigateurs retardent les requêtes serveur excédentaires jusqu'à ce que l'une des requêtes existantes soit terminée.

Paramètres et valeurs renvoyées

Vous pouvez appeler une fonction de serveur avec les paramètres du client. De même, une fonction de serveur peut renvoyer une valeur au client sous la forme d'un paramètre transmis à un gestionnaire de réussite.

Les paramètres juridiques et les valeurs renvoyées sont des primitives JavaScript comme Number, Boolean, String ou null, ainsi que des objets et des tableaux JavaScript composés de primitives, d'objets et de tableaux. Un élément form sur la page est également légal en tant que paramètre, mais il doit s'agir du seul paramètre de la fonction. Il n'est pas non plus autorisé comme valeur de retour. Les requêtes échouent si vous tentez de transmettre un élément Date, Function ou DOM en plus d'un élément form ou d'un autre type interdit, y compris les types interdits dans les objets ou les tableaux. Les objets qui créent des références circulaires échouent également, et les champs non définis dans des tableaux deviennent null.

Notez qu'un objet transmis au serveur devient une copie de l'original. Si une fonction de serveur reçoit un objet et modifie ses propriétés, les propriétés sur le client ne sont pas affectées.

Gestionnaires de réussite

Comme le code côté client continue à passer à la ligne suivante sans attendre la fin de l'appel du serveur, withSuccessHandler(function) vous permet de spécifier une fonction de rappel côté client à exécuter lorsque le serveur répond. Si la fonction serveur renvoie une valeur, l'API transmet cette valeur à la nouvelle fonction en tant que paramètre.

L'exemple suivant affiche une alerte de navigateur lorsque le serveur répond. Notez que cet exemple de code nécessite une autorisation, car la fonction côté serveur accède à votre compte Gmail. Le moyen le plus simple d'autoriser le script consiste à exécuter manuellement la fonction getUnreadEmails() une fois avant de charger la page, à partir de l'éditeur de scripts. Sinon, lorsque vous déployez l'application Web, vous pouvez choisir de l'exécuter en tant qu'"utilisateur accédant à l'application Web". Dans ce cas, vous serez invité à autoriser le chargement de l'application.

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>

Gestionnaires d'échecs

Si le serveur ne répond pas ou génère une erreur, withFailureHandler(function) vous permet de spécifier un gestionnaire d'échecs au lieu d'un gestionnaire de réussite, avec l'objet Error (le cas échéant) transmis en tant qu'argument.

Par défaut, si vous ne spécifiez pas de gestionnaire d'échecs, les échecs sont consignés dans la console JavaScript. Pour contourner ce problème, appelez withFailureHandler(null) ou fournissez un gestionnaire d'échecs qui n'effectue aucune action.

La syntaxe des gestionnaires d'échecs est presque identique à celle des gestionnaires de réussite, comme le montre cet exemple.

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>

Objets utilisateur

Vous pouvez réutiliser le même gestionnaire de réussite ou d'échec pour plusieurs appels au serveur en appelant withUserObject(object) afin de spécifier un objet qui sera transmis au gestionnaire en tant que second paramètre. Cet "objet utilisateur" (à ne pas confondre avec la classe User) vous permet de répondre au contexte dans lequel le client a contacté le serveur. Étant donné que les objets utilisateur ne sont pas envoyés au serveur, il peut s'agir de presque n'importe quel élément, y compris des fonctions, des éléments DOM, etc., sans restrictions concernant les paramètres et les valeurs renvoyées pour les appels au serveur. Toutefois, les objets utilisateur ne peuvent pas être des objets construits avec l'opérateur new.

Dans cet exemple, le fait de cliquer sur l'un de ces deux boutons mettra à jour ce bouton avec une valeur du serveur tout en laissant l'autre bouton inchangé, même s'ils partagent un gestionnaire de réussite. Dans le gestionnaire onclick, le mot clé this fait référence au button lui-même.

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>

Formulaires

Si vous appelez une fonction de serveur avec un élément form comme paramètre, le formulaire devient un objet unique avec des noms de champ comme clés et des valeurs de champ comme valeurs. Les valeurs sont toutes converties en chaînes, à l'exception du contenu des champs d'entrée de fichier, qui deviennent des objets Blob.

Cet exemple traite un formulaire, y compris un champ de saisie de fichier, sans actualiser la page. Il importe le fichier dans Google Drive, puis affiche l'URL du fichier sur la page côté client. Dans le gestionnaire onsubmit, le mot clé this fait référence au formulaire lui-même. Notez qu'au chargement de tous les formulaires de la page, l'action d'envoi par défaut est désactivée par preventFormSubmit. Cela empêche la page de rediriger les utilisateurs vers une URL inexacte en cas d'exception.

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>

Exécuteurs de script

Vous pouvez considérer google.script.run comme un compilateur pour un "exécuteur de script". Si vous ajoutez un gestionnaire de réussite, un gestionnaire d'échec ou un objet utilisateur à un exécuteur de script, vous ne modifiez pas l'exécuteur existant. À la place, vous obtenez à nouveau un exécuteur de script avec un nouveau comportement.

Vous pouvez utiliser n'importe quelle combinaison et n'importe quel ordre withSuccessHandler(), withFailureHandler() et withUserObject(). Vous pouvez également appeler l'une des fonctions de modification sur un exécuteur de script dont la valeur est déjà définie. La nouvelle valeur remplace simplement la valeur précédente.

Cet exemple définit un gestionnaire de défaillances commun pour les trois appels de serveur, mais deux gestionnaires de réussite distincts:

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

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

Fonctions privées

Les fonctions de serveur dont le nom se termine par un trait de soulignement sont considérées comme privées. Ces fonctions ne peuvent pas être appelées par google.script et leur nom n'est jamais envoyé au client. Vous pouvez ainsi les utiliser pour masquer les détails de mise en œuvre qui doivent rester secrets sur le serveur. google.script ne peut pas non plus voir les fonctions dans les bibliothèques, ni celles qui ne sont pas déclarées au niveau supérieur du script.

Dans cet exemple, la fonction getBankBalance() est disponible dans le code client. Un utilisateur qui inspecte votre code source peut découvrir son nom, même si vous ne l'appelez pas. Toutefois, les fonctions deepSecret_() et obj.objectMethod() sont complètement invisibles pour le client.

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>

Redimensionnement des boîtes de dialogue dans Google Workspace les applications

Vous pouvez redimensionner les boîtes de dialogue personnalisées de Google Docs, Sheets ou Forms en appelant les méthodes google.script.host setWidth(width) ou setHeight(height) dans le code côté client. (Pour définir la taille initiale d'une boîte de dialogue, utilisez les méthodes HtmlOutput setWidth(width) et setHeight(height).) Notez que les boîtes de dialogue ne sont pas recentrées dans la fenêtre parente lorsqu'elles sont redimensionnées et qu'il n'est pas possible de redimensionner les barres latérales.

Fermeture des boîtes de dialogue et des barres latérales dans Google Workspace

Si vous utilisez le service HTML pour afficher une boîte de dialogue ou une barre latérale dans Google Docs, Sheets ou Forms, vous ne pouvez pas fermer l'interface en appelant window.close(). Vous devez appeler google.script.host.close(). Pour obtenir un exemple, consultez la section Diffuser du code HTML en tant qu' Google Workspace interface utilisateur.

Déplacement du curseur dans le navigateur Google Workspace

Pour basculer le navigateur de l'utilisateur d'une boîte de dialogue ou d'une barre latérale vers l'éditeur Google Docs, Sheets ou Forms, il vous suffit d'appeler la méthode google.script.host.editor.focus(). Cette méthode est particulièrement utile en combinaison avec les méthodes Service Document Document.setCursor(position) et Document.setSelection(range).