Adicionar elementos de interface interativos a cards

Esta página explica como adicionar widgets e elementos de interface aos cards para que os usuários possam interagir com o app Google Chat, por exemplo, clicando em um botão ou enviando informações.

Os apps de chat podem usar as seguintes interfaces para criar cards interativos:

  • Mensagens que contêm um ou mais cards.
  • Páginas iniciais, que é um card que aparece na guia Início em mensagens diretas com o app Chat.
  • Diálogos, que são cards que são abertos em uma nova janela de mensagens e páginas iniciais.

Quando os usuários interagem com cards, os apps de chat podem usar os dados recebidos para processar e responder de acordo. Saiba mais em Coletar e processar informações dos usuários do Google Chat.


Use o Criador de cards para projetar e visualizar mensagens e interfaces do usuário para apps de chat:

Abrir o criador de cards

Pré-requisitos

Um app do Google Chat que tenha recursos interativos ativados. Para criar um app de chat interativo, conclua um dos guias de início rápido a seguir com base na arquitetura do app que você quer usar:

Adicionar um botão

O widget ButtonList mostra um conjunto de botões. Os botões podem mostrar texto, um ícone ou texto e um ícone. Cada Button oferece suporte a uma ação OnClick que ocorre quando os usuários clicam no botão. Exemplo:

  • Abra um hiperlink com OpenLink para fornecer mais informações aos usuários.
  • Execute um action que execute uma função personalizada, como chamar uma API.

Para acessibilidade, os botões aceitam texto alternativo.

Adicionar um botão que execute uma função personalizada

Confira abaixo um card que consiste em um widget ButtonList com dois botões. Um botão abre a documentação do desenvolvedor do Google Chat em uma nova guia. O outro botão executa uma função personalizada chamada goToView() e transmite o parâmetro viewType="BIRD EYE VIEW".

Adicionar um botão com o estilo do Material Design

Confira abaixo um conjunto de botões em diferentes estilos do Material Design.

Para aplicar o estilo do Material Design, não inclua o atributo 'cor' [color].

Adicionar um botão com cor personalizada e um botão desativado

É possível impedir que os usuários cliquem em um botão definindo "disabled": "true".

A imagem a seguir mostra um card composto por um widget ButtonList com dois botões. Um deles usa o campo Color para personalizar a cor do plano de fundo dele. O outro botão é desativado com o campo Disabled, que impede o usuário de clicar no botão e executar a função.

Adicionar um botão com um ícone

A imagem a seguir mostra um card que consiste em um widget ButtonList com dois widgets Button de ícone. Um botão usa o campo knownIcon para mostrar o ícone de e-mail integrado do Google Chat. O outro botão usa o campo iconUrl para mostrar um widget de ícone personalizado.

Adicionar um botão com ícone e texto

Veja a seguir um card que consiste em um widget ButtonList que solicita que o usuário envie um e-mail. O primeiro botão mostra um ícone de e-mail, e o segundo mostra texto. O usuário pode clicar no ícone ou no botão de texto para executar a função sendEmail.

Personalizar o botão de uma seção colapsável

Personalize o botão de controle que recolhe e expande seções em um card. Escolha entre vários ícones ou imagens para representar visualmente o conteúdo da seção, facilitando a compreensão e a interação dos usuários com as informações.

Adicionar um menu flutuante

O Overflow menu pode ser usado em cards de chat para oferecer mais opções e ações. Ele permite incluir mais opções sem sobrecarregar a interface do cartão, garantindo um design limpo e organizado.

Adicionar uma lista de ícones

O widget ChipList é uma maneira versátil e visualmente atraente de mostrar informações. Use listas de ícones para representar tags, categorias ou outros dados relevantes, facilitando a navegação e a interação dos usuários com seu conteúdo.

Coletar informações dos usuários

Esta seção explica como adicionar widgets que coletam informações, como texto ou seleções.

Para saber como processar o que os usuários inserem, consulte Coletar e processar informações dos usuários do Google Chat.

Coletar texto

