Serviço HTML: HTML a partir de modelo

Você pode misturar o código do Apps Script e o HTML para produzir páginas dinâmicas com o mínimo de esforço. Se você usou uma linguagem de modelo que mistura código e HTML, como PHP, ASP ou Java, a sintaxe deve parecer familiar.

Scriptlets

Os modelos do Apps Script podem conter três tags especiais, chamadas "scriptlets". Dentro de um scriptlet, você pode escrever qualquer código que funcione em um arquivo normal do Apps Script: eles podem chamar funções definidas em outros arquivos de código, referenciar variáveis globais ou usar qualquer uma das APIs do Apps Script. É possível até mesmo definir funções e variáveis dentro de scriptlets, com a ressalva de que eles não podem ser chamados por funções definidas em arquivos de código ou outros modelos.

Se você colar o exemplo abaixo no editor de script, o conteúdo da tag <?= ... ?> (um scriptlet de impressão) vai aparecer em itálico. Esse código em itálico é executado no servidor antes da página ser veiculada ao usuário. Como o código do scriptlet é executado antes da exibição da página, ele só pode ser executado uma vez por página. Ao contrário das funções JavaScript do lado do cliente ou do Apps Script que você chama por meio de google.script.run, os scripts não podem ser executados novamente depois que a página é carregada.

Code.gs

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

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    Hello, World! The time is <?= new Date() ?>.
  </body>
</html>

A função doGet() para HTML com modelo é diferente dos exemplos de criação e veiculação de HTML básico. A função mostrada aqui gera um objeto HtmlTemplate do arquivo HTML e chama o método evaluate() para executar os scriptlets e converter o modelo em um objeto HtmlOutput que o script pode veicular ao usuário.

Scriptlets padrão

Os scripts padrão, que usam a sintaxe <? ... ?>, executam o código sem gerar explicitamente o conteúdo para a página. No entanto, como o exemplo mostra, o resultado do código dentro de um scriptlet ainda pode afetar o conteúdo HTML fora do scriptlet:

Code.gs

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

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? if (true) { ?>
      <p>This will always be served!</p>
    <? } else  { ?>
      <p>This will never be served.</p>
    <? } ?>
  </body>
</html>

Como imprimir scripts

A impressão de scripts, que usam a sintaxe <?= ... ?>, geram os resultados do código na página usando escape contextual.

Escape contextual significa que o Apps Script monitora o contexto da saída na página (dentro de um atributo HTML, dentro de uma tag script do lado do cliente ou em qualquer outro lugar) e adiciona automaticamente caracteres de escape para proteger contra ataques de scripting em vários sites (XSS).

Neste exemplo, o primeiro scriptlet de impressão gera uma string diretamente. Ele é seguido por um scriptlet padrão que configura uma matriz e um loop, seguido por outro scriptlet de impressão para gerar o conteúdo da matriz.

Code.gs

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

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

Observe que um scriptlet de impressão só gera o valor da primeira instrução. As instruções restantes se comportam como se estivessem em um script padrão. Por exemplo, o scriptlet <?= 'Hello, world!'; 'abc' ?> imprime apenas "Hello, world!"

Forçar a impressão de scripts

Os scripts de impressão forçada, que usam a sintaxe <?!= ... ?>, são como scripts de impressão, mas evitam escape contextual.

O escape contextual é importante se o script permite entradas não confiáveis do usuário. Por outro lado, você terá que forçar a impressão se a saída do scriptlet contiver intencionalmente HTML ou scripts que você queira inserir exatamente como especificado.

Como regra geral, use scripts de impressão em vez de forçar a impressão, a menos que você saiba que precisa imprimir HTML ou JavaScript sem alterações.

Código do Apps Script em scriptlets

Os scripts não se restringem à execução de JavaScript normal. Também é possível usar uma das três técnicas a seguir para conceder aos seus modelos acesso aos dados do Apps Script.

No entanto, como o código do modelo é executado antes da página ser veiculada ao usuário, essas técnicas só podem alimentar a página com o conteúdo inicial. Para acessar os dados do Apps Script em uma página de maneira interativa, use a API google.script.run.

Como chamar funções do Apps Script usando um modelo

Os scripts podem chamar qualquer função definida em um arquivo ou biblioteca de código do Apps Script. Neste exemplo, mostramos uma maneira de extrair dados de uma planilha para um modelo e, em seguida, construir uma tabela HTML a partir dos dados.

