Большинство дополнений на основе карточек построены на основе нескольких карточек , представляющих разные «страницы» интерфейса дополнения. Для эффективного взаимодействия с пользователем следует использовать простую и естественную навигацию между карточками в дополнении.
Первоначально в дополнениях Gmail переходы между различными карточками пользовательского интерфейса осуществлялись путем вставки и извлечения карточек из одной стопки, при этом верхняя карточка стопки отображалась Gmail.

Дополнения Google Workspace вводят домашние страницы и неконтекстные карточки. Для размещения контекстных и неконтекстных карточек дополнения Google Workspace имеют внутренний стек карточек для каждого из них. При открытии дополнения на хосте срабатывает соответствующий homepageTrigger , создавая первую карточку домашней страницы в стеке (тёмно-синяя карточка «homepage» на схеме ниже). Если триггер homepageTrigger не определён, создаётся, отображается и помещается в неконтекстный стек карточка по умолчанию. Эта первая карточка является корневой .
Ваше дополнение может создавать дополнительные неконтекстные карты и добавлять их в стопку (синие «выдвинутые карты» на схеме) по мере перемещения пользователя по дополнению. В интерфейсе дополнения отображается верхняя карта в стопке, поэтому добавление новых карт в стопку изменяет отображение, а извлечение карт из стопки возвращает к предыдущим картам.
Если в вашем дополнении определён контекстный триггер , триггер срабатывает при попадании пользователя в этот контекст. Функция триггера создаёт контекстную карточку, но отображение пользовательского интерфейса обновляется в соответствии с DisplayStyle новой карточки:
- Если
DisplayStyle—REPLACE(по умолчанию), контекстная карта (тёмно-оранжевая «контекстная» карта на схеме) заменяет текущую отображаемую карту. Это фактически создаёт новую стопку контекстных карт поверх стопки неконтекстных карт, и эта контекстная карта становится корневой картой в этой стопке. - Если
DisplayStyle—PEEK, пользовательский интерфейс вместо этого создаёт всплывающий заголовок, который отображается в нижней части боковой панели надстройки, накладывая его на текущую карточку. В этом заголовке отображается название новой карточки и предоставляются кнопки управления, позволяющие пользователю решить, просматривать новую карточку или нет. При нажатии кнопки « Просмотр » текущая карточка заменяется новой (как описано выше дляREPLACE).
Вы можете создавать дополнительные контекстные карточки и помещать их в стопку (жёлтые «выдвинутые карточки» на схеме). Обновление стопки карточек изменяет интерфейс дополнения, отображая самую верхнюю карточку. Если пользователь покидает контекст, контекстные карточки в стопке удаляются, и отображение обновляется до самой верхней неконтекстной карточки или домашней страницы.
Если пользователь попадает в контекст, для которого ваше дополнение не определяет контекстный триггер, новая карточка не создается, а текущая карточка остается отображенной.
Описанные ниже действия Navigation действуют только на карточки из одного и того же контекста. Например, popToRoot() из контекстной карточки извлекает только все остальные контекстные карточки и не влияет на карточки домашней страницы.
Напротив, кнопка всегда доступна пользователю для перехода от контекстных карточек к неконтекстным.
Методы навигации
Вы можете создавать переходы между карточками, добавляя или удаляя карточки из стопок. Класс Navigation предоставляет функции для перемещения и извлечения карточек из стопок. Для создания эффективной навигации по карточкам необходимо настроить виджеты на использование действий навигации. Вы можете перемещать или извлекать несколько карточек одновременно, но вы не можете удалить начальную карточку домашней страницы, которая первой помещается в стопку при запуске дополнения.
Чтобы перейти к новой карточке в ответ на взаимодействие пользователя с виджетом, выполните следующие действия:
- Создайте объект
Actionи свяжите его с определенной вами функцией обратного вызова . - Вызовите соответствующую функцию обработчика виджета , чтобы задать
Actionдля этого виджета. - Реализуйте функцию обратного вызова, которая осуществляет навигацию. Эта функция получает объект события действия в качестве аргумента и должна выполнять следующие действия:
- Создайте объект
Navigationдля определения изменения карты. Один объектNavigationможет содержать несколько этапов навигации, которые выполняются в порядке их добавления к объекту. - Создайте объект
ActionResponse, используя классActionResponseBuilderи объектNavigation. - Верните построенный
ActionResponse.
- Создайте объект
При построении элементов управления навигацией используются следующие функции объекта Navigation :
| Функция | Описание |
|---|---|
Navigation.pushCard(Card) | Добавляет карту в текущую стопку. Для этого необходимо сначала полностью собрать карту. |
Navigation.popCard() | Удаляет одну карту с верха колоды. Аналогично нажатию стрелки «назад» в строке заголовка дополнения. Это не удаляет корневые карты. |
Navigation.popToRoot() | Удаляет все карты из колоды, кроме корневой. По сути, сбрасывает колоду карт. |
Navigation.popToNamedCard(String) | Извлекает карты из стопки, пока не достигнет карты с заданным именем или корневой карты стопки. Вы можете присваивать картам имена с помощью функции CardBuilder.setName(String) . |
Navigation.updateCard(Card) | Выполняет замену текущей карты на месте, обновляя ее отображение в пользовательском интерфейсе. |
Лучшие практики навигации
Если взаимодействие пользователя или событие должно привести к повторной отрисовке карточек в том же контексте, используйте методы Navigation.pushCard() , Navigation.popCard() и Navigation.updateCard() для замены существующих карточек. Если взаимодействие пользователя или событие должно привести к повторной отрисовке карточек в другом контексте, используйте ActionResponseBuilder.setStateChanged() для принудительного повторного выполнения вашего дополнения в этих контекстах.
Ниже приведены примеры навигации:
- Если взаимодействие или событие изменяют состояние текущей карточки (например, добавление задачи в список задач), используйте
updateCard(). - Если взаимодействие или событие предоставляет дополнительную информацию или побуждает пользователя к дальнейшим действиям (например, щелкнуть заголовок элемента, чтобы увидеть дополнительные сведения, или нажать кнопку, чтобы создать новое событие в календаре), используйте
pushCard()чтобы отобразить новую страницу и разрешить пользователю выйти с новой страницы с помощью кнопки «Назад». - Если взаимодействие или событие обновляет состояние в предыдущей карточке (например, обновление заголовка элемента в подробном представлении), используйте что-то вроде
popCard(),popCard(),pushCard(previous)иpushCard(current)для обновления предыдущей карточки и текущей карточки.
Обновление карт
Дополнения Google Workspace позволяют пользователям обновлять вашу карточку, повторно запуская функцию-триггер Apps Script, зарегистрированную в вашем манифесте. Пользователи могут запустить это обновление через пункт меню дополнения:

Это действие автоматически добавляется к карточкам, сгенерированным функциями триггера homepageTrigger или contextualTrigger , как указано в файле манифеста вашего дополнения («корни» стеков контекстных и неконтекстных карточек).
Возврат нескольких карт
Функции домашней страницы или контекстного триггера используются для создания и возврата либо одного объекта Card , либо массива объектов Card , которые отображает пользовательский интерфейс приложения.
Если имеется только одна карта, она добавляется в неконтекстный или контекстный стек как корневая карта, и пользовательский интерфейс хост-приложения отображает ее.
Если возвращаемый массив содержит более одного созданного объекта Card , хостовое приложение отображает новую карточку, содержащую список заголовков каждой карточки. При щелчке по любому из этих заголовков пользовательский интерфейс отображает соответствующую карточку.
Когда пользователь выбирает карту из списка, она помещается в текущий стек, и хост-приложение отображает её. Кнопка возвращает пользователя к списку заголовков карт.
Такая «плоская» структура карточек может хорошо подойти, если вашему дополнению не требуются переходы между создаваемыми карточками. Однако в большинстве случаев лучше напрямую определять переходы между карточками, а функции главной страницы и контекстного триггера возвращать один объект карточки.
Пример
Вот пример, демонстрирующий, как создать несколько карточек с кнопками навигации для перехода между ними. Эти карточки можно добавлять как в контекстный, так и в неконтекстный стек, помещая карту, возвращаемую функцией createNavigationCard() в определённый контекст или за его пределы.
/**
* Create the top-level card, with buttons leading to each of three
* 'children' cards, as well as buttons to backtrack and return to the
* root card of the stack.
* @return {Card}
*/
function createNavigationCard() {
// Create a button set with actions to navigate to 3 different
// 'children' cards.
var buttonSet = CardService.newButtonSet();
for(var i = 1; i <= 3; i++) {
buttonSet.addButton(createToCardButton(i));
}
// Build the card with all the buttons (two rows)
var card = CardService.newCardBuilder()
.setHeader(CardService.newCardHeader().setTitle('Navigation'))
.addSection(CardService.newCardSection()
.addWidget(buttonSet)
.addWidget(buildPreviousAndRootButtonSet()));
return card.build();
}
/**
* Create a button that navigates to the specified child card.
* @return {TextButton}
*/
function createToCardButton(id) {
var action = CardService.newAction()
.setFunctionName('gotoChildCard')
.setParameters({'id': id.toString()});
var button = CardService.newTextButton()
.setText('Card ' + id)
.setOnClickAction(action);
return button;
}
/**
* Create a ButtonSet with two buttons: one that backtracks to the
* last card and another that returns to the original (root) card.
* @return {ButtonSet}
*/
function buildPreviousAndRootButtonSet() {
var previousButton = CardService.newTextButton()
.setText('Back')
.setOnClickAction(CardService.newAction()
.setFunctionName('gotoPreviousCard'));
var toRootButton = CardService.newTextButton()
.setText('To Root')
.setOnClickAction(CardService.newAction()
.setFunctionName('gotoRootCard'));
// Return a new ButtonSet containing these two buttons.
return CardService.newButtonSet()
.addButton(previousButton)
.addButton(toRootButton);
}
/**
* Create a child card, with buttons leading to each of the other
* child cards, and then navigate to it.
* @param {Object} e object containing the id of the card to build.
* @return {ActionResponse}
*/
function gotoChildCard(e) {
var id = parseInt(e.parameters.id); // Current card ID
var id2 = (id==3) ? 1 : id + 1; // 2nd card ID
var id3 = (id==1) ? 3 : id - 1; // 3rd card ID
var title = 'CARD ' + id;
// Create buttons that go to the other two child cards.
var buttonSet = CardService.newButtonSet()
.addButton(createToCardButton(id2))
.addButton(createToCardButton(id3));
// Build the child card.
var card = CardService.newCardBuilder()
.setHeader(CardService.newCardHeader().setTitle(title))
.addSection(CardService.newCardSection()
.addWidget(buttonSet)
.addWidget(buildPreviousAndRootButtonSet()))
.build();
// Create a Navigation object to push the card onto the stack.
// Return a built ActionResponse that uses the navigation object.
var nav = CardService.newNavigation().pushCard(card);
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
/**
* Pop a card from the stack.
* @return {ActionResponse}
*/
function gotoPreviousCard() {
var nav = CardService.newNavigation().popCard();
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
/**
* Return to the initial add-on card.
* @return {ActionResponse}
*/
function gotoRootCard() {
var nav = CardService.newNavigation().popToRoot();
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}