カードの操作

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

もともと Gmail のアドオンでは、UI の異なるカード間の移行は、単一のカードスタックとの間でカードのプッシュとポップを行うことで、スタックの一番上のカードが Gmail によって表示される形で処理されていました。

ホームページ カードの操作

Google Workspace アドオンには、ホームページと非コンテキスト カードが導入されています。コンテキスト カードと非コンテキスト カードに対応するため、Google Workspace アドオンにはそれぞれの内部カードスタックがあります。ホストでアドオンが開かれると、対応する homepageTrigger が起動し、スタック上に最初のホームページ カード(下の図の濃い青色の「ホームページ」カード)が作成されます。homepageTrigger が定義されていない場合、デフォルトのカードが作成され、表示されて、非コンテキスト スタックにプッシュされます。1 枚目のカードはルートカードです。

アドオンは、コンテキストに基づかない追加のカードを作成し、ユーザーがアドオン内を移動する際に、それをスタック(図の青色の「プッシュカード」)にプッシュできます。アドオン UI ではスタックの一番上のカードが表示されます。新しいカードをスタックにプッシュすると表示が変更され、カードをスタックからポップすると、表示が前のカードに戻ります。

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

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

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

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

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

一方、 ボタンは常に利用可能であり、ユーザーはコンテキスト カードから非コンテキスト カードに移動できます。

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

ウィジェットでのユーザー操作に応じて新しいカードに移動するには、次の手順を行います。

  1. Action オブジェクトを作成し、定義したコールバック関数に関連付けます。
  2. ウィジェットの適切なウィジェット ハンドラ関数を呼び出して、そのウィジェットに Action を設定します。
  3. ナビゲーションを行うコールバック関数を実装します。この関数は、アクション イベント オブジェクトを引数として受け取り、次の処理を行う必要があります。
    1. Navigation オブジェクトを作成して、カードの変更を定義します。1 つの Navigation オブジェクトに複数のナビゲーション ステップを含めることができます。各ステップは、オブジェクトに追加された順序で実行されます。
    2. ActionResponseBuilder クラスと Navigation オブジェクトを使用して、ActionResponse オブジェクトを作成します。
    3. ビルドされた 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 トリガー関数を再実行してカードを更新できます。ユーザーは、アドオン メニュー項目からこの更新をトリガーします。

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

このアクションは、アドオンのマニフェスト ファイル(コンテキストと非コンテキストのカードスタックの「ルート」)の指定に従って、homepageTrigger または contextualTrigger トリガー関数によって生成されたカードに自動的に追加されます。

複数のカードの返却

アドオンカードの例

ホームページまたはコンテキスト トリガー関数は、アプリの UI に表示される単一の Card オブジェクトまたは Card オブジェクトの配列をビルドして返すために使用されます。

カードが 1 つしかない場合は、非コンテキスト スタックまたはコンテキスト スタックにルートカードとして追加され、ホストアプリの UI に表示されます。

返された配列にビルド済みの Card オブジェクトが複数含まれている場合、ホストアプリは代わりに、各カードのヘッダーのリストを含む新しいカードを表示します。ユーザーがこれらのヘッダーのいずれかをクリックすると、対応するカードが UI に表示されます。

ユーザーがリストからカードを選択すると、そのカードは現在のスタックにプッシュされ、ホストアプリに表示されます。 ボタンをクリックすると、ユーザーはカードヘッダー リストに戻ります。

作成したカード間の遷移がアドオンで不要な場合は、この「フラット」なカード配置が適しています。ただし、ほとんどの場合は、カードの切り替えを直接定義し、ホームページとコンテキスト トリガー関数が単一のカード オブジェクトを返すようにすることをおすすめします。

以下は、カード間をジャンプするナビゲーション ボタンを備えた複数のカードの作成方法を示しています。これらのカードをコンテキスト スタックまたは非コンテキスト スタックに追加するには、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();
  }