Cómo agregar elementos interactivos de la IU a las tarjetas

En esta página, se explica cómo agregar widgets y elementos de la IU a las tarjetas para que los usuarios puedan interactuar con tu app de Google Chat, por ejemplo, haciendo clic en un botón o enviando información.

Las apps de Chat pueden usar las siguientes interfaces de Chat para crear tarjetas interactivas:

  • Mensajes que contienen una o más tarjetas
  • Páginas principales, que es una tarjeta que aparece en la pestaña Página principal en los mensajes directos con la app de Chat.
  • Diálogos, que son tarjetas que se abren en una ventana nueva desde mensajes y páginas principales

Cuando los usuarios interactúan con las tarjetas, las apps de Chat pueden usar los datos que reciben para procesar y responder según corresponda. Para obtener más información, consulta Recopila y procesa información de los usuarios de Google Chat.


Usa el Creador de tarjetas para diseñar y obtener una vista previa de los mensajes y las interfaces de usuario de las apps de Chat:

Abre el Creador de tarjetas

Requisitos previos

Una app de Google Chat configurada para recibir interacciones de los usuarios y responder a ellas Para crear una app de Chat interactiva, completa una de las siguientes guías de inicio rápido según la arquitectura de la app que quieras usar:

Agrega un botón

El widget ButtonList muestra un conjunto de botones. Los botones pueden mostrar texto, un ícono o ambos. Cada Button admite una acción OnClick que se produce cuando los usuarios hacen clic en el botón. Por ejemplo:

  • Abrir un hipervínculo con OpenLink para proporcionar información adicional a los usuarios
  • Ejecuta un action que ejecute una función personalizada, como llamar a una API.

Para mejorar la accesibilidad, los botones admiten texto alternativo.

Cómo agregar un botón que ejecute una función personalizada

A continuación, se muestra una tarjeta que consta de un widget ButtonList con dos botones. Un botón abre la documentación para desarrolladores de Google Chat en una pestaña nueva. El otro botón ejecuta una función personalizada llamada goToView() y pasa el parámetro viewType="BIRD EYE VIEW".

Agrega un botón con el estilo de Material Design

A continuación, se muestra un conjunto de botones con diferentes estilos de botones de Material Design.

Para aplicar el estilo de Material Design, no incluyas el atributo "color".

Agrega un botón con un color personalizado y un botón desactivado

Puedes evitar que los usuarios hagan clic en un botón configurando "disabled": "true".

A continuación, se muestra una tarjeta que consta de un widget ButtonList con dos botones. Un botón usa el campo Color para personalizar el color de fondo del botón. El otro botón se desactiva con el campo Disabled, lo que evita que el usuario haga clic en el botón y ejecute la función.

Cómo agregar un botón con un ícono

A continuación, se muestra una tarjeta que consta de un widget ButtonList con dos widgets de ícono Button. Un botón usa el campo knownIcon para mostrar el ícono de correo electrónico integrado de Google Chat. El otro botón usa el campo iconUrl para mostrar un widget de ícono personalizado.

Cómo agregar un botón con un ícono y texto

A continuación, se muestra una tarjeta que consta de un widget ButtonList que solicita al usuario que envíe un correo electrónico. El primer botón muestra un ícono de correo electrónico y el segundo, texto. El usuario puede hacer clic en el ícono o en el botón de texto para ejecutar la función sendEmail.

Cómo personalizar el botón de una sección contraíble

Personalizar el botón de control que contrae y expande las secciones dentro de una tarjeta Elige entre una variedad de íconos o imágenes para representar visualmente el contenido de cada sección, lo que facilitará a los usuarios la interacción y comprensión de la información.

Agrega un menú ampliado

El elemento Overflow menu se puede usar en las tarjetas de Chat para ofrecer opciones y acciones adicionales. Te permite incluir más opciones sin desordenar la interfaz de la tarjeta, lo que garantiza un diseño claro y organizado.

Agrega una lista de chips

