カードの操作

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

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

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

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

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

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

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

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

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

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

一方、 ボタンは、ユーザーは常にコンテキスト カードから非コンテキスト カードに移動するために使用できます。

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

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

  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 に表示されます。

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

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

次のサンプルは、カード間をジャンプするナビゲーション ボタンを備えた複数のカードを作成する方法を示しています。これらのカードは、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();
  }