O widget TextInput oferece um campo em que os usuários podem inserir texto. O widget oferece suporte a sugestões, que ajudam os usuários a inserir dados uniformes, e ações ao mudar, que são Actions executadas quando ocorre uma mudança no campo de entrada de texto, como quando um usuário adiciona ou exclui texto.

Quando você precisar coletar dados abstratos ou desconhecidos dos usuários, use este widget TextInput. Para coletar dados definidos dos usuários, use o widget SelectionInput.

Confira a seguir um card que consiste em um widget TextInput:

Coletar datas ou horários

O widget DateTimePicker permite que os usuários insiram uma data, um horário ou ambos. Os usuários também podem usar o seletor para selecionar datas e horários. Se os usuários inserirem uma data ou hora inválida, o seletor mostrará um erro solicitando que eles insiram as informações corretamente.

A seguir, há um card com três tipos diferentes de widgets DateTimePicker:

Permitir que os usuários selecionem itens

O widget SelectionInput oferece um conjunto de itens selecionáveis, como caixas de seleção, botões de opção, chaves ou um menu suspenso. Você pode usar esse widget para coletar dados definidos e padronizados dos usuários. Para coletar dados indefinidos de usuários, use o widget TextInput.

O widget SelectionInput oferece suporte a sugestões, que ajudam os usuários a inserir dados uniformes, e ações quando há mudança, que são Actions executadas quando ocorre uma mudança em um campo de entrada de seleção, como quando um usuário seleciona ou desmarca um item.

Os apps de chat podem receber e processar o valor dos itens selecionados. Para saber mais sobre como trabalhar com entradas de formulário, consulte Processar informações inseridas pelos usuários.

Esta seção apresenta exemplos de cards que usam o widget SelectionInput. Os exemplos usam diferentes tipos de entradas de seção:

Adicionar uma caixa de seleção

A imagem a seguir mostra um card que pede ao usuário para especificar se um contato é profissional, pessoal ou ambos, com um widget SelectionInput que usa caixas de seleção:

Adicionar um botão de opção

A imagem a seguir mostra um card que pede ao usuário para especificar se um contato é profissional ou pessoal com um widget SelectionInput que usa botões de opção:

Adicionar uma chave

A imagem a seguir mostra um card que pede ao usuário para especificar se um contato é profissional, pessoal ou ambos com um widget SelectionInput que usa interruptores:

A imagem a seguir mostra um card que pede ao usuário para especificar se um contato é profissional ou pessoal com um widget SelectionInput que usa um menu suspenso:

Adicionar um menu de seleção múltipla

A imagem a seguir mostra um card que pede ao usuário para selecionar contatos em um menu de múltipla seleção:

É possível preencher itens para um menu de múltipla seleção com as seguintes fontes de dados no Google Workspace:

  • Usuários do Google Workspace: só é possível preencher usuários na mesma organização do Google Workspace.
  • Espaços do Chat: o usuário que insere itens no menu de seleção múltipla só pode visualizar e selecionar espaços aos quais pertence na organização do Google Workspace.

Para usar as fontes de dados do Google Workspace, especifique o campo platformDataSource. Ao contrário de outros tipos de entrada de seleção, você omite objetos SectionItem, porque esses itens de seleção são fornecidos dinamicamente pelo Google Workspace.

O código a seguir mostra um menu de múltipla seleção de usuários do Google Workspace. Para preencher usuários, a entrada de seleção define commonDataSource como USER:

JSON

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

O código a seguir mostra um menu de múltipla seleção de espaços de chat. Para preencher espaços, a entrada de seleção especifica o campo hostAppDataSource. O menu de seleção múltipla também define defaultToCurrentSpace como true, o que faz com que o espaço atual seja a seleção padrão no menu:

JSON

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

Os menus de múltipla seleção também podem preencher itens de uma fonte de dados externa ou de terceiros. Por exemplo, é possível usar menus de seleção múltipla para ajudar um usuário a selecionar de uma lista de leads de vendas de um sistema de gestão de relacionamento com o cliente (CRM).