El widget ChipList brinda una forma versátil y visualmente atractiva de mostrar la información. Usa listas de chips para representar etiquetas, categorías o demás datos relevantes, lo que facilita la interacción y navegación de los usuarios por tu contenido.

Recopilar información de los usuarios

En esta sección, se explica cómo agregar widgets que recopilan información, como texto o selecciones.

Para aprender a procesar lo que ingresan los usuarios, consulta Recopila y procesa información de los usuarios de Google Chat.

Recopila texto

El widget TextInput proporciona un campo en el que los usuarios pueden ingresar texto. El widget admite sugerencias, que ayudan a los usuarios a ingresar datos uniformes, y acciones de cambio, que son Actions que se ejecutan cuando se produce un cambio en el campo de entrada de texto, como cuando un usuario agrega o borra texto.

Cuando necesites recopilar datos abstractos o desconocidos de los usuarios, usa este widget de TextInput. Para recopilar datos definidos de los usuarios, usa el widget SelectionInput en su lugar.

La siguiente es una tarjeta que consta de un widget TextInput:

Recopilar fechas o horas

El widget DateTimePicker permite que los usuarios ingresen una fecha, una hora o ambas. También pueden usar el selector para elegir fechas y horas. Si los usuarios ingresan una fecha o una hora no válidas, el selector muestra un error que les solicita que ingresen la información correctamente.

A continuación, se muestra una tarjeta que consta de tres tipos diferentes de widgets de DateTimePicker:

Permite que los usuarios seleccionen elementos

El widget SelectionInput proporciona un conjunto de elementos seleccionables, como casillas de verificación, botones de selección, interruptores o un menú desplegable. Puedes usar este widget para recopilar datos definidos y estandarizados de los usuarios. Para recopilar datos no definidos de los usuarios, usa el widget TextInput en su lugar.

El widget SelectionInput admite sugerencias, que ayudan a los usuarios a ingresar datos uniformes, y acciones de cambio, que son Actions que se ejecutan cuando se produce un cambio en un campo de entrada de selección, como cuando un usuario selecciona o anula la selección de un elemento.

Las apps de Chat pueden recibir y procesar el valor de los elementos seleccionados. Para obtener detalles sobre cómo trabajar con entradas de formularios, consulta Cómo procesar la información que ingresan los usuarios.

En esta sección, se proporcionan ejemplos de tarjetas que usan el widget SelectionInput. En los ejemplos, se usan diferentes tipos de entradas de sección:

Agregar una casilla de verificación

A continuación, se muestra una tarjeta en la que se le pide al usuario que especifique si un contacto es profesional, personal o ambos, con un widget SelectionInput que usa casillas de verificación:

Cómo agregar un botón de selección

A continuación, se muestra una tarjeta que le pide al usuario que especifique si un contacto es profesional o personal con un widget SelectionInput que usa botones de selección:

Agrega un interruptor

A continuación, se muestra una tarjeta que le pide al usuario que especifique si un contacto es profesional, personal o ambos con un widget SelectionInput que usa interruptores:

A continuación, se muestra una tarjeta que le pide al usuario que especifique si un contacto es profesional o personal con un widget SelectionInput que usa un menú desplegable:

Cómo propagar menús desplegables de forma dinámica

Disponible para las apps de Google Chat.

Puedes propagar elementos de forma dinámica para un menú desplegable a partir de fuentes de datos en Google Workspace o de una fuente de datos externa. Para usar fuentes de datos dinámicas, especifica el campo dataSourceConfigs, que es un array de objetos DataSourceConfig. Cada DataSourceConfig puede contener un platformDataSource o un remoteDataSource. En este momento, solo se admite un DataSourceConfig.

Propaga elementos de Google Workspace

Para propagar elementos de fuentes de datos de Google Workspace, como los usuarios de Google Workspace, especifica el campo platformDataSource dentro de un DataSourceConfig. A diferencia de lo que ocurre cuando usas items estáticos, omites los objetos SelectionItem, ya que estos elementos de selección se obtienen de forma dinámica de Google Workspace.

