Serviço HTML: comunicar-se com as funções do servidor

A google.script.run é uma API JavaScript assíncrona do lado do cliente que permite que páginas de serviços HTML chamem funções do Apps Script no lado do servidor. No exemplo a seguir, mostramos a funcionalidade mais básica de google.script.run: chamar uma função no servidor usando o JavaScript do lado do cliente.

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>

Se você implantar esse script como um app da Web e acessar o URL dele, não vai ver nada, mas vai notar que a função do servidor doSomething() foi chamada nos registros.

As chamadas do lado do cliente para funções do lado do servidor são assíncronas: depois que o navegador solicita que o servidor execute a função doSomething(), o navegador continua imediatamente para a próxima linha de código sem esperar por uma resposta. Isso significa que as chamadas de função do servidor podem não ser executadas na ordem esperada. Se você fizer duas chamadas de função ao mesmo tempo, não será possível saber qual função será executada primeiro. O resultado poderá ser diferente cada vez que você carregar a página. Nessa situação, gerenciadores de sucesso e gerenciadores de falha ajudam a controlar o fluxo do código.

A API google.script.run permite 10 chamadas simultâneas para funções do servidor. Se você fizer uma 11a chamada enquanto as 10 ainda estiverem em execução, a função do servidor será atrasada até que um dos 10 espaços seja liberado. Na prática, raramente é necessário pensar sobre essa restrição, especialmente porque a maioria dos navegadores já limita o número de solicitações simultâneas para o mesmo servidor em um número inferior a 10. No Firefox, por exemplo, o limite é 6. Da mesma forma, a maioria dos navegadores atrasa as solicitações excedentes do servidor até que uma das solicitações seja concluída.

Parâmetros e valores de retorno

Você pode chamar uma função de servidor com parâmetros do cliente. Da mesma forma, uma função de servidor pode retornar um valor para o cliente como um parâmetro transmitido para um gerenciador de sucesso.

Parâmetros legais e valores de retorno são primitivos do JavaScript, como Number, Boolean, String ou null, além de objetos e matrizes JavaScript compostos de primitivos, objetos e matrizes. Um elemento form na página também é legal como parâmetro, mas precisa ser o único parâmetro da função e não é legal como um valor de retorno. As solicitações falham se você tenta transmitir um elemento Date, Function e DOM além de um form ou outro tipo proibido, inclusive tipos proibidos dentro de objetos ou matrizes. Os objetos que criam referências circulares também falharão, e os campos indefinidos dentro das matrizes se tornarão null.

Um objeto transmitido para o servidor se torna uma cópia do original. Se uma função do servidor receber um objeto e alterar as propriedades dele, as propriedades no cliente não serão afetadas.

Gerenciadores de sucesso

Como o código do lado do cliente continua na próxima linha sem esperar a conclusão de uma chamada de servidor, withSuccessHandler(function) permite especificar uma função de callback do lado do cliente para ser executada quando o servidor responder. Se a função do servidor retornar um valor, a API passará o valor para a nova função como um parâmetro.

O exemplo a seguir exibe um alerta do navegador quando o servidor responde. Esse exemplo de código requer autorização porque a função do lado do servidor está acessando sua conta do Gmail. A maneira mais simples de autorizar o script é executar a função getUnreadEmails() manualmente no editor de script uma vez antes de carregar a página. Como alternativa, quando você implanta o aplicativo da Web, pode optar por executá-lo como o “usuário que acessa o aplicativo da Web”. Nesse caso, você receberá uma solicitação de autorização ao carregar o aplicativo.

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>

Gerenciadores de falhas

Caso o servidor não responda ou gere um erro, o withFailureHandler(function) permite que você especifique um gerenciador de falhas em vez de um gerenciador de sucesso, com o objeto Error (se houver) transmitido como um argumento.

Por padrão, se você não especificar um gerenciador de falhas, as falhas serão registradas no console JavaScript. Para substituir isso, chame withFailureHandler(null) ou forneça um gerenciador de falhas que não faça nada.

A sintaxe dos gerenciadores de falha é quase idêntica à sintaxe dos gerenciadores de sucesso, como mostra este exemplo.

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>

Objetos do usuário

