ほとんどのカードベースのアドオンは、アドオン インターフェースのさまざまな「ページ」を表す複数のカードを使用して構築されています。効果的なユーザー エクスペリエンスを実現するには、アドオンのカード間でシンプルで自然なナビゲーションを使用する必要があります。
元々 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 トリガー関数によって生成されたカードに自動的に追加されます。
複数のカードを返す
ホームページまたはコンテキスト トリガー関数は、アプリケーション 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();
}