En el siguiente código, se muestra un menú desplegable que propaga los usuarios de Google Workspace:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "contacts",
            "type": "DROPDOWN",
            "label": "Select contact from organization",
            "dataSourceConfigs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "minCharactersTrigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}
Propaga elementos desde una fuente de datos externa

Para propagar elementos de una fuente de datos externa o de terceros, como un sistema de administración de relaciones con clientes (CRM), usa el campo remoteDataSource dentro de un DataSourceConfig para especificar una función que devuelva elementos de la fuente de datos.

En el siguiente código, se muestra un menú desplegable que propaga elementos de un conjunto externo de contactos ejecutando la función getCrmLeads:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "crm_leads",
            "type": "DROPDOWN",
            "label": "Select CRM Lead",
            "dataSourceConfigs": [
              {
                "remoteDataSource": {
                  "function": "getCrmLeads"
                },
                "minCharactersTrigger": 2
              }
            ],
            "items": [
              {
                "text": "Suggested Lead 1",
                "value": "lead-1"
              }
            ]
          }
        }
      ]
    }
  ]
}

Para reducir las solicitudes a una fuente de datos dinámica, puedes incluir elementos sugeridos que aparezcan en el menú desplegable antes de que los usuarios escriban. También puedes configurar el menú desplegable para que complete automáticamente los elementos según lo que escriban los usuarios. Para ello, establece minCharactersTrigger dentro de DataSourceConfig. Cuando un usuario escribe, al menos, la cantidad de caracteres especificada en minCharactersTrigger, se activa la función especificada en remoteDataSource. El objeto de evento que se pasa a tu función incluye la entrada del usuario en la clave autocomplete_widget_query.

Cómo agregar un menú de selección múltiple

A continuación, se muestra una tarjeta que le pide al usuario que seleccione contactos de un menú de selección múltiple:

Puedes propagar elementos para un menú de selección múltiple desde las siguientes fuentes de datos en Google Workspace:

  • Usuarios de Google Workspace: Solo puedes propagar usuarios dentro de la misma organización de Google Workspace.
  • Espacios de Chat: El usuario que ingresa elementos en el menú de selección múltiple solo puede ver y seleccionar los espacios a los que pertenece dentro de su organización de Google Workspace.

Para usar fuentes de datos de Google Workspace, especifica el campo platformDataSource. A diferencia de otros tipos de entradas de selección, omites los objetos SelectionItem, ya que estos elementos de selección se obtienen de forma dinámica de Google Workspace.

En el siguiente código, se muestra un menú de selección múltiple de usuarios de Google Workspace. Para propagar usuarios, la entrada de selección establece commonDataSource en USER:

JSON

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

En el siguiente código, se muestra un menú de selección múltiple de espacios de Chat. Para propagar espacios, la entrada de selección especifica el campo hostAppDataSource. El menú de selección múltiple también establece defaultToCurrentSpace en true, lo que hace que el espacio actual sea la selección predeterminada en el menú:

JSON

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

Los menús de selección múltiple también pueden propagar elementos de una fuente de datos externa o de terceros. Por ejemplo, puedes usar menús de selección múltiple para ayudar a un usuario a seleccionar de una lista de leads comerciales de un sistema de administración de relaciones con clientes (CRM).

Para usar una fuente de datos externa, utiliza el campo externalDataSource para especificar una función que devuelva elementos de la fuente de datos.

Para reducir las solicitudes a una fuente de datos externa, puedes incluir elementos sugeridos que aparecen en el menú de selección múltiple antes de que los usuarios escriban en el menú. Por ejemplo, puedes propagar los contactos que el usuario buscó recientemente. Para propagar elementos sugeridos de una fuente de datos externa, especifica objetos SelectionItem.

En la siguiente muestra de código, se indica un menú de selección múltiple que consulta y propaga elementos de una fuente de datos externa:

Node.js

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

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Python

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

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Java

java/chat/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(FUNCTION_URL))
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  .setItems(List.of(getSuggestedContact("3")))))))))));

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Apps Script

En este ejemplo, se envía un mensaje de tarjeta con la devolución del JSON de la tarjeta. También puedes usar el servicio de tarjetas de Apps Script.

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