É possível reutilizar o mesmo gerenciador de sucesso ou falha para várias chamadas para o servidor chamando withUserObject(object) para especificar um objeto que será transmitido ao gerenciador como um segundo parâmetro. Esse "objeto de usuário", que não pode ser confundido com a classe User, permite responder ao contexto em que o cliente entrou em contato com o servidor. Como os objetos de usuário não são enviados ao servidor, eles podem ser quase tudo, incluindo funções, elementos do DOM e assim por diante, sem as restrições de parâmetros e valores de retorno para chamadas de servidor. No entanto, os objetos do usuário não podem ser criados com o operador new.

Neste exemplo, clicar em um dos dois botões vai atualizar o botão com um valor do servidor e deixar o outro inalterado, mesmo que eles compartilhem um gerenciador de sucesso. No gerenciador onclick, a palavra-chave this se refere ao próprio button.

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>

Formulários

Se você chamar uma função do servidor com um elemento form como parâmetro, o formulário se tornará um único objeto com nomes de campo como chaves e valores de campo como valores. Todos os valores são convertidos em strings, exceto o conteúdo dos campos de entrada do arquivo, que se tornam objetos Blob.

Este exemplo processa um formulário, incluindo um campo de entrada de arquivo, sem recarregar a página. Ele faz upload do arquivo para o Google Drive e imprime o URL dele na página do lado do cliente. No gerenciador onsubmit, a palavra-chave this se refere ao próprio formulário. Observe que, ao carregar todos os formulários na página, a ação de envio padrão será desativada por preventFormSubmit. Isso evita que a página redirecione para um URL incorreto no caso de uma exceção.

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>

Executores de scripts

Pense em google.script.run como um builder para um "executor de script". Se você adicionar um gerenciador de sucesso, gerenciador de falhas ou objeto de usuário a um executor de script, você não vai alterar o executor de scripts. Em vez disso, você receberá um novo executor de script com novo comportamento.

Você pode usar qualquer combinação e qualquer ordem de withSuccessHandler(), withFailureHandler() e withUserObject(). Também é possível chamar qualquer uma das funções de modificação em um executor de script que já tenha um valor definido. O novo valor simplesmente substitui o valor anterior.

Neste exemplo, definimos um gerenciador de falhas comum para as três chamadas de servidor, mas dois gerenciadores de sucesso separados:

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

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

Funções privadas

As funções de servidor que têm nomes terminados em sublinhado são consideradas privadas. Essas funções não podem ser chamadas por google.script e os nomes delas nunca são enviados ao cliente. Use-os para ocultar detalhes de implementação que precisam ser mantidos em sigilo no servidor. google.script também não pode ver funções em bibliotecas e funções que não são declaradas no nível superior do script.

Neste exemplo, a função getBankBalance() está disponível no código do cliente. Um usuário que inspeciona seu código-fonte pode descobrir o nome dela mesmo que você não o chame. No entanto, as funções deepSecret_() e obj.objectMethod() são completamente invisíveis para o cliente.

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>

Redimensionamento de caixas de diálogo em Google Workspace aplicativos

É possível redimensionar caixas de diálogo personalizadas nos apps Documentos, Planilhas ou Formulários Google chamando os métodos google.script.host setWidth(width) ou setHeight(height) no código do lado do cliente. Para definir o tamanho inicial de uma caixa de diálogo, use os métodos HtmlOutput setWidth(width) e setHeight(height). As caixas de diálogo não são recentralizadas na janela mãe quando redimensionadas, e não é possível redimensionar as barras laterais.

Fechar caixas de diálogo e barras laterais no Google Workspace

Se você usa o serviço HTML para exibir uma caixa de diálogo ou uma barra lateral nos Documentos, Planilhas ou Formulários Google, não é possível fechar a interface chamando window.close(). Em vez disso, chame google.script.host.close(). Para ver um exemplo, consulte a seção sobre como exibir HTML como uma Google Workspace interface do usuário.

Movendo o foco do navegador em Google Workspace

Para mudar o foco do navegador do usuário de uma caixa de diálogo ou barra lateral de volta para o editor dos Documentos, Planilhas ou Formulários Google, basta chamar o método google.script.host.editor.focus(). Esse método é particularmente útil em combinação com os métodos do serviço de documentos Document.setCursor(position) e Document.setSelection(range).