Служба HTML: HTML-шаблон

Вы можете смешивать код Apps Script и HTML для создания динамических страниц с минимальными усилиями. Если вы использовали язык шаблонов, сочетающий код и HTML, например PHP, ASP или JSP, синтаксис должен показаться вам знакомым.

Скриптлеты

Шаблоны сценариев приложений могут содержать три специальных тега, называемых скриптлетами. Внутри скриптлета вы можете написать любой код, который будет работать в обычном файле Apps Script: скриптлеты могут вызывать функции, определенные в других файлах кода, ссылаться на глобальные переменные или использовать любые API-интерфейсы Apps Script. Вы даже можете определять функции и переменные внутри скриптлетов, но с оговоркой, что они не могут быть вызваны функциями, определенными в файлах кода или других шаблонах.

Если вы вставите приведенный ниже пример в редактор сценариев, содержимое тега <?= ... ?> ( скриптлета печати ) будет выделено курсивом. Этот выделенный курсивом код выполняется на сервере до того, как страница будет передана пользователю . Поскольку код скриптлета выполняется до обслуживания страницы, он может выполняться только один раз на каждой странице; в отличие от клиентских функций JavaScript или Apps Script, которые вы вызываете через google.script.run , скриптлеты не могут выполняться повторно после загрузки страницы.

Код.gs

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

Индекс.html

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

Обратите внимание, что функция doGet() для шаблонного HTML отличается от примеров создания и обслуживания базового HTML . Показанная здесь функция генерирует объект HtmlTemplate из файла HTML, затем вызывает метод evaluate() для выполнения скриптлетов и преобразования шаблона в объект HtmlOutput , который скрипт может предоставить пользователю.

Стандартные скриптлеты

Стандартные скриптлеты, использующие синтаксис <? ... ?> , выполнить код без явного вывода содержимого на страницу. Однако, как показывает этот пример, результат выполнения кода внутри скриптлета может по-прежнему влиять на содержимое HTML за пределами скриптлета:

Код.gs

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

Индекс.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>

Печать скриптлетов

Печать скриптлетов, использующих синтаксис <?= ... ?> , выводит результаты своего кода на страницу с использованием контекстного экранирования.

Контекстное экранирование означает, что Apps Script отслеживает контекст вывода на странице — внутри атрибута HTML, внутри тега script на стороне клиента или где-либо еще — и автоматически добавляет escape-символы для защиты от атак межсайтового скриптинга (XSS) .

В этом примере первый сценарий печати выводит строку напрямую; за ним следует стандартный скриптлет, настраивающий массив и цикл, а затем еще один скриптлет печати для вывода содержимого массива.

Код.gs

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

Индекс.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>

Обратите внимание, что скриптлет печати выводит только значение своего первого оператора; любые оставшиеся операторы ведут себя так, как если бы они содержались в стандартном скриптлете. Так, например, скриптлет <?= 'Hello, world!'; 'abc' ?> печатает только «Привет, мир!»

Принудительная печать скриптлетов

Скриптлеты принудительной печати, использующие синтаксис <?!= ... ?> , аналогичны скриптлетам печати, за исключением того, что они избегают контекстного экранирования.

Контекстное экранирование важно, если ваш скрипт допускает ненадежный пользовательский ввод. Напротив, вам придется принудительно печатать, если выходные данные вашего скриптлета намеренно содержат HTML или скрипты, которые вы хотите вставить точно так, как указано.

Как правило, используйте скриптлеты для печати, а не для принудительной печати, если только вы не уверены, что вам нужно печатать HTML или JavaScript без изменений.

Код скрипта приложений в скриптлетах

Скриптлеты не ограничиваются выполнением обычного JavaScript; вы также можете использовать любой из следующих трех методов, чтобы предоставить вашим шаблонам доступ к данным Apps Script.

Однако помните, что, поскольку код шаблона выполняется до того, как страница будет предоставлена ​​пользователю, эти методы могут передавать на страницу только начальный контент. Чтобы получить интерактивный доступ к данным Apps Script со страницы, используйте вместо этого API google.script.run .

Вызов функций Apps Script из шаблона

Скриптлеты могут вызывать любую функцию, определенную в файле или библиотеке кода Apps Script. В этом примере показан один из способов извлечения данных из электронной таблицы в шаблон, а затем построения таблицы HTML на основе данных.

Код.gs

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

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

Индекс.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>

Вызов API-интерфейсов Apps Script напрямую

Вы также можете использовать код Apps Script непосредственно в скриптлетах. В этом примере достигается тот же результат, что и в предыдущем примере, за счет загрузки данных в сам шаблон, а не через отдельную функцию.

Код.gs

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

Индекс.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 . И снова этот пример дает тот же результат, что и предыдущие примеры.

Код.gs

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

Индекс.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() возвращает строку, содержащую код, который сервер создает из шаблона. Если вы зарегистрируете код, а затем вставите его в редактор сценариев, вы сможете запустить его и отладить, как обычный код сценария приложений.

Вот простой шаблон, который снова отображает список продуктов Google, за которым следует результат getCode() :

Код.gs

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

Индекс.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() , но возвращает оцененный код в виде комментариев, которые появляются рядом с исходным шаблоном.

Прогулка по оцениваемому коду

Первое, что вы заметите в любом примере оцениваемого кода, — это неявный output объект, созданный методом HtmlService.initTemplate() . Этот метод недокументирован, поскольку его нужно использовать только самим шаблонам. output — это специальный объект HtmlOutput с двумя свойствами с необычными именами: _ и _$ , которые являются сокращением для вызова метода append() и appendUntrusted() .

output есть еще одно специальное свойство, $out , которое ссылается на обычный объект HtmlOutput , не обладающий этими специальными свойствами. Шаблон возвращает этот обычный объект в конце кода.

Теперь, когда вы понимаете этот синтаксис, остальную часть кода будет довольно легко понять. HTML-содержимое вне скриптлетов (например, тег b ) добавляется с помощью output._ = (без контекстного экранирования ), а скриптлеты добавляются как JavaScript (с контекстным экранированием или без него, в зависимости от типа скриптлета).

Обратите внимание, что оцененный код сохраняет номера строк из шаблона. Если при выполнении оцененного кода вы получите ошибку, эта строка будет соответствовать эквивалентному содержимому в шаблоне.

Иерархия комментариев

Поскольку оцененный код сохраняет номера строк, комментарии внутри скриптлетов могут комментировать другие скриптлеты и даже 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. */ ?>