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