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

google.script.run é uma API JavaScript assíncrona do lado do cliente que permite que páginas do serviço HTML chamem funções do Apps Script do lado do servidor. O exemplo a seguir mostra a funcionalidade mais básica de google.script.run: chamar uma função no servidor usando 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 web app e acessar o URL dele, nada vai aparecer. No entanto, se você visualizar os registros, vai notar que a função do servidor doSomething() foi chamada.

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(), ele continua imediatamente para a próxima linha de código sem esperar 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 delas será executada primeiro. O resultado pode ser diferente a cada vez que você carregar a página. Nessa situação, os processadores de sucesso e processadores de falha ajudam a controlar o fluxo do seu código.

A API google.script.run permite 10 chamadas simultâneas para funções do servidor. Se você fizer uma 11ª chamada enquanto 10 ainda estiverem em execução, a função do servidor será adiada até que um dos 10 spots seja liberado. Na prática, raramente é necessário pensar nessa restrição, principalmente porque a maioria dos navegadores já limita o número de solicitações simultâneas ao mesmo servidor a um número inferior a 10. No Firefox, por exemplo, o limite é 6. A maioria dos navegadores atrasa o excesso de solicitações do servidor até que uma das solicitações atuais seja concluída.

Parâmetros e valores de retorno

É possível chamar uma função do servidor com parâmetros do cliente. Da mesma forma, uma função do servidor pode retornar um valor ao cliente como um parâmetro transmitido a um processador 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 de JavaScript que são compostos de primitivos, objetos e matrizes. Um elemento form na página também é permitido como parâmetro, mas precisa ser o único parâmetro da função e não é permitido como valor de retorno. As solicitações falham se você tentar transmitir um Date, Function, elemento DOM além de um form ou outro tipo proibido, incluindo tipos proibidos dentro de objetos ou matrizes. Objetos que criam referências circulares também falham, e campos indefinidos em matrizes se tornam null.

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

Manipuladores de sucesso

Como o código do lado do cliente continua para a próxima linha sem esperar que uma chamada do servidor seja concluída, o 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 vai transmitir esse valor para a nova função como um parâmetro.

O exemplo a seguir mostra um alerta do navegador quando o servidor responde. Essa amostra de código exige 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 scripts uma vez antes de carregar a página. Como alternativa, ao implantar o app da Web, você pode escolher executá-lo como o "usuário que acessa o app da Web". Nesse caso, será solicitada uma autorização quando você carregar o app.

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 especificar 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, elas serão registradas no console JavaScript. Para substituir isso, chame withFailureHandler(null) ou forneça um gerenciador de falhas que não faz nada.

A sintaxe dos gerenciadores de falhas é quase idêntica à 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 manipulador de sucesso ou falha para várias chamadas ao servidor chamando withUserObject(object) para especificar um objeto que será transmitido ao manipulador como um segundo parâmetro. Esse "objeto de usuário", que não deve 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 qualquer coisa, incluindo funções, elementos DOM e assim por diante, sem as restrições de parâmetros e valores de retorno para chamadas de servidor. No entanto, os objetos de usuário não podem ser construídos com o operador new.

Neste exemplo, clicar em qualquer um dos dois botões vai atualizar esse botão com um valor do servidor, deixando o outro botão inalterado, mesmo que eles compartilhem um manipulador de sucesso. Dentro do manipulador 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 de 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. Os valores são todos convertidos em strings, exceto o conteúdo dos campos de entrada de 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 do arquivo na página do lado do cliente. Dentro do manipulador onsubmit, a palavra-chave this se refere ao próprio formulário. Ao carregar, todos os formulários na página têm a ação de envio padrão desativada por preventFormSubmit. Isso evita que a página redirecione para um URL impreciso em caso de 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 criador de um "executor de script". Se você adicionar um manipulador de sucesso, um manipulador de falha ou um objeto de usuário a um executor de script, você não vai mudar o executor atual. Em vez disso, você vai receber um novo executor de script com um novo comportamento.

É possível usar qualquer combinação e ordem de withSuccessHandler(), withFailureHandler() e withUserObject(). Você também pode 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 anterior.

Este exemplo define um gerenciador de falhas comum para todas 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 particulares

As funções do servidor cujos nomes terminam com um sublinhado são consideradas particulares. Essas funções não podem ser chamadas por google.script, e os nomes delas nunca são enviados ao cliente. Assim, é possível usá-los para ocultar detalhes de implementação que precisam ser mantidos em segredo no servidor. O google.script também não consegue 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 a 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>

Redimensionar caixas de diálogo nos aplicativos do Google Workspace

Caixas de diálogo personalizadas nos Documentos, Planilhas ou Formulários Google podem ser redimensionadas 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 principal quando redimensionadas, e não é possível redimensionar as barras laterais.

Como fechar caixas de diálogo e barras laterais no Google Workspace

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

Mover o foco do navegador no Google Workspace

Para mudar o foco no 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 é útil principalmente quando usado com os métodos do serviço de documentos Document.setCursor(position) e Document.setSelection(range).