Google Apps Script: Передача параметров в HTML-шаблоны

Передача данных между серверной частью Google Apps Script (.gs файлы) и клиентским интерфейсом (.html файлы) является фундаментальной задачей при создании веб-приложений, надстроек для Документов, Таблиц, Форм или при отображении пользовательских интерфейсов в боковых панелях и диалоговых окнах.

Что такое HTML-шаблоны в Google Apps Script и зачем они нужны?

HTML-шаблоны в Google Apps Script — это HTML-файлы, которые могут содержать специальный синтаксис, называемый scriptlets. Сервис HtmlService позволяет обрабатывать эти шаблоны на сервере, исполнять встроенный код Apps Script (внутри scriptlets) и передавать данные из вашего .gs кода в HTML перед отправкой его клиенту. Это позволяет динамически генерировать HTML-страницы с контентом, зависящим от данных вашего скрипта, результатов вычислений или информации из сервисов Google Workspace.

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

Обзор способов передачи данных из Apps Script в HTML

Существует два основных механизма передачи данных из серверного кода Apps Script в HTML-шаблон:

  1. Прямая передача через scriptlets: Данные присваиваются свойствам объекта шаблона в .gs файле и затем доступны внутри HTML с использованием синтаксиса <%= ... %>. Этот метод синхронный и выполняется на сервере перед отправкой HTML клиенту.
  2. Асинхронная передача через google.script.run: Клиентский JavaScript в HTML-файле вызывает функции серверного .gs кода с помощью google.script.run. Данные передаются асинхронно после загрузки страницы, что обеспечивает более гибкое и интерактивное взаимодействие.

Предварительные требования: настройка проекта Apps Script и HTML-файла

Для работы с передачей параметров вам понадобится:

  1. Проект Google Apps Script (привязанный к Таблице, Документу или автономный).
  2. Как минимум один .gs файл для серверной логики.
  3. Как минимум один .html файл, который будет служить шаблоном для пользовательского интерфейса.

Пример базовой структуры:Code.gs:

/**
 * Отображает пользовательский интерфейс из HTML-файла.
 *
 * @param {string} userName Имя пользователя для отображения.
 * @param {boolean} isAdmin Флаг администратора.
 * @returns {HtmlOutput} Сгенерированный HTML-объект.
 */
function showSidebar(userName: string = 'Гость', isAdmin: boolean = false): GoogleAppsScript.HTML.HtmlOutput {
  const template: GoogleAppsScript.HTML.HtmlTemplate = HtmlService.createTemplateFromFile('Sidebar');
  // Прямая передача данных для scriptlets
  template.userName = userName;
  template.isAdmin = isAdmin;
  return template.evaluate()
    .setTitle('Моя боковая панель')
    .setWidth(300);
}

// Пример функции для вызова из Spreadsheet
function onOpen(): void {
  SpreadsheetApp.getUi()
      .createMenu('Мое меню')
      .addItem('Показать панель', 'callShowSidebar')
      .addToUi();
}

// Промежуточная функция для вызова showSidebar без параметров из меню
function callShowSidebar(): void {
  // Здесь можно получить реальные данные пользователя или другие параметры
  const currentUserEmail: string = Session.getActiveUser().getEmail();
  const isAdminUser: boolean = currentUserEmail === 'admin@example.com'; // Пример проверки
  SpreadsheetApp.getUi().showSidebar(showSidebar(currentUserEmail, isAdminUser));
}

Sidebar.html:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>Приветствие</h1>
    <p>Загрузка данных...</p>
    <!-- Место для отображения данных -->
    <div id="userInfo"></div>
    <div id="content"></div>

    <?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); /* Подключение CSS */ ?>
    <?!= HtmlService.createHtmlOutputFromFile('JavaScript').getContent(); /* Подключение JS */ ?>
  </body>
</html>

Простой способ: использование scriptlets для прямой передачи параметров

Scriptlets — это специальные теги внутри HTML-шаблона, которые позволяют выполнять код Apps Script на стороне сервера во время генерации HTML.

Синтаксис scriptlets: <%= ... %> и <? ... ?>