Code.gs

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

function getData() {
  return SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = getData(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

Como chamar APIs do Apps Script diretamente

Também é possível usar o código do Apps Script diretamente em scripts. Este exemplo tem o mesmo resultado que o exemplo anterior carregando os dados no próprio modelo em vez de usar uma função separada.

Code.gs

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

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = SpreadsheetApp
        .openById('1234567890abcdefghijklmnopqrstuvwxyz')
        .getActiveSheet()
        .getDataRange()
        .getValues(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

Como enviar variáveis para modelos

Por fim, é possível enviar variáveis para um modelo atribuindo-as como propriedades do objeto HtmlTemplate. Mais uma vez, este exemplo tem o mesmo resultado que os exemplos anteriores.

Code.gs

function doGet() {
  var t = HtmlService.createTemplateFromFile('Index');
  t.data = SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
  return t.evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

Modelos de depuração

A depuração dos modelos pode ser difícil porque o código que você escreve não é executado diretamente. Em vez disso, o servidor transforma o modelo em código e, em seguida, executa o código resultante.

Se não for óbvio como o modelo está interpretando os scriptlets, dois métodos de depuração na classe HtmlTemplate podem ajudar você a entender melhor o que está acontecendo.

getCode()

getCode() retorna uma string contendo o código que o servidor cria a partir do modelo. Se você registrar o código e colar no editor de script, poderá executá-lo e depurá-lo como o código normal do Apps Script.

Este é o modelo simples que mostra uma lista de produtos do Google novamente, seguido pelo resultado de getCode():

Code.gs

function myFunction() {
  Logger.log(HtmlService
      .createTemplateFromFile('Index')
      .getCode());
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

REGISTRO (AVALIADO)

(function() { var output = HtmlService.initTemplate(); output._ =  '<!DOCTYPE html>\n';
  output._ =  '<html>\n' +
    '  <head>\n' +
    '    <base target=\"_top\">\n' +
    '  </head>\n' +
    '  <body>\n' +
    '    '; output._$ =  'My favorite Google products:' ;
  output._ =  '    ';  var data = ['Gmail', 'Docs', 'Android'];
        for (var i = 0; i < data.length; i++) { ;
  output._ =  '        <b>'; output._$ =  data[i] ; output._ =  '</b>\n';
  output._ =  '    ';  } ;
  output._ =  '  </body>\n';
  output._ =  '</html>';
  /* End of user code */
  return output.$out.append('');
})();

getCodeWithComments()

getCodeWithComments() é semelhante a getCode(), mas retorna o código avaliado como comentários que aparecem lado a lado com o modelo original.

Como percorrer o código avaliado

A primeira coisa que você vai notar em qualquer exemplo de código avaliado é o objeto output implícito criado pelo método HtmlService.initTemplate(). Esse método não é documentado, porque apenas os próprios modelos precisam usá-lo. output é um objeto HtmlOutput especial com duas propriedades com nomes incomuns, _ e _$, que são uma abreviação para chamar append() e appendUntrusted().

output tem mais uma propriedade especial, $out, que se refere a um objeto HtmlOutput normal que não tem essas propriedades especiais. O modelo retorna esse objeto normal ao final do código.

Agora que você entende essa sintaxe, o restante do código deve ser bastante fácil de seguir. O conteúdo HTML fora dos scriptlets (como a tag b) é anexado usando output._ = (sem escape contextual), e os scripts são anexados como JavaScript (com ou sem escape contextual, dependendo do tipo de scriptlet).

Observe que o código avaliado preserva os números de linha do modelo. Se você receber um erro ao executar o código avaliado, a linha vai corresponder ao conteúdo equivalente no modelo.

Hierarquia dos comentários

Como o código avaliado preserva números de linha, é possível que comentários dentro de scriptlets comentem outros scriptlets e até mesmo código HTML. Esses exemplos mostram alguns efeitos surpreendentes dos comentários:

<? var x; // a comment ?> This sentence won't print because a comment begins inside a scriptlet on the same line.

<? var y; // ?> <?= "This sentence won't print because a comment begins inside a scriptlet on the same line.";
output.append("This sentence will print because it's on the next line, even though it's in the same scriptlet.”) ?>

<? doSomething(); /* ?>
This entire block is commented out,
even if you add a */ in the HTML
or in a <script> */ </script> tag,
<? until you end the comment inside a scriptlet. */ ?>