Cómo propagar elementos sugeridos desde una fuente de datos dinámica

En el caso de las fuentes de datos externas, también puedes autocompletar y sugerir elementos que los usuarios comiencen a escribir en un menú de selección múltiple o un menú desplegable. Por ejemplo, si un usuario comienza a escribir Atl para un menú que propaga ciudades de Estados Unidos, tu app de Chat puede sugerir automáticamente Atlanta antes de que el usuario termine de escribir. Puedes sugerir hasta 100 elementos.

Para mostrar los elementos sugeridos, la función que consulta la fuente de datos externa debe hacer lo siguiente:

  1. Controlar un objeto de evento, que la app de Chat recibe cuando los usuarios escriben en el menú
  2. En el objeto de evento, obtén el valor que escribe el usuario, que se representa en el campo event.commonEventObject.parameters["autocomplete_widget_query"].
  3. Consulta la fuente de datos con el valor de entrada del usuario para obtener uno o más objetos SelectionItems que se le sugerirán al usuario.
  4. Devuelve los elementos sugeridos con la acción RenderActions con un objeto modifyCard.

En la siguiente muestra de código, se indica cómo una app de Chat sugiere elementos de forma dinámica en el menú de selección múltiple de una tarjeta. Cuando un usuario escribe en el menú, la función o el extremo proporcionados en el campo externalDataSource del widget consulta una fuente de datos externa y sugiere elementos que el usuario puede seleccionar:

Node.js

