Карточная навигация

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

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

Навигация по карточкам главной страницы

Дополнения Google Workspace представляют домашние страницы и неконтекстные карточки. Для размещения контекстных и неконтекстных карточек в надстройках Google Workspace предусмотрена внутренняя стопка карточек для каждой из них. Когда надстройка открывается на хосте, соответствующий homepageTrigger срабатывает для создания первой карточки домашней страницы в стеке (темно-синяя карточка «домашней страницы» на диаграмме ниже). Если homepageTrigger не определен, карточка по умолчанию создается, отображается и помещается в неконтекстный стек. Эта первая карта является корневой картой.

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

Если в вашей надстройке есть определенный контекстный триггер , когда пользователь входит в этот контекст, триггер срабатывает. Функция триггера создает контекстную карту, но отображение пользовательского интерфейса обновляется на основе DisplayStyle новой карты:

  • Если DisplayStyle имеет значение REPLACE (по умолчанию), контекстная карточка (темно-оранжевая «контекстная» карточка на схеме) заменяет отображаемую в данный момент карточку. Это фактически запускает новую стопку контекстных карт поверх стопки неконтекстных карт, и эта контекстная карта является корневой картой контекстной стопки.
  • Если DisplayStyle имеет значение PEEK , пользовательский интерфейс вместо этого создает просматриваемый заголовок, который отображается в нижней части боковой панели надстройки, перекрывая текущую карточку. Заголовок просмотра показывает заголовок новой карточки и предоставляет пользователю кнопки управления, которые позволяют ему решить, просматривать новую карточку или нет. Если они нажмут кнопку «Просмотр» , карта заменит текущую карту (как описано выше с помощью REPLACE ).

Вы можете создать дополнительные контекстные карты и положить их в стопку (желтые «толкаемые карты» на диаграмме). Обновление стопки карточек изменяет пользовательский интерфейс надстройки, чтобы отображать самую верхнюю карту. Если пользователь покидает контекст, контекстные карточки в стопке удаляются, а на дисплее отображается самая верхняя неконтекстная карточка или домашняя страница.

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

Действия Navigation , описанные ниже, действуют только на карточки из того же контекста; например, popToRoot() из контекстной карточки извлекает только все остальные контекстные карточки и не влияет на карточки домашней страницы.

Напротив, всегда доступна пользователю для перехода от контекстных карточек к неконтекстным карточкам.

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

Чтобы перейти к новой карточке в ответ на взаимодействие пользователя с виджетом, выполните следующие действия:

  1. Создайте объект Action и свяжите его с определяемой вами функцией обратного вызова .
  2. Вызовите соответствующую функцию обработчика виджета , чтобы установить Action для этого виджета.
  3. Реализуйте функцию обратного вызова, которая осуществляет навигацию. Этой функции в качестве аргумента передается объект события действия , и она должна выполнять следующие действия:
    1. Создайте объект Navigation , чтобы определить изменение карты. Один объект Navigation может содержать несколько шагов навигации, которые выполняются в порядке их добавления к объекту.
    2. Создайте объект ActionResponse , используя класс ActionResponseBuilder и объект Navigation .
    3. Верните построенный 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, зарегистрированную в вашем манифесте. Пользователи запускают это обновление через дополнительный пункт меню:

Боковая панель дополнения Google Workspace

Это действие автоматически добавляется к карточкам, созданным триггерными функциями 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();
  }