インタラクティブな UI 要素をカードに追加する

このページでは、ユーザーがボタンをクリックしたり、情報を送信したりするなど、Google Chat アプリを操作できるように、カードにウィジェットと UI 要素を追加する方法について説明します。

Chat アプリでは、次の Chat インターフェースを使用してインタラクティブなカードを作成できます。

  • メッセージ - 1 枚以上のカードが含まれます。
  • ホームページ。Chat 用アプリとのダイレクト メッセージで [ホーム] タブに表示されるカードです。
  • ダイアログ: メッセージやホームページから新しいウィンドウで開くカードです。

ユーザーがカードを操作すると、Chat アプリは受信したデータを処理し、それに応じて応答できます。詳しくは、Google Chat ユーザーから情報を収集して処理するをご覧ください。


カードビルダーを使用して、Chat 用アプリのメッセージとユーザー インターフェースを設計し、プレビューします。

カードビルダーを開く

前提条件

インタラクティブ機能が有効になっている Google Chat アプリ。インタラクティブな Chat アプリを作成するには、使用するアプリのアーキテクチャに基づいて、次のいずれかのクイックスタートを完了します。

ボタンを追加する

ButtonList ウィジェットには一連のボタンが表示されます。ボタンには、テキスト、アイコン、またはテキストとアイコンの両方を表示できます。各 Button は、ユーザーがボタンをクリックしたときに発生する OnClick アクションをサポートしています。例:

  • ユーザーに追加情報を提供するために、OpenLink でハイパーリンクを開きます。
  • API の呼び出しなどのカスタム関数を実行する action を実行します。

ユーザー補助機能については、ボタンが代替テキストをサポートしています。

カスタム関数を実行するボタンを追加する

次のカードは、2 つのボタンを持つ ButtonList ウィジェットで構成されています。1 つのボタンで、Google Chat デベロッパー向けドキュメントが新しいタブで開きます。もう一方のボタンは、goToView() というカスタム関数を実行し、viewType="BIRD EYE VIEW" パラメータを渡します。

マテリアル デザイン スタイルのボタンを追加する

以下の例では、マテリアル デザインのさまざまなスタイルのボタンを表示します。

マテリアル デザイン スタイルを適用するには、[color] 属性を含めないでください。

カスタム色のボタンと無効なボタンを追加する

"disabled": "true" を設定すると、ユーザーがボタンをクリックできないようにすることができます。

次のコードは、2 つのボタンを持つ ButtonList ウィジェットからなるカードを表示します。1 つのボタンは、Color フィールドを使用してボタンの背景色をカスタマイズします。他のボタンは Disabled フィールドで無効になります。これにより、ユーザーはボタンをクリックして機能を実行できなくなります。

アイコン付きのボタンを追加する

以下は、2 つのアイコン Button ウィジェットを持つ ButtonList ウィジェットで構成されるカードの例です。1 つのボタンは、knownIcon フィールドを使用して Google Chat の組み込みメールアイコンを表示します。もう 1 つのボタンは、iconUrl フィールドを使用してカスタム アイコン ウィジェットを表示します。

アイコンとテキストを含むボタンを追加する

以下は、メールを送信するようユーザーに求める ButtonList ウィジェットを含むカードを示しています。最初のボタンにはメールアイコン、2 番目のボタンにはテキストが表示されます。ユーザーは、アイコンまたはテキスト ボタンをクリックして sendEmail 関数を実行できます。

折りたたみ可能なセクションのボタンをカスタマイズする

カード内のセクションの折りたたみ / 展開を行うコントロール ボタンをカスタマイズします。さまざまなアイコンや画像から選択して、セクションの内容を視覚的に表現し、ユーザーが情報を簡単に理解して操作できるようにします。

オーバーフロー メニューの追加

Chat カードで Overflow menu を使用すると、追加のオプションやアクションを提供できます。カードのインターフェースを乱すことなく、より多くのオプションを含めることができ、すっきりと整理されたデザインを実現できます。

チップリストを追加する

ChipList ウィジェットは、情報を表示するための汎用性と視覚的な魅力を備えています。チップリストを使用してタグ、カテゴリ、その他の関連データを表すと、ユーザーがコンテンツを容易に操作して操作できるようになります。

ユーザーから情報を収集する

