É possível misturar código do Apps Script e HTML para produzir páginas dinâmicas com o mínimo de esforço. Se você já usou uma linguagem de modelos que mistura código e HTML, como PHP, ASP ou JSP, a sintaxe vai parecer familiar.
Scriptlets
Os modelos do Apps Script podem conter três tags especiais, chamadas scriptlets. Em um scriptlet, é possível escrever qualquer código que funcione em um arquivo normal do Apps Script: os scriptlets podem chamar funções definidas em outros arquivos de código, referenciar variáveis globais ou usar qualquer uma das APIs do Apps Script. Você pode até definir funções e variáveis em scriptlets, mas elas não podem ser chamadas 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 que a página seja enviada
ao usuário. Como o código do scriptlet é executado antes que a página seja veiculada, 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 pelo
google.script.run
, os scriptlets não podem
ser executados novamente após o carregamento da página.
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 oferecer ao usuário.
Scriptlets padrão
Os scriptlets padrão, que usam a sintaxe <? ... ?>
, executam o código sem
retornar o conteúdo explicitamente para a página. No entanto, como este 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 scriptlets
Os scriptlets de impressão, que usam a sintaxe <?= ... ?>
, geram os resultados do
código na página usando a codificação contextual.
A codificação evasiva contextual significa que o Apps Script rastreia o contexto da saída na página (dentro de um atributo HTML, de uma tag script
do lado do cliente ou em qualquer outro lugar) e adiciona automaticamente caracteres de codificação evasiva para se proteger contra ataques de script 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>
Um scriptlet de impressão só gera o valor da primeira instrução.
Todas as outras instruções se comportam como se estivessem contidas em um scriptlet
padrão. Por exemplo, o scriptlet <?= 'Hello, world!'; 'abc' ?>
só
imprime "Hello, world!"
Scriptlets de impressão forçada
Os scriptlets de impressão forçada, que usam a sintaxe <?!= ... ?>
, são como scriptlets
de impressão, exceto que evitam a fuga de contexto.
A codificação por contexto é importante se o script permitir entrada de usuário não confiável. Por outro lado, será necessário forçar a impressão se a saída do scriptlet intencionalmente contiver HTML ou scripts que você quer inserir exatamente como especificado.
Como regra geral, use scriptlets de impressão em vez de forçar a impressão de scriptlets, a menos que você saiba que precisa imprimir HTML ou JavaScript sem alterações.
Código do Apps Script em scriptlets
Os scriptlets não são restritos a execução de JavaScript normal. Você também pode usar qualquer uma das três técnicas a seguir para dar aos seus modelos acesso aos dados do Apps Script.
No entanto, como o código do modelo é executado antes que a página seja enviada
ao usuário, essas técnicas só podem alimentar o conteúdo inicial de uma página. Para acessar
os dados do Apps Script de uma página de forma interativa, use a
API google.script.run
.
Chamar funções do Apps Script em um modelo
Os scriptlets podem chamar qualquer função definida em um arquivo ou biblioteca de código do Apps Script. Este exemplo mostra uma maneira de extrair dados de uma planilha para um modelo e, em seguida, criar uma tabela HTML com base nos 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>
Chamar as APIs do Apps Script diretamente
Também é possível usar o código do Apps Script diretamente em scriptlets. Esse exemplo alcança o mesmo resultado do exemplo anterior ao carregar os dados no 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 alcança o mesmo resultado dos 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
Os modelos podem ser difíceis de depurar porque o código escrito 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 estiver claro como o modelo está interpretando seus 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 que contém o código criado pelo servidor com base no modelo. Se você
registrar o
código e colá-lo no editor de script, poderá executá-lo e
depurar como um código normal do
Apps Script.
Confira 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>
LOG (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 tem documentação porque só os modelos precisam usá-lo. output
é um
objeto HtmlOutput
especial com duas
propriedades com nomes incomuns, _
e _$
, que são abreviações para chamar
append()
e
appendUntrusted()
.
output
tem mais uma propriedade especial, $out
, que se refere a um objeto
HtmlOutput
regular que não possui essas propriedades especiais. O modelo
retorna esse objeto normal no 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 scriptlets são anexados como JavaScript (com ou sem escape contextual,
dependendo do tipo de scriptlet).
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 de comentários
Como o código avaliado preserva os números de linha, é possível que comentários dentro de scriptlets desativem outros scriptlets e até mesmo o 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. */ ?>