HTML 服务:模板化 HTML

您可以将 Apps 脚本代码和 HTML 混合在一起,以尽可能减少 努力。如果您使用将代码和 HTML 混合的模板语言(例如 PHP、ASP 或 JSP,那么语法应该很熟悉。

Scriptlet

Apps 脚本模板可以包含三个特殊标记,称为 scriptlet。内部 因此您可以编写任何可在普通 Apps 脚本中使用的代码 文件:scriptlet 可以调用其他代码文件中定义的函数、 全局变量,也可以使用任何 Apps Script API。您甚至可以定义 函数和变量,但请注意,不能 由代码文件或其他模板中定义的函数调用。

如果将下面的示例粘贴到脚本编辑器中, <?= ... ?> 标记(打印脚本)将出现在 斜体。在网页提供之前,在服务器上运行该斜体代码 。由于 scriptlet 代码是在加载网页之前执行的,因此它 每个网页只能运行一次;与客户端 JavaScript 或 Apps 脚本不同, 函数 google.script.run,scriptlet 不能 在网页加载后再次执行

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>

请注意,模板化 HTML 的 doGet() 函数与示例不同 了解创建和提供基本 HTML 的介绍。函数 生成了一个 HTML 中的 HtmlTemplate 对象 文件,然后调用其 evaluate() 方法 执行 scriptlet 并将模板转换为 HtmlOutput 对象,供脚本 可向该用户投放

标准 Scriptlet

使用 <? ... ?> 语法的标准脚本执行代码无需 以显式方式将内容输出到网页。不过,正如此示例所示, scriptlet 内代码的结果仍可能会影响 HTML 内容 在 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>

打印 Scriptlet

输出脚本(使用 <?= ... ?> 语法)会输出 通过内容相关转义将其代码嵌入到网页中

内容相关转义是指 Apps 脚本跟踪输出内容的上下文。 在网页上 - HTML 属性内、客户端 script 标记内,或者 ,并自动添加转义字符 跨站点脚本攻击 (XSS) 攻击

在此示例中,第一个输出 scriptlet 直接输出一个字符串;是 然后是设置数组和循环的标准脚本,最后是 另一个输出脚本脚本来输出数组的内容。

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>

请注意,输出 scriptlet 仅输出其第一个语句的值; 其余所有语句的行为方式和其包含在 脚本。例如,scriptlet <?= 'Hello, world!'; 'abc' ?> 只有 输出“Hello, world!”

强制打印 Scriptlet

强制打印的 scriptlet(使用语法<?!= ... ?>)与输出类似 但脚本可以避免上下文转义。

如果您的脚本允许不受信任的用户输入,请务必进行内容相关转义。修改者 相比之下,如果您的脚本输出是有意为之, 包含要完全按照指定方式插入的 HTML 或脚本。

一般而言,请使用输出脚本,而不是强制打印 Scriptlet 除非您知道自己需要按原样打印 HTML 或 JavaScript。

Scriptlet 中的 Apps 脚本代码

Scriptlet 不限于运行常规 JavaScript;您也可以使用任意 通过以下三种方法为模板提供对 Apps 脚本的访问权限 数据。

不过请注意,由于模板代码是在投放网页之前执行的, 因此这些技术只能将初始内容馈送到网页中。如何访问 使用 google.script.run API。

通过模板调用 Apps 脚本函数

Scriptlet 可以调用 Apps 脚本代码文件或库中定义的任何函数。 此示例展示了一种将数据从电子表格提取到模板的方法, 根据数据构建 HTML 表格。

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>

直接调用 Apps Script API

您也可以直接在脚本中使用 Apps 脚本代码。本示例 通过在 而不是通过单独的函数来实现

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>

将变量推送到模板

最后,您可以将变量作为属性分配,从而将变量推送到模板中 HtmlTemplate 对象的 ID。一次 但这个示例实现的结果与前面的示例相同。

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>

调试模板

调试模板可能比较困难,因为您编写的代码不会执行 ;而是会将模板转换为代码,然后执行 生成的代码

如果模板对脚本的解读方式不够明显,可以添加两个 调试方法 HtmlTemplate 类可以帮助您 从而更好地了解发生的情况

getCode()

getCode() 会返回一个 包含服务器通过模板创建的代码的字符串。如果您 记录 然后将其粘贴到脚本编辑器中,就可以运行它并 像往常一样进行调试 Apps 脚本代码。

下面是一个简单模板,再次显示 Google 产品列表, 然后是 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>

日志(已评估)

(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()getCode() 类似,但会将已评估的代码作为注释返回: 与原始模板并排显示。

浏览已评估的代码

在两个已评估的代码示例中,您首先会注意到的是 通过 HtmlService.initTemplate() 方法创建的 output 对象。此方法 因为只有模板本身才需要使用它,所以没有文档记录。output是 特殊的 HtmlOutput 对象,其中包含两个 __$ 等命名异常的属性,它们是调用 append()appendUntrusted()

output 还有一个特殊属性 $out,它指的是常规 的 HtmlOutput 对象。模板 会在代码末尾返回常规对象。

现在,您已经了解了此语法,其余代码应该非常简单 。附加了 scriptlet 之外的 HTML 内容(例如 b 标记) 使用 output._ =(不进行上下文转义), 和 scriptlet 会被附加为 JavaScript(无论是否有上下文转义, 具体取决于脚本的类型)。

请注意,评估的代码会保留模板中的行号。如果您 在运行已评估的代码时出错,该行将与 等同的内容。

注释的层次结构

由于经过评估的代码会保留行号,因此可以对 以注释掉其他 scriptlet 甚至 HTML 代码。这些 以下示例展示了评论的一些令人惊讶的效果:

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