node/chat/selection-input/index.js
/**
 * Web app that responds to events sent from a Google Chat space.
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
app.post('/', async (req, res) => {
  // Stores the Google Chat event
  const chatEvent = req.body.chat;

  // Handle user interaction with multiselect.
  if(chatEvent.widgetUpdatedPayload) {
    return res.json(queryContacts(req.body));
  }

  // Replies with a card that contains the multiselect menu.
  return res.json({ hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: FUNCTION_URL },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}});
});

/**
 * Get contact suggestions based on text typed by users.
 *
 * @param {Object} event the event object that contains the user's query
 * @return {Object} suggestions
 */
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("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 selection item in the menu.
 */
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Python

python/chat/selection-input/main.py
@app.route('/', methods=['POST'])
def post() -> Mapping[str, Any]:
  """Handle requests from Google Chat

  Returns:
      Mapping[str, Any]: The response
  """
  # Stores the Google Chat event
  chatEvent = request.get_json().get('chat')

  # Handle user interaction with multiselect.
  if chatEvent.get('widgetUpdatedPayload') is not None:
    return json.jsonify(query_contacts(request.get_json()))

  # Replies with a card that contains the multiselect menu.
  return json.jsonify({ 'hostAppDataAction': { 'chatDataAction': { 'createMessageAction': {
    'message': { 'cardsV2': [{
      'cardId': "contactSelector",
      'card': { 'sections':[{ 'widgets': [{
        'selectionInput': {
          'name': "contacts",
          'type': "MULTI_SELECT",
          'label': "Selected contacts",
          'multiSelectMaxSelectedItems': 3,
          'multiSelectMinQueryLength': 1,
          'externalDataSource': { 'function': FUNCTION_URL },
          # Suggested items loaded by default.
          # The list is static here but it could be dynamic.
          'items': [get_suggested_contact("3")]
        }
      }]}]}
    }]}
  }}}})


def query_contacts(event: dict) -> dict:
  """Get contact suggestions based on text typed by users.

  Args:
      event (Mapping[str, Any]): The event object that contains the user's query

  Returns:
      Mapping[str, Any]: The response with contact suggestions.
  """
  query = event.get("commonEventObject").get("parameters").get("autocomplete_widget_query")
  return { 'action': { 'modifyOperations': [{ 'updateWidget': { 'selectionInputWidgetSuggestions': { 'suggestions': list(
    filter(lambda e: query is None or query in e["text"], [
      # The list is static here but it could be dynamic.
      get_suggested_contact("1"), get_suggested_contact("2"), get_suggested_contact("3"), get_suggested_contact("4"), get_suggested_contact("5")
    # Only return items based on the query from the user
    ])
  )}}}]}}


def get_suggested_contact(id: str) -> dict:
  """Generate a suggested contact given an ID.

  Args:
      id (str): The ID of the contact to return.

  Returns:
      Mapping[str, Any]: The contact formatted as a selection item in the menu.
  """
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Java

java/chat/selection-input/src/main/java/com/google/chat/selectionInput/App.java
@SpringBootApplication
@RestController
// Web app that responds to events sent from a Google Chat space.
public class App {
  private static final String FUNCTION_URL = "your-function-url";

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  /**
   * Handle requests from Google Chat
   * 
   * @param event the event object sent by Google Chat
   * @return The response to be sent back to Google Chat
   */
  @PostMapping("/")
  @ResponseBody
  public GenericJson onEvent(@RequestBody JsonNode event) throws Exception {
    // Stores the Google Chat event
    JsonNode chatEvent = event.at("/chat");

    // Handle user interaction with multiselect.
    if (!chatEvent.at("/widgetUpdatedPayload").isEmpty()) {
      return queryContacts(event);
    }

    // Replies with a card that contains the multiselect menu.
    Message message = new Message().setCardsV2(List.of(new CardWithId()
      .setCardId("contactSelector")
      .setCard(new GoogleAppsCardV1Card()
        .setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(new GoogleAppsCardV1Widget()
          .setSelectionInput(new GoogleAppsCardV1SelectionInput()
            .setName("contacts")
            .setType("MULTI_SELECT")
            .setLabel("Selected contacts")
            .setMultiSelectMaxSelectedItems(3)
            .setMultiSelectMinQueryLength(1)
            .setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
            // Suggested items loaded by default.
            // The list is static here but it could be dynamic.
            .setItems(List.of(getSuggestedContact("3")))))))))));

    return new GenericJson() {{
      put("hostAppDataAction", new GenericJson() {{
        put("chatDataAction", new GenericJson() {{
          put("createMessageAction", new GenericJson() {{
            put("message", message);
          }});
        }});
      }});
    }};
  }

  /**
   * Get contact suggestions based on text typed by users.
   *
   * @param event the event object that contains the user's query.
   * @return The response with contact suggestions.
   */
  GenericJson queryContacts(JsonNode event) throws Exception {
    String query = event.at("/commonEventObject/parameters/autocomplete_widget_query").asText();
    List<GoogleAppsCardV1SelectionItem> suggestions = List.of(
      // The list is static here but it could be dynamic.
      getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
    // Only return items based on the query from the user
    ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList();

    return new GenericJson() {{
      put("action", new GenericJson() {{
        put("modifyOperations", List.of(new GenericJson() {{
          put("updateWidget", new GenericJson() {{
            put("selectionInputWidgetSuggestions", new GenericJson() {{
              put("suggestions", suggestions);
            }});
          }});
        }}));
      }});
    }};
  }

  /**
   * Generate a suggested contact given an ID.
   * 
   * @param id The ID of the contact to return.
   * @return The contact formatted as a selection item in the menu.
   */
  GoogleAppsCardV1SelectionItem getSuggestedContact(String id) {
    return new GoogleAppsCardV1SelectionItem()
      .setValue(id)
      .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
      .setText("Contact " + id);
  }
}

Reemplaza FUNCTION_URL por el extremo HTTP que consulta la fuente de datos externa.

Apps Script

En este ejemplo, se envía un mensaje de tarjeta con la devolución del JSON de la tarjeta. También puedes usar el servicio de tarjetas de Apps Script.

apps-script/chat/selection-input/selection-input.gs
/**
* Responds to a Message trigger in Google Chat.
*
* @param {Object} event the event object from Google Chat
* @return {Object} Response from the Chat app.
*/
function onMessage(event) {
  // Replies with a card that contains the multiselect menu.
  return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: "queryContacts" },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}};
}

