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

Дополнения Google Workspace представляют собой домашние страницы и неконтекстуальные карточки. Для работы с контекстными и неконтекстуальными карточками в дополнениях Google Workspace используется внутренний стек карточек для каждого типа. При открытии дополнения на хосте срабатывает соответствующий homepageTrigger , который создает первую карточку домашней страницы в стеке (темно-синяя карточка «домашняя страница» на диаграмме ниже). Если триггер 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();
}