ほとんどのカードベースのアドオンは、アドオン インターフェースのさまざまな「ページ」を表す複数のカードを使用して作成されます。効果的なユーザー エクスペリエンスを実現するには、アドオン内のカード間をシンプルで自然に移動できるようにする必要があります。
元の Gmail アドオンでは、UI の異なるカード間の遷移は、単一のカードスタックとの間でカードをプッシュおよびポップすることで処理され、スタックの一番上のカードが Gmail によって表示されます。

Google Workspace アドオンに、ホームページとコンテキストに依存しないカードが導入されました。コンテキスト カードとコンテキスト外カードに対応するため、Google Workspace アドオンにはそれぞれ内部カードスタックがあります。ホストでアドオンが開かれると、対応する homepageTrigger がトリガーされ、スタック上の最初のホームページ カード(下の図の濃い青色の「ホームページ」カード)が作成されます。homepageTrigger が定義されていない場合、デフォルトのカードが作成され、表示されて、コンテキスト外のスタックにプッシュされます。この最初のカードはルート カードです。
ユーザーがアドオン内を移動すると、アドオンはコンテキストに依存しないカードを追加してスタックにプッシュできます(図の青い「プッシュされたカード」)。アドオン UI にはスタック内の一番上のカードが表示されるため、新しいカードをスタックにプッシュすると表示が変更され、カードをスタックからポップすると表示が前のカードに戻ります。
アドオンにコンテキスト トリガーが定義されている場合、ユーザーがそのコンテキストに入るとトリガーが起動します。トリガー関数はコンテキスト カードをビルドしますが、UI の表示は新しいカードの DisplayStyle に基づいて更新されます。
DisplayStyleがREPLACEの場合(デフォルト)、現在表示されているカードにコンテキスト カード(図の濃いオレンジ色の「コンテキスト」カード)が置き換えられます。これにより、コンテキスト外のカードスタックの上に新しいコンテキスト カードスタックが開始されます。このコンテキスト カードは、コンテキスト スタックのルート カードになります。DisplayStyleがPEEKの場合、UI は代わりに、アドオン サイドバーの下部に表示される、現在のカードをオーバーレイするピーキング ヘッダーを作成します。プレビュー ヘッダーには、新しいカードのタイトルが表示され、新しいカードを表示するかどうかをユーザーが決定できるユーザー ボタン コントロールが用意されています。[表示] ボタンをクリックすると、現在のカードがこのカードに置き換えられます(上記のREPLACEを参照)。
追加のコンテキスト カードを作成して、スタックにプッシュできます(図の黄色の「プッシュされたカード」)。カードスタックを更新すると、アドオン UI が変更され、一番上のカードが表示されます。ユーザーがコンテキストを離れると、グルーピングされたコンテキスト カードは削除され、表示はコンテキスト外の一番上のカードまたはホームページに更新されます。
ユーザーが、アドオンでコンテキスト トリガーが定義されていないコンテキストを入力した場合、新しいカードは作成されず、現在のカードが引き続き表示されます。
以下で説明する Navigation アクションは、同じコンテキストのカードにのみ作用します。たとえば、コンテキスト カード内からの popToRoot() は、他のすべてのコンテキスト カードをポップアップするだけで、ホームページ カードには影響しません。
一方、 ボタンは、ユーザーがコンテキストに応じたカードからコンテキストに応じないカードに移動するために常に使用できます。
ナビゲーション方法
カードのスタックにカードを追加または削除することで、カード間の遷移を作成できます。Navigation クラスには、スタックからカードを push および pop する関数があります。効果的なカード ナビゲーションを構築するには、ナビゲーション アクションを使用するようにウィジェットを構成します。複数のカードを一括でプッシュまたはポップできますが、アドオンの起動時に最初にスタックにプッシュされる最初のホームページ カードは削除できません。
ユーザーがウィジェットを操作したときに新しいカードに移動するには、次の手順を行います。
Actionオブジェクトを作成し、定義したコールバック関数に関連付けます。- ウィジェットの適切なウィジェット ハンドラ関数を呼び出して、そのウィジェットに 
Actionを設定します。 - ナビゲーションを実行するコールバック関数を実装します。この関数には、アクション イベント オブジェクトが引数として渡され、次のことを行う必要があります。
Navigationオブジェクトを作成して、カードの変更を定義します。1 つのNavigationオブジェクトに複数のナビゲーション ステップを含めることができます。これらのステップは、オブジェクトに追加された順序で実行されます。ActionResponseBuilderクラスとNavigationオブジェクトを使用して、ActionResponseオブジェクトを作成します。- ビルドされた 
ActionResponseを返します。 
 
