Servicio HTML: HTML basado en una plantilla

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Puedes mezclar el código de Apps Script y el HTML para producir páginas dinámicas con un esfuerzo mínimo. Si usaste un lenguaje de plantillas que combina código y HTML, como PHP, ASP o JSP, la sintaxis debería resultarte familiar.

Guiones

Las plantillas de Apps Script pueden contener tres etiquetas especiales, denominadas scriptlets. Dentro de un scriptlet, puedes escribir cualquier código que funcione en un archivo normal de Apps Script: los scriptlets pueden llamar a funciones definidas en otros archivos de código, hacer referencia a variables globales o usar cualquiera de las API de Apps Script. Incluso puedes definir funciones y variables dentro de los scriptlets, con la salvedad de que no se pueden llamar mediante funciones definidas en archivos de código o en otras plantillas.

Si pegas el ejemplo a continuación en el editor de secuencias de comandos, el contenido de la etiqueta <?= ... ?> (un scriptlet de impresión) aparecerá en cursiva. Ese código en cursiva se ejecuta en el servidor antes de que la página se entregue al usuario. Debido a que el código de scriptlet se ejecuta antes de que se entregue la página, solo puede ejecutarse una vez por página. A diferencia de las funciones de JavaScript o de Apps Script del cliente a las que llamas a través de google.script.run, las secuencias de comandos no se pueden volver a ejecutar después de que se carga la página.

Code.gs

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

Índice html

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

Ten en cuenta que la función doGet() para HTML con plantilla difiere de los ejemplos para crear y entregar HTML básico. La función que se muestra aquí genera un objeto HtmlTemplate a partir del archivo HTML y, luego, llama a su método evaluate() para ejecutar los scriptlet y convertir la plantilla en un objeto HtmlOutput que la secuencia de comandos puede entregar al usuario.

Guiones estándar

Los scriptlets estándar, que usan la sintaxis <? ... ?>, ejecutan código sin generar contenido de forma explícita en la página. Sin embargo, como se muestra en este ejemplo, el resultado del código dentro de un scriptlet puede afectar el contenido HTML fuera del scriptlet:

Code.gs

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

Índice 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>

Impresión de guiones

La impresión de secuencias de comandos, que usan la sintaxis <?= ... ?>, genera los resultados de su código en la página mediante el escape contextual.

El escape contextual significa que Apps Script realiza un seguimiento del contexto de la salida en la página (dentro de un atributo HTML, dentro de una etiqueta script del cliente o en cualquier otro lugar) y agrega caracteres de escape automáticamente para proteger contra ataques de secuencias de comandos entre sitios (XSS).

En este ejemplo, el primer scriptlet de impresión genera una string directamente, seguida de un scriptlet estándar que configura un arreglo y un bucle, seguido de otro scriptlet de impresión para generar el contenido del arreglo.

Code.gs

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

Índice 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>

Ten en cuenta que un scriptlet de impresión solo genera el valor de su primera declaración; las declaraciones restantes se comportan como si estuvieran dentro de un scriptlet estándar. Por ejemplo, el scriptlet <?= 'Hello, world!'; 'abc' ?> solo imprime"Hello, world!"

Secuencias de comandos de impresión forzada

Las secuencias de comandos de impresión forzada, que usan la sintaxis <?!= ... ?>, son como imprimir secuencias de comandos, excepto que evitan el escape contextual.

El escape contextual es importante si tu secuencia de comandos permite la entrada de usuarios no confiables. Por el contrario, deberás forzar la impresión si el resultado del scriptlet contiene HTML o secuencias de comandos que deseas insertar exactamente como se especifica.

Como regla general, usa impresoras en lugar de secuencias de comandos de impresión automática, a menos que sepas que debes imprimir HTML o JavaScript sin modificar.

Código de Apps Script en scriptslets

Las secuencias de comandos no están restringidas a la ejecución de JavaScript normal; también puedes usar cualquiera de las siguientes tres técnicas para dar a tus plantillas acceso a los datos de Apps Script.

Sin embargo, recuerda que, como el código de la plantilla se ejecuta antes de que se muestre la página al usuario, estas técnicas solo pueden alimentar el contenido inicial. Para acceder a los datos de Apps Script de una página de forma interactiva, usa la API de google.script.run en su lugar.

Llama a funciones de Apps Script desde una plantilla

Los scriptlets pueden llamar a cualquier función definida en un archivo de código o una biblioteca de Apps Script. En este ejemplo, se muestra una forma de extraer datos de una hoja de cálculo en una plantilla y, luego, construir una tabla HTML a partir de los datos.

Code.gs

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

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

Índice 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>

Llama a las API de Apps Script directamente

También puedes usar el código de Apps Script directamente en los scriptlets. Este ejemplo logra el mismo resultado que en el ejemplo anterior cuando carga los datos en la plantilla en sí, en lugar de hacerlo a través de una función separada.

Code.gs

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

Índice 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>

Envía variables a plantillas

Por último, puedes enviar variables a una plantilla asignándolas como propiedades del objeto HtmlTemplate. Una vez más, este ejemplo logra el mismo resultado que los ejemplos anteriores.

Code.gs

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

Índice 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>

Plantillas de depuración

Las plantillas pueden ser difíciles de depurar porque el código que escribes no se ejecuta directamente; en cambio, el servidor transforma tu plantilla en código y, luego, ejecuta el código resultante.

Si no es evidente cómo la plantilla interpreta tus scriptlets, hay dos métodos de depuración en la clase HtmlTemplate que pueden ayudarte a comprender mejor lo que sucede.

getCode().

getCode() muestra una string que contiene el código que crea el servidor a partir de la plantilla. Si registras el código y, luego, lo pegas en el editor de secuencias de comandos, puedes ejecutarlo y depurarlo como el código normal de Apps Script.

A continuación, te mostramos una plantilla simple que vuelve a mostrar una lista de productos de Google, seguida del resultado de getCode():

Code.gs

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

Índice 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 (EVALUADO)

(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('');
})();

getCodeWithComment().

getCodeWithComments() es similar a getCode(), pero muestra el código evaluado como comentarios que aparecen junto con la plantilla original.

Explicación del código evaluado

Lo primero que notarás en cualquiera de las muestras de código evaluado es el objeto output implícito que creó el método HtmlService.initTemplate(). Este método no está documentado porque solo las plantillas lo usan. output es un objeto HtmlOutput especial con dos propiedades inusualmente llamadas, _ y _$, que son las abreviaturas para llamar a append() y appendUntrusted().

output tiene una propiedad más, $out, que hace referencia a un objeto HtmlOutput común que no posee estas propiedades especiales. La plantilla muestra ese objeto normal al final del código.

Ahora que comprendes esta sintaxis, el resto del código debería ser bastante fácil de seguir. El contenido HTML fuera de los scriptlet (como la etiqueta b) se anexa mediante output._ = (sin escape escalonado), y los scriptlets se agregan como JavaScript (con o sin escape contextual, según el tipo de scriptlet).

Ten en cuenta que el código evaluado conserva los números de línea de la plantilla. Si recibes un error mientras ejecutas el código evaluado, la línea corresponderá al contenido equivalente en la plantilla.

Jerarquía de los comentarios

Debido a que el código evaluado conserva los números de línea, es posible que los comentarios dentro de los scriptslet comenten otros scripts y hasta el código HTML. En estos ejemplos, se muestran algunos efectos sorprendentes de los comentarios:

<? 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. */ ?>