Существует три типа scriptlets:

  1. Стандартные scriptlets <? ... ?>: Выполняют код Apps Script внутри тегов. Используются для управляющих структур (циклы, условия), вызова функций, но не выводят результат напрямую в HTML.
  2. Печатающие scriptlets <%= ... %>: Выполняют выражение Apps Script внутри тегов и выводят его результат в HTML. Важно: результат автоматически экранируется для предотвращения XSS-атак.
  3. Печатающие scriptlets без экранирования <?!= ... ?>: Выполняют выражение Apps Script и выводят его сырой результат в HTML. Используйте с осторожностью, только если вы уверены в безопасности выводимых данных (например, для вставки другого HTML-содержимого, сгенерированного HtmlService).

Примеры передачи строк, чисел и булевых значений

Дополним Code.gs передачей данных:

// ... внутри функции showSidebar ...
  const template: GoogleAppsScript.HTML.HtmlTemplate = HtmlService.createTemplateFromFile('Sidebar');
  template.userName = userName; // string
  template.userPoints = 150; // number
  template.isAdmin = isAdmin; // boolean
  template.tasks = ['Задача 1', 'Задача 2', 'Завершить проект']; // array
  // ... return template.evaluate() ...

Теперь в Sidebar.html можно использовать эти данные:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>Привет, <%= userName %>!</h1>
    <p>У вас <%= userPoints %> баллов.</p>

    <? if (isAdmin) { ?>
      <p>Вы вошли как администратор.</p>
      <button>Админ-панель</button>
    <? } else { ?>
      <p>У вас нет прав администратора.</p>
    <? } ?>

    <h2>Ваши задачи:</h2>
    <ul>
      <? for (let i = 0; i < tasks.length; i++) { ?>
        <li><%= tasks[i] %></li>
      <? } ?>
    </ul>

    <!-- Пример использования <?!= ... ?> для подключения CSS/JS -->
    <?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
    <?!= HtmlService.createHtmlOutputFromFile('JavaScript').getContent(); ?>
  </body>
</html>

Ограничения и рекомендации по использованию scriptlets

  • Синхронность: Весь код в scriptlets выполняется на сервере до отправки HTML клиенту. Это может замедлить первоначальную загрузку интерфейса, если вычисления сложны.
  • Смешивание логики: Использование сложной логики внутри HTML затрудняет чтение и поддержку кода.
  • Безопасность: Будьте предельно осторожны с <?!= ... ?>. Никогда не используйте его для вывода данных, введенных пользователем, без предварительной санации.

Scriptlets хорошо подходят для инициализации страницы статическими или редко изменяющимися данными, определения структуры страницы на основе прав доступа и т.п.

Реклама

Рекомендуемый способ: использование google.script.run для асинхронной передачи данных

google.script.run — это асинхронный JavaScript API, который позволяет клиентскому коду в HTML вызывать серверные функции Apps Script.

Принцип работы google.script.run

  1. Клиент (HTML): Вызывает google.script.run с указанием имени серверной функции и необязательными параметрами.
  2. Сервер (Apps Script): Выполняет указанную функцию.
  3. Клиент (HTML): Опционально получает результат выполнения через функции обратного вызова (success/failure handlers).

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

Передача данных из Apps Script в HTML с помощью функций обратного вызова

Добавим серверную функцию для получения данных и клиентский код для их запроса.

Code.gs:

/**
 * Получает данные о кампаниях из Google Ads (пример).
 *
 * @returns {object[]} Массив объектов с данными кампаний.
 * @throws {Error} Если произошла ошибка при получении данных.
 */
function getCampaignData(): { name: string; clicks: number; cost: number }[] {
  // Здесь мог бы быть вызов к API Google Ads или чтение из Таблицы
  // Имитация данных для примера:
  try {
    Utilities.sleep(1500); // Имитация задержки сети/API
    const campaigns = [
      { name: 'Поиск - Бренд', clicks: 1500, cost: 300.50 },
      { name: 'КМС - Ремаркетинг', clicks: 12000, cost: 450.75 },
      { name: 'Поиск - Общие запросы', clicks: 2100, cost: 620.00 }
    ];
    // Имитация возможной ошибки
    // if (Math.random() > 0.7) {
    //   throw new Error('Не удалось подключиться к API Google Ads');
    // }
    return campaigns;
  } catch (e) {
    console.error('Ошибка в getCampaignData: ' + e);
    throw new Error('Произошла ошибка при загрузке данных кампаний.'); // Пробрасываем user-friendly ошибку
  }
}