ナビゲーション コントロールを作成する場合は、次の Navigation オブジェクト関数を使用します。
| 関数 | 説明 | 
|---|---|
Navigation.pushCard(Card) | 
現在のスタックにカードをプッシュします。そのためには、まずカードを完全に作成する必要があります。 | 
Navigation.popCard() | 
スタックの上から 1 枚のカードを削除します。アドオンのヘッダー行の戻る矢印をクリックするのと同じです。ルートカードは削除されません。 | 
Navigation.popToRoot() | 
ルートカードを除くすべてのカードがスタックから削除されます。カードのスタックをリセットします。 | 
Navigation.popToNamedCard(String) | 
指定された名前のカードまたはスタックのルートカードに到達するまで、スタックからカードをポップします。カードに名前を割り当てるには、CardBuilder.setName(String) 関数を使用します。 | 
Navigation.updateCard(Card) | 
現在のカードをその場で置き換え、UI での表示を更新します。 | 
ナビゲーションに関するベスト プラクティス
ユーザー操作やイベントによって同じコンテキストでカードを再レンダリングする必要がある場合は、Navigation.pushCard()、Navigation.popCard()、Navigation.updateCard() メソッドを使用して既存のカードに置き換えます。ユーザー操作またはイベントによって、別のコンテキストでカードが再レンダリングされる場合は、ActionResponseBuilder.setStateChanged() を使用して、それらのコンテキストでアドオンを強制的に再実行します。
ナビゲーションの例を次に示します。
- インタラクションまたはイベントによって現在のカードの状態が変更される場合(タスクリストにタスクを追加する場合など)は、
updateCard()を使用します。 - 操作やイベントによって詳細が提供されたり、ユーザーに追加の操作を求めたりする場合(アイテムのタイトルをクリックして詳細を表示する、ボタンを押して新しいカレンダー イベントを作成するなど)、
pushCard()を使用して新しいページを表示し、ユーザーが戻るボタンを使用して新しいページを終了できるようにします。 - インタラクションまたはイベントによって前のカードのステータスが更新された場合(詳細ビューからアイテムのタイトルを更新した場合など)、
popCard()、popCard()、pushCard(previous)、pushCard(current)などを使用して、前のカードと現在のカードを更新します。 
カードの更新
Google Workspace アドオンを使用すると、マニフェストに登録されている Apps Script トリガー関数を再実行してカードを更新できます。ユーザーは、アドオンのメニュー項目からこの更新をトリガーします。
このアクションは、アドオンのマニフェスト ファイル(コンテキスト依存カード スタックとコンテキスト非依存カード スタックの「ルート」)で指定されているように、homepageTrigger または contextualTrigger トリガー関数によって生成されたカードに自動的に追加されます。
複数のカード返品
ホームページ トリガー関数またはコンテキスト トリガー関数は、単一の Card オブジェクトまたは、アプリケーション UI に表示される Card オブジェクトの配列を構築して返すために使用されます。
カードが 1 つしかない場合、そのカードはルートカードとしてコンテキスト外またはコンテキスト内のスタックに追加され、ホスト アプリケーションの UI に表示されます。
返された配列に複数のビルド済み Card オブジェクトが含まれている場合、ホスト アプリケーションは、各カードのヘッダーのリストを含む新しいカードを表示します。ユーザーがこれらのヘッダーのいずれかをクリックすると、UI に対応するカードが表示されます。
ユーザーがリストからカードを選択すると、そのカードが現在のスタックにプッシュされ、ホスト アプリケーションに表示されます。 ボタンを押すと、カード ヘッダーのリストに戻ります。
この「フラット」なカードの配置は、作成したカード間の遷移がアドオンで必要ない場合に適しています。ただし、ほとんどの場合、カードの遷移を直接定義し、ホームページとコンテキスト トリガー関数で 1 つのカード オブジェクトを返すようにすることをおすすめします。
例
以下は、カード間を移動するナビゲーション ボタンを使用して複数のカードを作成する方法を示す例です。これらのカードは、特定のコンテキスト内またはコンテキスト外で 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();
  }