ほとんどのカードベースのアドオンは、アドオン インターフェースのさまざまな「ページ」を表す複数のカードを使用して構築されています。効果的なユーザー エクスペリエンスを実現するには、アドオンのカード間のナビゲーションをシンプルで自然なものにする必要があります。
元々 Gmail アドオンでは、UI のカード間の遷移は、1 つのカードスタックとの間でカードのプッシュとポップを行い、Gmail によってスタックの一番上のカードが表示されることで処理されていました。
Google Workspace アドオンには、ホームページと非コンテキスト カードが導入されています。コンテキスト カードと非コンテキスト カードに対応するため、Google Workspace アドオンにはそれぞれに内部カードスタックがあります。ホストでアドオンを開くと、対応する homepageTrigger
が起動され、スタック上に最初のホームページ カード(下の図の濃い青色の「ホームページ」カード)が作成されます。homepageTrigger
が定義されていない場合は、デフォルトのカードが作成され、表示されて、非コンテキスト スタックに push されます。最初のカードは、ルートカードです。
アドオンは、ユーザーがアドオンを操作するときにコンテキストに依存しない追加のカードを作成し、スタック(図の青色の「プッシュされたカード」)にプッシュできます。アドオン UI にはスタックの一番上のカードが表示されるため、新しいカードをスタックにプッシュすると表示が変更され、スタックからカードをポップすると前のカードに戻ります。
アドオンにコンテキスト トリガーが定義されている場合、ユーザーがそのコンテキストを入力すると、トリガーが起動します。トリガー関数はコンテキスト カードを作成しますが、UI 表示は新しいカードの DisplayStyle
に基づいて更新されます。
DisplayStyle
がREPLACE
(デフォルト)の場合、現在表示されているカードはコンテキスト カード(図の濃いオレンジ色の「コンテキスト」カード)に置き換えられます。これにより、非コンテキスト カードスタックの上に新しいコンテキスト カードスタックが実質的に開始されます。このコンテキスト カードは、コンテキスト スタックのルートカードです。DisplayStyle
がPEEK
の場合、UI は代わりにアドオン サイドバーの下部に、現在のカードにオーバーレイして表示されるポップアップ ヘッダーを作成します。ピークヘッダーには新しいカードのタイトルが表示され、新しいカードを表示するかどうかをユーザーが決定できるボタン コントロールが提供されます。[View] ボタンをクリックすると、現在のカードがREPLACE
に置き換えられます。
コンテキストに応じた追加のカードを作成して、スタックにプッシュできます(図の黄色の「プッシュされたカード」)。カードスタックを更新すると、アドオン UI が変更され、一番上のカードが表示されます。ユーザーがコンテキストから離れると、スタック上のコンテキスト カードは削除され、ディスプレイは最上位の非コンテキスト カードまたはホームページに更新されます。
アドオンがコンテキスト トリガーを定義していないコンテキストをユーザーが入力した場合、新しいカードは作成されず、現在のカードは表示されたままになります。
以下に説明する Navigation
アクションは、同じコンテキストのカードに対してのみ機能します。たとえば、コンテキスト カード内の popToRoot()
は、他のすべてのコンテキスト カードにのみポップされ、ホームページ カードには影響しません。
一方、
ボタンは、ユーザーは常にコンテキスト カードから非コンテキスト カードに移動するために使用できます。ナビゲーション メソッド
カードスタックにカードを追加または削除することで、カード間の移行を作成できます。Navigation
クラスには、スタックからカードのプッシュとポップを行う関数が用意されています。効果的なカード ナビゲーションを作成するには、ナビゲーション アクションを使用するようにウィジェットを構成します。複数のカードを同時にプッシュまたはポップできますが、アドオンの起動時に最初にスタックにプッシュされた最初のホームページ カードは削除できません。
ユーザーによるウィジェットの操作に応じて新しいカードに移動する手順は次のとおりです。
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 に表示されます。
ユーザーがリストからカードを選択すると、そのカードが現在のスタックに 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();
}