/**
* Get contact suggestions based on text typed by users.
*
* @param {Object} event the event object that contains the user's query
* @return {Object} suggestions
*/
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("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 selection item in the menu.
*/
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Valida los datos ingresados en las tarjetas

En esta página, se explica cómo validar los datos ingresados en los widgets y el elemento action de una tarjeta. Por ejemplo, puedes validar que un campo de entrada de texto tenga texto ingresado por el usuario o que contenga una cierta cantidad de caracteres.

Establece los widgets obligatorios para las acciones

Como parte del action de la tarjeta, agrega nombres de widgets que una acción necesita a su lista requiredWidgets.

Si alguno de los widgets que se indican aquí no tiene un valor cuando se invoca esta acción, se cancelará el envío de la acción del formulario.

Cuando se establece "allWidgetsAreRequired": true para una acción, esta requiere todos los widgets de la tarjeta.

Cómo establecer una acción de allWidgetsAreRequired en la selección múltiple

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "allWidgetsAreRequired": 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
              }
            ]
          }
        }
      ]
    }
  ]
}
Establece una acción allWidgetsAreRequired en 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":{
              "allWidgetsAreRequired": 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"
          }
        }
      ]
    }
  ]
}
Establece una acción de allWidgetsAreRequired en el menú desplegable.

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "allWidgetsAreRequired": 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
              }
            ]
          }
        }
      ]
    }
  ]
}

Establece la validación para un widget de entrada de texto

En el campo de validación del widget textInput, se puede especificar el límite de caracteres y el tipo de entrada para este widget de entrada de texto.

Establece un límite de caracteres para un 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": {"characterLimit": 15},
            "onChangeAction":{
              "allWidgetsAreRequired": true
            }
          }
        }
      ]
    }
  ]
}
Cómo establecer el tipo de entrada para un 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": {
              "inputType": "EMAIL"
            },
            "onChangeAction": {
              "allWidgetsAreRequired": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "inputType": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "inputType": "FLOAT"
            }
          }
        }
      ]
    }
  ]
}

Solucionar problemas

Cuando una app de Google Chat o una tarjeta devuelven un error, la interfaz de Chat muestra el mensaje "Se produjo un error". o "No se puede procesar tu solicitud". A veces, la IU de Chat no muestra ningún mensaje de error, pero la app o tarjeta de Chat produce un resultado inesperado; por ejemplo, es posible que no aparezca un mensaje de tarjeta.

Aunque es posible que no se muestre un mensaje de error en la IU de Chat, hay mensajes de error descriptivos y datos de registro disponibles para ayudarte a corregir errores cuando se activa el registro de errores para las apps de Chat. Para obtener ayuda con la visualización, la depuración y la corrección de errores, consulta Cómo solucionar y corregir errores de Google Chat.

Apps de Chat que no son complementos: Diseña tarjetas y diálogos interactivos

La siguiente documentación se aplica a las apps de Chat que no son complementos de Google Workspace. Para migrar una app de Chat que no sea un complemento, consulta Cómo convertir una app de Google Chat en un complemento de Google Workspace.

En el siguiente código, se muestra un menú de selección múltiple de elementos de un conjunto externo de contactos para el usuario en una app de Chat que no es un complemento. El menú muestra un contacto de forma predeterminada y ejecuta la función getContacts para recuperar y propagar elementos de la fuente de datos 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 autocompletar elementos en una app de Chat que no sea un complemento, crea una función que consulte la fuente de datos externa y devuelva elementos cada vez que un usuario escriba en el menú de selección múltiple. La función debe hacer lo siguiente:

  • Pasa un objeto de evento que represente la interacción del usuario con el menú.
  • Identifica que el valor invokedFunction del evento de interacción coincida con la función del campo externalDataSource.
  • Cuando las funciones coincidan, devuelve los elementos sugeridos de la fuente de datos externa. Para sugerir elementos según lo que escriba el usuario, obtén el valor de la clave autocomplete_widget_query. Este valor representa lo que el usuario escribe en el menú.

El siguiente código autocompleta elementos de un recurso de datos externo. Con el ejemplo anterior, la app de Chat que no es un complemento sugiere elementos según el momento en que se activa la función 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
  };
}