Card Navigation

Most Gmail add-ons are built using multiple cards. To have an effective user experience, you should use simple and natural navigation between cards in your add-on.

Basic navigation

Example add-on card

An add-on that uses a contextual trigger builds and returns an array of one or more Card objects that the Gmail UI displays. If there is only one card, Gmail displays it. Otherwise, Gmail auotmatically presets the card headers in a list when the add-on opens. When the user clicks any of those headers, Gmail displays the corresponding card.

When the user selects a card from the list, the UI switches to that card and provides a back arrow in the card's header row. Selecting this back arrow takes the user back to the card header list.

This 'flat' card arrangement works well if your add-on doesn't need any transitions between cards you create. In these cases, no extra work is necessary to create effective card navigation controls.

Card stack navigation

You can also create transitions between cards as you build your add-on's UI. The UI places the cards that are displayed in an internal stack that you can control. After a card is pushed or popped from the stack, the UI automatically displays the top card of the stack. It is possible to push or pop multiple cards simultaneously. The cards added when the add-on is first opened are the root cards. Root cards can't be removed.

The Navigation class provides functions to push and pop cards from the stack. To build effective card navigation, you configure your widgets to use navigation actions.

To navigate to a new card when a widget is selected, you must do the following:

  1. Create an Action object and associate it with a callback function you define.
  2. Call the widget's appropriate widget handler function to set the Action on that widget.
  3. Implement the callback function that conducts the navigation. This function is given an event object as an argument and must do the following:
    1. Create a Navigation object to define the card change. A single Navigation object can contain multiple navigation steps, which are conducted in the order they are added to the object.
    2. Build an ActionResponse object using the ActionResponseBuilder class and the Navigation object.
    3. Return the built ActionResponse.

When building navigation controls, you use the following functions:

Function Description
Navigation.pushCard(Card) Pushes a card onto the stack. This requires building the card completely first.
Navigation.popCard() Removes one card from the top of the stack. Equivalent of clicking the back arrow in the add-on header row.
Navigation.popToRoot() Removes all cards from the stack except for the root cards. Essentially resets the add-on to the same state it acquires when it first opens.
Navigation.popToNamedCard(String) Pops cards from the stack until it reaches a card with the given name. You can assign names to cards using the CardBuilder.setName(String) function.

Example

Here is an example that shows how to construct several cards with navigation buttons that jump between them.

  /**
   *  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();
  }