JavaScript.html (подключается в Sidebar.html через <?!= ... ?>):

<script>
  /**
   * Обработчик успешного получения данных кампаний.
   * @param {object[]} campaigns Массив объектов кампаний.
   */
  function onSuccess(campaigns) {
    const contentDiv = document.getElementById('content');
    contentDiv.innerHTML = ''; // Очистка предыдущего содержимого

    if (!campaigns || campaigns.length === 0) {
      contentDiv.innerHTML = '<p>Нет данных о кампаниях.</p>';
      return;
    }

    let tableHtml = '<table><thead><tr><th>Название</th><th>Клики</th><th>Расход</th></tr></thead><tbody>';
    campaigns.forEach(campaign => {
      tableHtml += `<tr><td>${escapeHtml(campaign.name)}</td><td>${campaign.clicks}</td><td>${campaign.cost.toFixed(2)}</td></tr>`;
    });
    tableHtml += '</tbody></table>';
    contentDiv.innerHTML = tableHtml;
  }

  /**
   * Обработчик ошибки при получении данных.
   * @param {Error} error Объект ошибки.
   */
  function onFailure(error) {
    const contentDiv = document.getElementById('content');
    contentDiv.innerHTML = `<p style="color: red;">Ошибка: ${escapeHtml(error.message)}</p>`;
    console.error('Ошибка вызова google.script.run: ', error);
  }

  /**
   * Вспомогательная функция для экранирования HTML.
   * @param {string} unsafe Строка для экранирования.
   * @returns {string} Экранированная строка.
   */
  function escapeHtml(unsafe) {
    return unsafe
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "'");
  }

  // Запрос данных при загрузке страницы
  document.addEventListener('DOMContentLoaded', function() {
    const loadingP = document.querySelector('p'); // Убираем 'Загрузка данных...'
    if (loadingP && loadingP.textContent.includes('Загрузка')) {
       loadingP.style.display = 'none';
    }
    google.script.run
      .withSuccessHandler(onSuccess)
      .withFailureHandler(onFailure)
      .getCampaignData();
  });

</script>

Stylesheet.html (подключается в Sidebar.html):

<style>
  body { font-family: sans-serif; margin: 10px; }
  table { width: 100%; border-collapse: collapse; margin-top: 10px; }
  th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
  th { background-color: #f2f2f2; }
  button { padding: 8px 15px; margin-top: 10px; cursor: pointer; }
</style>

Обработка ошибок и отображение сообщений пользователю

Как видно из примера выше, google.script.run предоставляет метод .withFailureHandler(callbackFunction).

  • Серверная сторона: Ловите ошибки с помощью try...catch и выбрасывайте new Error('Понятное сообщение для пользователя'). Можно логировать детали ошибки с помощью console.error().
  • Клиентская сторона: В функции-обработчике ошибки (onFailure в примере) принимайте объект ошибки и отображайте его message свойство пользователю. Также логируйте полный объект ошибки в консоль браузера для отладки.

Передача сложных объектов (массивов, объектов) и их обработка в HTML

google.script.run автоматически сериализует и десериализует сложные объекты (массивы, объекты, содержащие примитивные типы, даты, другие массивы/объекты). Вам не нужно вручную использовать JSON.stringify или JSON.parse.

  • Сервер: Просто возвращайте массив или объект из вашей функции (getCampaignData возвращает object[]).
  • Клиент: В onSuccess вы получите JavaScript-массив или объект, с которым можно работать стандартными методами (циклы forEach, доступ к свойствам и т.д.).

Важно: Нельзя передавать функции или объекты с циклическими ссылками.

Продвинутые техники передачи параметров

Использование шаблонов Handlebars для более сложной логики в HTML

Хотя стандартные scriptlets позволяют выполнять базовую логику, для сложных интерфейсов они могут стать громоздкими. Вы можете интегрировать сторонние JavaScript-библиотеки для шаблонизации на клиенте, такие как Handlebars.js.

  1. Подключение: Добавьте код библиотеки Handlebars.js в ваш HTML (например, через CDN или в отдельном .html файле, подключаемом через <?!= ... ?>).
  2. Сервер: Ваша серверная функция (.gs) через google.script.run возвращает только данные (например, JSON-объект).
  3. Клиент:
    • Определите шаблон Handlebars в теге `<script type=

Добавить комментарий