Navegación por la tarjeta

La mayoría de los complementos basados en tarjetas se compilan mediante varias tarjetas que representan diferentes "páginas" de la interfaz del complemento. Para tener una experiencia del usuario eficaz, debes usar una navegación simple y natural entre las tarjetas de tu complemento.

Originalmente, en los complementos de Gmail, las transiciones entre diferentes tarjetas de la IU se controlan enviando y mostrando tarjetas desde y hacia una sola pila de tarjetas, y Gmail muestra la tarjeta superior de la pila.

Navegación desde la tarjeta de la página principal

Los complementos de Google Workspace presentan páginas principales y tarjetas no contextuales. Para incluir tarjetas contextuales y no contextuales, los complementos de Google Workspace tienen una pila de tarjetas interna para cada uno. Cuando se abre un complemento en un host, se activa la homepageTrigger correspondiente para crear la primera tarjeta de página principal en la pila (la tarjeta "página principal" azul oscuro en el siguiente diagrama). Si no se define un homepageTrigger, se crea una tarjeta predeterminada, se muestra y se envía a la pila no contextual. Esta primera tarjeta es una tarjeta raíz.

Tu complemento puede crear tarjetas no contextuales adicionales y enviarlas a la pila (las "tarjetas enviadas" azules en el diagrama) a medida que el usuario navega por tu complemento. La IU del complemento muestra la tarjeta superior en la pila, por lo que enviar tarjetas nuevas a la pila cambia la visualización, y cuando se quitan las tarjetas de la pila, se muestra la pantalla a las tarjetas anteriores.

Si tu complemento tiene un activador contextual definido, se activa el activador cuando el usuario ingresa en ese contexto. La función de activador compila la tarjeta contextual, pero la pantalla de la IU se actualiza en función de DisplayStyle de la tarjeta nueva:

  • Si DisplayStyle es REPLACE (la opción predeterminada), la tarjeta contextual (la tarjeta "contextual" de color naranja oscuro del diagrama) reemplaza la tarjeta que se muestra actualmente. Esto inicia efectivamente una nueva pila de tarjetas contextuales sobre la pila de tarjetas no contextual, y esta tarjeta contextual es la tarjeta raíz de la pila contextual.
  • Si DisplayStyle es PEEK, la IU crea un encabezado visible que aparece en la parte inferior de la barra lateral del complemento y se superpone a la tarjeta actual. El encabezado de vista previa muestra el título de la tarjeta nueva y proporciona los controles del botón del usuario que le permiten decidir si ver la tarjeta nueva o no. Si hace clic en el botón Ver, la tarjeta reemplazará la tarjeta actual (como se describió anteriormente con REPLACE).

Puedes crear tarjetas contextuales adicionales y enviarlas a la pila (las "tarjetas enviadas" amarillas del diagrama). Cuando se actualiza la pila de tarjetas, se cambia la IU del complemento para mostrar la tarjeta en la parte superior. Si el usuario deja un contexto, se quitan las tarjetas contextuales de la pila y la pantalla se actualiza a la página principal o tarjeta no contextual de más arriba.

Si el usuario ingresa a un contexto para el que tu complemento no define un activador contextual, no se creará una tarjeta nueva y se mostrará la tarjeta actual.

Las acciones de Navigation que se describen a continuación solo actúan en tarjetas del mismo contexto; por ejemplo, popToRoot() de una tarjeta contextual solo muestra todas las demás tarjetas contextuales y no afectarán a las tarjetas de la página principal.

Por el contrario, el botón siempre está disponible para que el usuario navegue desde tus tarjetas contextuales a las no contextuales.

Para crear transiciones entre tarjetas, puedes agregar o quitar tarjetas de las pilas de tarjetas. La clase Navigation proporciona funciones para enviar y extraer tarjetas de las pilas. Si deseas compilar una navegación eficaz por tarjetas, configura tus widgets de modo que usen acciones de navegación. Puedes enviar o mostrar varias tarjetas a la vez, pero no puedes quitar la tarjeta inicial de la página principal que se inserta primero en la pila cuando se inicia el complemento.

Para navegar a una tarjeta nueva en respuesta a una interacción del usuario con un widget, sigue estos pasos:

  1. Crea un objeto Action y asócialo con una función de devolución de llamada que definas.
  2. Llama a la función del controlador de widgets adecuada del widget para configurar Action en ese widget.
  3. Implementa la función de devolución de llamada que realiza la navegación. A esta función se le asigna un objeto de evento de acción como argumento y debe hacer lo siguiente:
    1. Crea un objeto Navigation para definir el cambio de tarjeta. Un solo objeto Navigation puede contener varios pasos de navegación, que se realizan en el orden en que se agregan al objeto.
    2. Compila un objeto ActionResponse mediante la clase ActionResponseBuilder y el objeto Navigation.
    3. Muestra el elemento ActionResponse compilado.