Para usar uma fonte de dados externa, use o campo externalDataSource para especificar uma função que retorna itens da fonte de dados.

Para reduzir as solicitações a uma fonte de dados externa, é possível incluir itens sugeridos que aparecem no menu de múltipla seleção antes que os usuários digitem no menu. Por exemplo, é possível preencher os contatos pesquisados recentemente para o usuário. Para preencher itens sugeridos em uma fonte de dados externa, especifique objetos SelectionItem.

O código a seguir mostra um menu de seleção múltipla de itens de um conjunto externo de contatos para o usuário. O menu exibe um contato por padrão e executa a função getContacts para recuperar e preencher itens da fonte de dados externa:

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")]
}

Para fontes de dados externas, também é possível fazer preenchimento automático de itens que os usuários começam a digitar no menu de seleção múltipla. Por exemplo, se um usuário começar a digitar Atl para um menu que preenche cidades nos Estados Unidos, o app de chat pode sugerir automaticamente Atlanta antes que o usuário termine de digitar. Você pode fazer o preenchimento automático de até 100 itens.

Para fazer o preenchimento automático de itens, crie uma função que consulte a fonte de dados externa e retorne itens sempre que um usuário digitar no menu de seleção múltipla. A função precisa:

  • Transmita um objeto de evento que represente a interação do usuário com o menu.
  • Identifique se o valor invokedFunction do evento de interação corresponde à função do campo externalDataSource.
  • Quando as funções corresponderem, retorne itens sugeridos da fonte de dados externa. Para sugerir itens com base no que o usuário digita, acesse o valor da chave autocomplete_widget_query. Esse valor representa o que o usuário digita no menu.

O código a seguir faz o preenchimento automático de itens de um recurso de dados externo. Usando o exemplo anterior, o app de chat sugere itens com base em quando a função getContacts é acionada:

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
  };
}

Validar os dados inseridos nos cards

Esta página explica como validar os dados inseridos no action e nos widgets de um card. Por exemplo, é possível validar se um campo de entrada de texto tem texto inserido pelo usuário ou se ele contém um determinado número de caracteres.

Definir os widgets necessários para ações

Como parte do action do card, adicione nomes de widgets necessários para uma ação à lista requiredWidgets.

Se algum widget listado aqui não tiver um valor quando essa ação for invocada, o envio da ação do formulário será cancelado.

Quando "all_widgets_are_required": "true" é definido para uma ação, todos os widgets no card são necessários para essa ação.

Definir uma ação all_widgets_are_required na seleção múltipla

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
              }
            ]
          }
        }
      ]
    }
  ]
}
Definir uma ação all_widgets_are_required no dateTimePicker

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"
          }
        }
      ]
    }
  ]
}
Definir uma ação all_widgets_are_required no menu suspenso

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
              }
            ]
          }
        }
      ]
    }
  ]
}

Definir a validação de um widget de entrada de texto

No campo de validação do widget textInput, ele pode especificar o limite de caracteres e o tipo de entrada para esse widget de entrada de texto.

Definir um limite de caracteres para um widget de entrada de texto

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
            }
          }
        }
      ]
    }
  ]
}
Definir o tipo de entrada para um widget de entrada de texto

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"
            }
          }
        }
      ]
    }
  ]
}

Resolver problemas

Quando um app do Google Chat ou um card retorna um erro, a interface do Chat mostra uma mensagem informando que "Ocorreu um erro". ou "Não foi possível processar sua solicitação". Às vezes, a interface do Chat não mostra nenhuma mensagem de erro, mas o app ou o card do Chat produz um resultado inesperado. Por exemplo, uma mensagem de card pode não aparecer.

Embora uma mensagem de erro possa não aparecer na interface do Chat, mensagens de erro descritivas e dados de registro estão disponíveis para ajudar a corrigir erros quando o registro de erros para apps de chat estiver ativado. Para saber como visualizar, depurar e corrigir erros, consulte Resolver e corrigir erros do Google Chat.