カード ナビゲーション

ほとんどのカードベースのアドオンは、アドオン インターフェースのさまざまな「ページ」を表す複数のカードを使用して構築されます。効果的なユーザー エクスペリエンスを実現するには、アドオン内のカード間のシンプルで自然なナビゲーションを使用する必要があります。

もともと Gmail のアドオンでは、UI の異なるカード間の遷移は、Gmail によって表示されるスタックの一番上のカードを使用して、1 つのカードスタックとの間でカードをプッシュおよびポップすることで処理されます。

ホームページ カード ナビゲーション

Google Workspace アドオンでは、ホームページとコンテキスト以外のカードが導入されます。コンテキスト カードとコンテキスト以外のカードに対応するため、Google Workspace アドオンにはそれぞれにカードカード スタックがあります。ホストでアドオンを開くと、対応する homepageTrigger が呼び出され、スタック上に最初のホームページ カードが作成されます(次の図の濃い青色の「ホームページ」カード)。homepageTrigger が定義されていない場合、デフォルト カードが作成され、表示され、コンテキスト以外のスタックに push されます。この最初のカードはルートカードです。

アドオンを介してユーザーが移動することで、コンテキスト以外の追加のカードがスタック(図の青色の「プッシュされたカード」)にプッシュされます。アドオン UI はスタックの一番上のカードを表示するため、新しいカードをスタックにプッシュするとディスプレイが変更され、スタックからカードをポップすると、ディスプレイは前のカードに戻ります。

アドオンにコンテキスト トリガーが定義されている場合、ユーザーがそのコンテキストに入るとトリガーが発動します。トリガー関数はコンテキスト カードを作成しますが、UI の表示は新しいカードの DisplayStyle に基づいて更新されます。

  • DisplayStyleREPLACE(デフォルト)の場合、現在表示されているカードがコンテキスト カード(図の濃いオレンジ色の「コンテキスト」カード)に置き換わります。これにより、コンテキスト以外のカードスタックの上に新しいコンテキスト カード スタックが実際に開始されます。このコンテキスト カードは、コンテキスト スタックのルートカードです。
  • DisplayStylePEEK の場合、代わりに UI は、アドオン サイドバーの下部に現在のカードをオーバーレイするピークヘッダーを作成します。ピークヘッダーには新しいカードのタイトルが表示され、新しいボタンを表示するかどうかを決定するユーザーボタン コントロールが提供されます。ユーザーが [表示] ボタンをクリックすると、現在のカードが上記の REPLACE に置き換えられます。

追加のコンテキスト カードを作成してスタックにプッシュできます(図の黄色い「プッシュされたカード」)。カードスタックを更新すると、アドオン UI が変更され、最上位のカードが表示されます。ユーザーがコンテキストを終了すると、スタック上のコンテキスト カードが削除され、最上位にコンテキストのない最上位のカードまたはホームページが表示されます。

アドオンでコンテキスト トリガーが定義されていないコンテキストをユーザーが入力すると、新しいカードは作成されず、現在のカードが引き続き表示されます。

以下で説明する Navigation アクションは、同じコンテキストのカードに対してのみ機能します。たとえば、コンテキスト カード内の popToRoot() は他のすべてのコンテキスト カードのみをポップし、ホームページ カードには影響しません。

一方、 ボタンを使用して、コンテキスト カードからコンテキスト以外のカードに移動できます。

カードスタックにカードを追加または削除することで、カード間の遷移を作成できます。Navigation クラスは、スタックからカードをプッシュおよびポップする関数を提供します。効果的なカード ナビゲーションを作成するには、ナビゲーション アクションを使用するようにウィジェットを構成します。複数のカードを同時に push またはポップすることもできますが、アドオンの開始時にスタックに最初に push される最初のホームページ カードを削除することはできません。

ユーザーによるウィジェットの操作に応じて新しいカードに移動する手順は次のとおりです。

  1. Action オブジェクトを作成し、定義したコールバック関数に関連付けます。
  2. ウィジェットの適切なウィジェット ハンドラ関数を呼び出して、そのウィジェットに Action を設定します。
  3. ナビゲーションを行うコールバック関数を実装します。この関数には引数としてアクション イベント オブジェクトを指定します。以下の処理を行う必要があります。
    1. Navigation オブジェクトを作成して、カードの変更を定義します。1 つの Navigation オブジェクトに複数のナビゲーション ステップを含めることができます。これらのステップは、オブジェクトに追加された順に行われます。
    2. ActionResponseBuilder クラスと Navigation オブジェクトを使用して、ActionResponse オブジェクトを作成します。
    3. ビルドされた ActionResponse を返します。

ナビゲーション コントロールを作成する際は、次の Navigation オブジェクト関数を使用します。

機能 説明
Navigation.pushCard(Card) 現在のスタックにカードを push します。まず、カードを完全に構築する必要があります。
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 トリガー関数を再実行することで、ユーザーがカードを更新できるようにします。この更新は、アドオン メニュー項目からトリガーされます。

Google Workspace アドオン サイドバー Google Workspace アドオン サイドバー





このアクションは、アドオンのマニフェスト ファイル(コンテキスト カード スタックと非コンテキスト カード スタックの「ルート」)で指定されているように、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();
  }