Cuando compilas controles de navegación, usas las siguientes funciones del objeto Navigation:

Función Descripción
Navigation.pushCard(Card) Envía una tarjeta a la pila actual. Para ello, primero debes compilar la tarjeta por completo.
Navigation.popCard() Quita una tarjeta de la parte superior de la pila. Equivale a hacer clic en la flecha hacia atrás en la fila del encabezado del complemento. Esta acción no quita las tarjetas raíz.
Navigation.popToRoot() Quita todas las tarjetas de la pila, excepto la tarjeta raíz. Básicamente, restablece esa pila de tarjetas.
Navigation.popToNamedCard(String) Abre las tarjetas de la pila hasta llegar a una con el nombre determinado o la tarjeta raíz de la pila. Puedes asignar nombres a las tarjetas con la función CardBuilder.setName(String).
Navigation.updateCard(Card) Realiza un reemplazo in situ de la tarjeta actual y actualiza su visualización en la IU.

Si una interacción del usuario o un evento deberían generar que las tarjetas se vuelvan a renderizar en el mismo contexto, usa los métodos Navigation.pushCard(), Navigation.popCard() y Navigation.updateCard() para reemplazar las tarjetas existentes. Si una interacción del usuario o un evento provocan que las tarjetas se vuelvan a renderizar en un contexto diferente, usa ActionResponseBuilder.setStateChanged() para forzar la reejecución del complemento en esos contextos.

Los siguientes son ejemplos de navegación:

  • Si una interacción o un evento cambia el estado de la tarjeta actual (por ejemplo, agregar una tarea a una lista de tareas), usa updateCard().
  • Si una interacción o un evento proporcionan más detalles o le solicita al usuario acciones adicionales (por ejemplo, hacer clic en el título de un elemento para ver más detalles o presionar un botón para crear un evento de calendario nuevo), usa pushCard() para mostrar la página nueva y, al mismo tiempo, permitir que el usuario salga de ella con el botón Atrás.
  • Si una interacción o un evento actualiza el estado de una tarjeta anterior (por ejemplo, si actualizas el título de un elemento con la vista detallada), usa opciones como popCard(), popCard(), pushCard(previous) y pushCard(current) para actualizar la tarjeta anterior y la actual.

Actualizando tarjetas

Los complementos de Google Workspace permiten a los usuarios actualizar tu tarjeta volviendo a ejecutar la función del activador de Apps Script registrada en tu manifiesto. Los usuarios activan esta actualización a través de un elemento de menú de complementos:

Barra lateral del complemento de Google Workspace

Esta acción se agrega automáticamente a las tarjetas generadas por las funciones de activación homepageTrigger o contextualTrigger, como se especifica en el archivo de manifiesto de tu complemento (las "raíces" de las pilas de tarjetas contextuales y no contextuales).

Cómo devolver varias tarjetas

Ejemplo de tarjeta de complemento

Las funciones de la página principal o del activador contextual se usan para compilar y mostrar un solo objeto Card o un array de objetos Card que muestra la IU de la aplicación.

Si solo hay una tarjeta, se agrega a la pila no contextual o contextual como la tarjeta raíz y la IU de la aplicación host la muestra.

Si el array que se muestra incluye más de un objeto Card compilado, la aplicación host mostrará una tarjeta nueva, que contiene una lista del encabezado de cada tarjeta. Cuando el usuario hace clic en cualquiera de esos encabezados, la IU muestra la tarjeta correspondiente.

Cuando el usuario selecciona una tarjeta de la lista, esta se envía a la pila actual y la aplicación host la muestra. El botón dirige al usuario a la lista de encabezados de la tarjeta.

Esta disposición "plana" de tarjetas puede funcionar bien si tu complemento no necesita ninguna transición entre las tarjetas que crees. Sin embargo, en la mayoría de los casos, es mejor definir las transiciones de tarjetas directamente y hacer que las funciones de la página principal y del activador contextual muestren un solo objeto de tarjeta.

Ejemplo

A continuación, se incluye un ejemplo que muestra cómo construir varias tarjetas con botones de navegación que se alternan entre ellas. Estas tarjetas se pueden agregar a la pila contextual o no contextual si envías la tarjeta que muestra createNavigationCard() dentro o fuera de un contexto en particular.

  /**
   *  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();
  }