このセクションでは、テキストや選択内容などの情報を収集するウィジェットを追加する方法について説明します。

ユーザーが入力した内容を処理する方法については、Google Chat ユーザーから情報を収集して処理するをご覧ください。

テキストを収集する

TextInput ウィジェットには、ユーザーがテキストを入力できるフィールドがあります。このウィジェットは、ユーザーが均一なデータを入力するのに役立つ提案と、テキスト入力フィールドに変更(テキストの追加や削除など)が発生したときに実行される Actions である変更時のアクションをサポートしています。

抽象的なデータや未知のデータをユーザーから収集する必要がある場合は、この TextInput ウィジェットを使用します。ユーザーから定義済みデータを収集するには、代わりに SelectionInput ウィジェットを使用してください。

TextInput ウィジェットで構成されるカードは次のとおりです。

日時を収集する

DateTimePicker ウィジェットを使用すると、日付、時刻、または日付と時刻の両方を入力できます。または、選択ツールを使用して日付と時刻を選択することもできます。無効な日付または時刻を入力すると、選択ツールにエラーが表示され、情報を正しく入力するよう求めるメッセージが表示されます。

以下は、3 種類の DateTimePicker ウィジェットで構成されたカードを示しています。

ユーザーがアイテムを選択できるようにする

SelectionInput ウィジェットは、チェックボックス、ラジオボタン、スイッチ、プルダウン メニューなど、選択可能な一連のアイテムを提供します。このウィジェットを使用すると、定義済みで標準化されたデータをユーザーから収集できます。ユーザーから未定義のデータを収集するには、代わりに TextInput ウィジェットを使用します。

SelectionInput ウィジェットは、ユーザーが均一なデータを入力するのに役立つ候補と、変更時のアクション(ユーザーによるアイテムの選択や選択解除など、選択入力フィールドに変更が発生したときに実行される Actions)をサポートしています。

Chat アプリは、選択されたアイテムの値を受け取って処理できます。フォーム入力の操作の詳細については、ユーザーが入力した情報を処理するをご覧ください。

このセクションでは、SelectionInput ウィジェットを使用するカードの例を示します。この例では、さまざまな種類のセクション入力を使用します。

チェックボックスを追加する

次のコードは、連絡先が仕事用、個人用、またはその両方であるかどうかをユーザーに尋ねるカードを表示します。チェックボックスを使用する SelectionInput ウィジェットも使用しています。

ラジオボタンを追加する

次のコードは、ラジオボタンを使用する SelectionInput ウィジェットで、連絡先が仕事用か個人用かを指定するようユーザーに求めるカードを表示します。

スイッチを追加する

次のコードは、スイッチを使用する SelectionInput ウィジェットで、連絡先が仕事用、個人用、またはその両方であるかどうかをユーザーに尋ねるカードを表示します。

以下は、プルダウン メニューを使用する SelectionInput ウィジェットで、連絡先が仕事用か個人用かを指定するようユーザーに求めるカードです。

複数選択メニューを追加する

次に、複数選択メニューから連絡先を選択するようユーザーに求めるカードが表示されます。

マルチ選択メニューの項目は、Google Workspace の次のデータソースから入力できます。

  • Google Workspace ユーザー: 同じ Google Workspace 組織内のユーザーのみを入力できます。
  • Chat スペース: マルチ選択メニューにアイテムを入力するユーザーは、Google Workspace 組織内で自分が所属するスペースのみを表示および選択できます。

Google Workspace データソースを使用するには、platformDataSource フィールドを指定します。他の選択入力タイプとは異なり、SectionItem オブジェクトは省略します。これらの選択項目は Google Workspace から動的に取得されるためです。

次のコードは、Google Workspace ユーザーの複数選択メニューを示しています。ユーザーを入力するには、選択入力で commonDataSourceUSER に設定します。

JSON

{
  "selectionInput": {
    "name": "contacts",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 5,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "commonDataSource": "USER"
    }
  }
}

次のコードは、Chat スペースの複数選択メニューを示しています。スペースを入力するには、選択入力で hostAppDataSource フィールドを指定します。また、複数選択メニューでは defaultToCurrentSpacetrue に設定されるため、現在のスペースがメニューのデフォルト選択になります。

JSON

