Передача данных между серверной частью 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-шаблон:
- Прямая передача через scriptlets: Данные присваиваются свойствам объекта шаблона в
.gsфайле и затем доступны внутри HTML с использованием синтаксиса<%= ... %>. Этот метод синхронный и выполняется на сервере перед отправкой HTML клиенту. - Асинхронная передача через
google.script.run: Клиентский JavaScript в HTML-файле вызывает функции серверного.gsкода с помощьюgoogle.script.run. Данные передаются асинхронно после загрузки страницы, что обеспечивает более гибкое и интерактивное взаимодействие.
Предварительные требования: настройка проекта Apps Script и HTML-файла
Для работы с передачей параметров вам понадобится:
- Проект Google Apps Script (привязанный к Таблице, Документу или автономный).
- Как минимум один
.gsфайл для серверной логики. - Как минимум один
.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:
- Стандартные scriptlets
<? ... ?>: Выполняют код Apps Script внутри тегов. Используются для управляющих структур (циклы, условия), вызова функций, но не выводят результат напрямую в HTML. - Печатающие scriptlets
<%= ... %>: Выполняют выражение Apps Script внутри тегов и выводят его результат в HTML. Важно: результат автоматически экранируется для предотвращения XSS-атак. - Печатающие 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
- Клиент (HTML): Вызывает
google.script.runс указанием имени серверной функции и необязательными параметрами. - Сервер (Apps Script): Выполняет указанную функцию.
- Клиент (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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.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.
- Подключение: Добавьте код библиотеки Handlebars.js в ваш HTML (например, через CDN или в отдельном
.htmlфайле, подключаемом через<?!= ... ?>). - Сервер: Ваша серверная функция (
.gs) черезgoogle.script.runвозвращает только данные (например, JSON-объект). - Клиент:
- Определите шаблон Handlebars в теге `<script type=