{
  "selectionInput": {
    "name": "spaces",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 3,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "hostAppDataSource": {
        "chatDataSource": {
          "spaceDataSource": {
            "defaultToCurrentSpace": true
          }
        }
      }
    }
  }
}

複数選択メニューには、サードパーティまたは外部のデータソースからアイテムを入力することもできます。たとえば、複数選択メニューを使用すると、顧客管理(CRM)システムの見込み顧客のリストからユーザーが選択できるようになります。

外部データソースを使用するには、externalDataSource フィールドを使用して、データソースからアイテムを返す関数を指定します。

外部データソースへのリクエストを減らすには、ユーザーがメニューに入力する前に、複数選択メニューに候補アイテムを表示できます。たとえば、そのユーザーの最近検索した連絡先を入力できます。外部データソースから候補アイテムを入力するには、SelectionItem オブジェクトを指定します。

次のコードは、ユーザーの外部連絡先セットのアイテムの複数選択メニューを示しています。メニューにはデフォルトで 1 件の連絡先が表示され、関数 getContacts が実行されて外部データソースからアイテムが取得され、入力されます。

Node.js

node/selection-input/index.js
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

Python

python/selection-input/main.py
'selectionInput': {
  'name': "contacts",
  'type': "MULTI_SELECT",
  'label': "Selected contacts",
  'multiSelectMaxSelectedItems': 3,
  'multiSelectMinQueryLength': 1,
  'externalDataSource': { 'function': "getContacts" },
  # Suggested items loaded by default.
  # The list is static here but it could be dynamic.
  'items': [get_contact("3")]
}

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
  .setName("contacts")
  .setType("MULTI_SELECT")
  .setLabel("Selected contacts")
  .setMultiSelectMaxSelectedItems(3)
  .setMultiSelectMinQueryLength(1)
  .setExternalDataSource(new GoogleAppsCardV1Action().setFunction("getContacts"))
  .setItems(List.of(getContact("3")))))))))));

Apps Script

apps-script/selection-input/selection-input.gs
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

外部データソースの場合は、ユーザーがマルチ選択メニューで入力を開始したアイテムを自動的に補完することもできます。たとえば、ユーザーが米国の都市を入力するメニューに対して Atl の入力を開始すると、ユーザーが入力を終える前に、Chat アプリは Atlanta を自動提案できます。最大 100 個の項目を自動入力できます。

項目の予測入力を行うには、外部データソースに対してクエリを実行し、ユーザーが複数選択メニューに入力するたびに項目を返す関数を作成します。この関数は、次のことを行う必要があります。

  • メニューに対するユーザー操作を表すイベント オブジェクトを渡します。
  • インタラクション イベントの invokedFunction 値が externalDataSource フィールドの関数と一致していることを確認します。
  • 関数が一致すると、外部データソースから候補アイテムを返します。ユーザーが入力した内容に基づいてアイテムを提案するには、autocomplete_widget_query キーの値を取得します。この値は、ユーザーがメニューに入力した内容を表します。

次のコードは、外部データリソースのアイテムを自動的に入力します。上記の例を使用すると、Chat アプリは、getContacts 関数がトリガーされたタイミングに基づいてアイテムを提案します。

Node.js

node/selection-input/index.js
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Python

python/selection-input/main.py
def on_widget_update(event: dict) -> dict:
  """Responds to a WIDGET_UPDATE event in Google Chat."""
  if "getContacts" == event.get("common").get("invokedFunction"):
    query = event.get("common").get("parameters").get("autocomplete_widget_query")
    return { 'actionResponse': {
      'type': "UPDATE_WIDGET",
      'updatedWidget': { 'suggestions': { 'items': list(filter(lambda e: query is None or query in e["text"], [
        # The list is static here but it could be dynamic.
        get_contact("1"), get_contact("2"), get_contact("3"), get_contact("4"), get_contact("5")
      # Only return items based on the query from the user
      ]))}}
    }}


def get_contact(id: str) -> dict:
  """Generate a suggested contact given an ID."""
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
// Responds to a WIDGET_UPDATE event in Google Chat.
Message onWidgetUpdate(JsonNode event) {
  if ("getContacts".equals(event.at("/invokedFunction").asText())) {
    String query = event.at("/common/parameters/autocomplete_widget_query").asText();
    return new Message().setActionResponse(new ActionResponse()
      .setType("UPDATE_WIDGET")
      .setUpdatedWidget(new UpdatedWidget()
        .setSuggestions(new SelectionItems().setItems(List.of(
          // The list is static here but it could be dynamic.
          getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
        // Only return items based on the query from the user
        ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList()))));
  }
  return null;
}

// Generate a suggested contact given an ID.
GoogleAppsCardV1SelectionItem getContact(String id) {
  return new GoogleAppsCardV1SelectionItem()
    .setValue(id)
    .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
    .setText("Contact " + id);
}

Apps Script

apps-script/selection-input/selection-input.gs
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

カードに入力されたデータを検証する

このページでは、カードの action とウィジェットに入力されたデータを検証する方法について説明します。たとえば、テキスト入力フィールドにユーザーが入力したテキストや、特定の文字数が含まれていることを検証できます。

アクションに必要なウィジェットを設定する

カードの action の一部として、アクションに必要なウィジェットの名前を requiredWidgets リストに追加します。

このアクションが呼び出されたときに、ここにリストされているウィジェットに値がない場合、フォーム アクションの送信はキャンセルされます。

アクションに "all_widgets_are_required": "true" が設定されている場合、カード内のすべてのウィジェットがこのアクションで必要になります。

複数選択で all_widgets_are_required アクションを設定する

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "value": "contact-1",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 1",
                "bottomText": "Contact one description",
                "selected": false
              },
              {
                "value": "contact-2",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 2",
                "bottomText": "Contact two description",
                "selected": false
              },
              {
                "value": "contact-3",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 3",
                "bottomText": "Contact three description",
                "selected": false
              },
              {
                "value": "contact-4",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 4",
                "bottomText": "Contact four description",
                "selected": false
              },
              {
                "value": "contact-5",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 5",
                "bottomText": "Contact five description",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}
dateTimePicker で all_widgets_are_required アクションを設定する

JSON

{
  "sections": [
    {
      "widgets": [
        {
          "textParagraph": {
            "text": "A datetime picker widget with both date and time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_and_time",
            "label": "meeting",
            "type": "DATE_AND_TIME"
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just date:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_only",
            "label": "Choose a date",
            "type": "DATE_ONLY",
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_time_only",
            "label": "Select a time",
            "type": "TIME_ONLY"
          }
        }
      ]
    }
  ]
}
プルダウン メニューで all_widgets_are_required アクションを設定する

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "text": "Red",
                "value": "red",
                "selected": false
              },
              {
                "text": "Green",
                "value": "green",
                "selected": false
              },
              {
                "text": "White",
                "value": "white",
                "selected": false
              },
              {
                "text": "Blue",
                "value": "blue",
                "selected": false
              },
              {
                "text": "Black",
                "value": "black",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}

テキスト入力ウィジェットの検証を設定する

textInput ウィジェットの検証フィールドでは、このテキスト入力ウィジェットの文字数制限と入力タイプを指定できます。

テキスト入力ウィジェットの文字数制限を設定する

JSON

{
  "sections": [
    {
      "header": "Tell us about yourself",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "favoriteColor",
            "label": "Favorite color",
            "type": "SINGLE_LINE",
            "validation": {"character_limit":15},
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        }
      ]
    }
  ]
}
テキスト入力ウィジェットの入力タイプを設定する

JSON

{
  "sections": [
    {
      "header": "Validate text inputs by input types",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "mailing_address",
            "label": "Please enter a valid email address",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "EMAIL"
            },
            "onChangeAction": {
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "input_type": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "FLOAT"
            }
          }
        }
      ]
    }
  ]
}

トラブルシューティング

Google Chat アプリまたはカードからエラーが返されると、Chat インターフェースに「エラーが発生しました」というメッセージが表示されます。または「リクエストを処理できません」が表示されます。Chat UI にエラー メッセージが表示されなくても、Chat アプリやカードが予期しない結果(カード メッセージが表示されないなど)を生成することがあります。

Chat UI にエラー メッセージが表示されない場合がありますが、Chat 用アプリのエラーロギングが有効になっている場合は、説明的なエラー メッセージとログデータを使用してエラーを修正できます。エラーの表示、デバッグ、修正については、Google Chat エラーのトラブルシューティングと修正をご覧ください。