Trabalhar com ícones de menu suspenso

A API Google Docs permite criar, inserir, atualizar, ler e gerenciar ícones de menu suspenso de forma programática em documentos do Google Docs.

O que são ícones de menu suspenso?

Os ícones de menu suspenso no Google Docs oferecem aos usuários um menu de seleção interativo e personalizável em linha com o texto do documento. Os usuários podem clicar em um ícone de menu suspenso para selecionar em uma lista predefinida de opções, cada uma com seu próprio texto de exibição e estilo de cor. Os ícones de menu suspenso são usados com frequência para rastreamento de projetos, atualizações de status, fluxos de trabalho de revisão e etapas de aprovação.

Com a API Docs, você pode:

  • Defina modelos de menu suspenso reutilizáveis com títulos, nomes de opções e cores personalizados.
  • Insira ícones de menu suspenso em um local de caractere válido.
  • Atualiza a opção selecionada de uma instância de ícone de menu suspenso individual.
  • Modifique as definições de menus suspensos compartilhados no lugar, atualizando todos os chips de referência simultaneamente.
  • Substitua ou desative opções com segurança, mantendo a integridade referencial em todos os chips atuais.
  • Exclua modelos de menu suspenso não usados.

Arquitetura: definições e instâncias

Na API Docs, os ícones de menu suspenso têm definições e instâncias. Uma definição define opções para uma instância de menu suspenso. Uma instância de menu suspenso é um menu com que as pessoas podem interagir e que salva informações sobre as seleções.

A API Docs separa a configuração do modelo de menu suspenso das instâncias de ícone de menu suspenso inline:

  1. DropdownDefinition: um modelo no nível da guia que define o título do menu suspenso e a coleção de opções selecionáveis (DropdownOption). Ele não reside em um deslocamento de caractere específico no documento. Em vez disso, é armazenado no mapa de definição da guia: document.tabs[].documentTab.dropdownDefinitions.
  2. Menu suspenso: uma instância de chip individual incorporada inline em um elemento de parágrafo (ParagraphElement.dropdown). Cada instância de menu suspenso faz referência a um dropdownDefinitionId e armazena seu próprio selectedOptionId ativo.

Ao modificar um DropdownDefinition (por exemplo, adicionar uma opção ou renomear o título), todos os ícones de referência na guia são atualizados sem exigir atualizações em cada elemento individual.

Cada instância de Dropdown rastreia o próprio selectedOptionId. A mutação do valor selecionado de um chip individual afeta apenas essa instância específica.

Formato e regras de validação de ID

Os IDs fornecidos pelo usuário precisam obedecer a especificações rigorosas de formato, prefixo e comprimento:

Identificador Prefixo obrigatório Expressão regular de validação Limite de tamanho Exemplo
ID da definição do menu suspenso (dropdownDefinitionId) kix. ^kix\.[a-zA-Z0-9_-]{2,14}$ 6 a 18 caracteres kix.review_status
ID da opção do menu suspenso (optionId) dropdownItem. ^dropdownItem\.[a-zA-Z0-9_-]{2,14}$ 15 a 27 caracteres dropdownItem.pending

Gerar IDs fornecidos pelo usuário

Para gerar IDs que atendam aos requisitos obrigatórios de prefixo e expressão regular, use as seguintes funções auxiliares. Esses helpers geram sufixos alfanuméricos de base 36 em letras minúsculas, correspondentes ao formato gerado pela interface do Google Docs:

Python

import random
import string

def generate_dropdown_definition_id(suffix_len: int = 8) -> str:
    """Generates a valid user-provided DropdownDefinition ID (6-18 chars, starting with 'kix.')."""
    chars = string.ascii_lowercase + string.digits
    suffix = "".join(random.choices(chars, k=max(2, min(suffix_len, 14))))
    return f"kix.{suffix}"

def generate_dropdown_option_id(suffix_len: int = 8) -> str:
    """Generates a valid user-provided DropdownOption ID (15-27 chars, starting with 'dropdownItem.')."""
    chars = string.ascii_lowercase + string.digits
    suffix = "".join(random.choices(chars, k=max(2, min(suffix_len, 14))))
    return f"dropdownItem.{suffix}"

Java

import java.security.SecureRandom;

public final class DropdownIdGenerator {
  private static final String BASE36_CHARS =
      "abcdefghijklmnopqrstuvwxyz0123456789";
  private static final SecureRandom RANDOM = new SecureRandom();

  private DropdownIdGenerator() {}

  /** Generates a valid user-provided DropdownDefinition ID (6-18 chars, starting with "kix."). */
  public static String generateDefinitionId(int suffixLength) {
    int length = Math.max(2, Math.min(suffixLength, 14));
    StringBuilder sb = new StringBuilder("kix.");
    for (int i = 0; i < length; i++) {
      sb.append(BASE36_CHARS.charAt(RANDOM.nextInt(BASE36_CHARS.length())));
    }
    return sb.toString();
  }

  /** Generates a valid user-provided DropdownOption ID (15-27 chars, starting with "dropdownItem."). */
  public static String generateOptionId(int suffixLength) {
    int length = Math.max(2, Math.min(suffixLength, 14));
    StringBuilder sb = new StringBuilder("dropdownItem.");
    for (int i = 0; i < length; i++) {
      sb.append(BASE36_CHARS.charAt(RANDOM.nextInt(BASE36_CHARS.length())));
    }
    return sb.toString();
  }
}

Criar e inserir ícones de menu suspenso

Criação de lote único (recomendado)

O padrão mais eficiente combina CreateDropdownDefinitionRequest e InsertDropdownRequest em uma chamada documents.batchUpdate.

O exemplo de código a seguir cria uma definição de menu suspenso "Status da revisão" com três opções codificadas por cores usando IDs fornecidos pelo usuário e insere uma instância no final do documento:

Python

requests = [
    {
        "createDropdownDefinition": {
            "dropdownDefinition": {
                "dropdownDefinitionId": "kix.review_status",
                "dropdownDefinitionProperties": {
                    "title": "Review Status",
                    "options": [
                        {
                            "optionId": "dropdownItem.pending",
                            "displayValue": "Pending Review",
                            "textStyle": {
                                "backgroundColor": {
                                    "color": {"rgbColor": {"red": 0.99, "green": 0.90, "blue": 0.65}}
                                },
                                "foregroundColor": {
                                    "color": {"rgbColor": {"red": 0.45, "green": 0.30, "blue": 0.0}}
                                },
                            },
                        },
                        {
                            "optionId": "dropdownItem.approved",
                            "displayValue": "Approved",
                            "textStyle": {
                                "backgroundColor": {
                                    "color": {"rgbColor": {"red": 0.85, "green": 0.95, "blue": 0.85}}
                                },
                                "foregroundColor": {
                                    "color": {"rgbColor": {"red": 0.08, "green": 0.40, "blue": 0.15}}
                                },
                            },
                        },
                        {
                            "optionId": "dropdownItem.rejected",
                            "displayValue": "Needs Changes",
                            "textStyle": {
                                "backgroundColor": {
                                    "color": {"rgbColor": {"red": 0.98, "green": 0.84, "blue": 0.84}}
                                },
                                "foregroundColor": {
                                    "color": {"rgbColor": {"red": 0.65, "green": 0.10, "blue": 0.10}}
                                },
                            },
                        },
                    ],
                },
            }
        }
    },
    {
        "insertDropdown": {
            "endOfSegmentLocation": {},
            "dropdownDefinitionId": "kix.review_status",
            "selectedOptionId": "dropdownItem.pending",
        }
    },
]

result = service.documents().batchUpdate(
    documentId=DOCUMENT_ID,
    body={"requests": requests}
).execute()

Java

List<Request> requests = new ArrayList<>();

List<DropdownOption> options = Arrays.asList(
    new DropdownOption()
        .setOptionId("dropdownItem.pending")
        .setDisplayValue("Pending Review")
        .setTextStyle(new TextStyle()
            .setBackgroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.99f).setGreen(0.90f).setBlue(0.65f))))
            .setForegroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.45f).setGreen(0.30f).setBlue(0.0f))))),
    new DropdownOption()
        .setOptionId("dropdownItem.approved")
        .setDisplayValue("Approved")
        .setTextStyle(new TextStyle()
            .setBackgroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.85f).setGreen(0.95f).setBlue(0.85f))))
            .setForegroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.08f).setGreen(0.40f).setBlue(0.15f))))),
    new DropdownOption()
        .setOptionId("dropdownItem.rejected")
        .setDisplayValue("Needs Changes")
        .setTextStyle(new TextStyle()
            .setBackgroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.98f).setGreen(0.84f).setBlue(0.84f))))
            .setForegroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.65f).setGreen(0.10f).setBlue(0.10f))))));

DropdownDefinition definition = new DropdownDefinition()
    .setDropdownDefinitionId("kix.review_status")
    .setDropdownDefinitionProperties(new DropdownDefinitionProperties()
        .setTitle("Review Status")
        .setOptions(options));

requests.add(new Request().setCreateDropdownDefinition(
    new CreateDropdownDefinitionRequest().setDropdownDefinition(definition)));

requests.add(new Request().setInsertDropdown(
    new InsertDropdownRequest()
        .setEndOfSegmentLocation(new EndOfSegmentLocation())
        .setDropdownDefinitionId("kix.review_status")
        .setSelectedOptionId("dropdownItem.pending")));

BatchUpdateDocumentRequest body = new BatchUpdateDocumentRequest().setRequests(requests);
BatchUpdateDocumentResponse response = docsService.documents()
    .batchUpdate(DOCUMENT_ID, body)
    .execute();

Regras e limites de validação de menus suspensos

Ao criar ou modificar definições de menu suspenso, as seguintes restrições se aplicam:

  • Contagem de opções: uma definição de menu suspenso precisa ter entre 2 e 50 opções.
  • Tamanho do título: a definição title não pode estar vazia e não pode exceder 200 caracteres.
  • Comprimento do valor de exibição: cada displayValue de opção não pode estar vazio e não pode exceder 200 caracteres.
  • Estilo de opção: apenas foregroundColor e backgroundColor são compatíveis com DropdownOption.textStyle. Definir outras propriedades de estilo retorna um erro 400 Bad Request.
  • Seleção padrão: em InsertDropdownRequest, se selectedOptionId for omitido, o ícone vai usar a primeira opção definida em DropdownDefinition.

Atualizar a seleção de um ícone de menu suspenso individual

Para mudar a opção selecionada de uma instância de ícone de menu suspenso sem alterar o modelo ou outros ícones, use UpdateDropdownPropertiesRequest:

  • dropdownId: (obrigatório) o ID da instância específica do ícone de menu suspenso a ser atualizada.
  • tabId: o ID da guia que contém o menu suspenso (o padrão é a primeira guia se omitido).
  • Defina fields como "selectedOptionId".

O exemplo de código a seguir atualiza a seleção ativa de um ícone de menu suspenso para "dropdownItem.approved":

Python

requests = [
    {
        "updateDropdownProperties": {
            "dropdownId": "kix.chip_abc1",
            "tabId": "t.0",
            "dropdownProperties": {
                "selectedOptionId": "dropdownItem.approved",
            },
            "fields": "selectedOptionId",
        }
    }
]

result = service.documents().batchUpdate(
    documentId=DOCUMENT_ID,
    body={"requests": requests}
).execute()

Java

List<Request> requests = new ArrayList<>();
requests.add(new Request().setUpdateDropdownProperties(
    new UpdateDropdownPropertiesRequest()
        .setDropdownId("kix.chip_abc1")
        .setTabId("t.0")
        .setDropdownProperties(new DropdownProperties()
            .setSelectedOptionId("dropdownItem.approved"))
        .setFields("selectedOptionId")));

BatchUpdateDocumentRequest body = new BatchUpdateDocumentRequest().setRequests(requests);
BatchUpdateDocumentResponse response = docsService.documents()
    .batchUpdate(DOCUMENT_ID, body)
    .execute();

Atualizar definições e opções do menu suspenso

Para modificar o modelo em si (título, lista de opções, rótulos de exibição ou cores), use UpdateDropdownDefinitionPropertiesRequest. Atualizar uma definição atualiza automaticamente todos os ícones de menu suspenso que fazem referência a ela na guia.

Substituição completa da lista de opções

Quando dropdownDefinitionProperties.options é incluído na máscara fields, a solicitação realiza uma substituição completa da lista. Você precisa fornecer a lista completa de opções na ordem escolhida. O servidor diferencia a lista recebida da definição atual:

  • Nova opção: incluir uma opção sem um optionId (ou com um novo optionId fornecido pelo usuário) adiciona essa opção à definição.
  • Opção atualizada: fornecer um optionId com displayValue ou textStyle modificados atualiza essa opção no lugar.
  • Opções reordenadas: a lista de opções é salva na sequência exata fornecida na solicitação.
  • Opção excluída: omitir um optionId existente exclui essa opção da definição.

Integridade referencial e substituição de opções

Se uma opção excluída estiver selecionada em um ícone de menu suspenso no documento, você precisa fornecer um mapeamento em selectedOptionIdReplacements (map<string, string>). As chaves são os IDs das opções excluídas, e os valores são os IDs das opções de substituição.

O exemplo de código a seguir demonstra como atualizar uma definição de menu suspenso com substituições de opções:

Python

requests = [
    {
        "updateDropdownDefinitionProperties": {
            "dropdownDefinitionId": "kix.review_status",
            "tabId": "t.0",
            "dropdownDefinitionProperties": {
                "title": "Editorial Review Status",
                "options": [
                    {
                        "optionId": "dropdownItem.approved",
                        "displayValue": "Approved",
                        "textStyle": {
                            "backgroundColor": {
                                "color": {"rgbColor": {"red": 0.85, "green": 0.95, "blue": 0.85}}
                            },
                            "foregroundColor": {
                                "color": {"rgbColor": {"red": 0.08, "green": 0.40, "blue": 0.15}}
                            },
                        },
                    },
                    {
                        "optionId": "dropdownItem.rejected",
                        "displayValue": "Changes Requested",
                        "textStyle": {
                            "backgroundColor": {
                                "color": {"rgbColor": {"red": 0.98, "green": 0.84, "blue": 0.84}}
                            },
                            "foregroundColor": {
                                "color": {"rgbColor": {"red": 0.65, "green": 0.10, "blue": 0.10}}
                            },
                        },
                    },
                ],
            },
            "selectedOptionIdReplacements": {
                "dropdownItem.pending": "dropdownItem.rejected"
            },
            "fields": "title,options",
        }
    }
]

result = service.documents().batchUpdate(
    documentId=DOCUMENT_ID,
    body={"requests": requests}
).execute()

Java

List<DropdownOption> updatedOptions = Arrays.asList(
    new DropdownOption()
        .setOptionId("dropdownItem.approved")
        .setDisplayValue("Approved")
        .setTextStyle(new TextStyle()
            .setBackgroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.85f).setGreen(0.95f).setBlue(0.85f))))
            .setForegroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.08f).setGreen(0.40f).setBlue(0.15f))))),
    new DropdownOption()
        .setOptionId("dropdownItem.rejected")
        .setDisplayValue("Changes Requested")
        .setTextStyle(new TextStyle()
            .setBackgroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.98f).setGreen(0.84f).setBlue(0.84f))))
            .setForegroundColor(new OptionalColor().setColor(
                new Color().setRgbColor(new RgbColor().setRed(0.65f).setGreen(0.10f).setBlue(0.10f))))));

Map<String, String> replacements = new HashMap<>();
replacements.put("dropdownItem.pending", "dropdownItem.rejected");

List<Request> requests = new ArrayList<>();
requests.add(new Request().setUpdateDropdownDefinitionProperties(
    new UpdateDropdownDefinitionPropertiesRequest()
        .setDropdownDefinitionId("kix.review_status")
        .setTabId("t.0")
        .setDropdownDefinitionProperties(new DropdownDefinitionProperties()
            .setTitle("Editorial Review Status")
            .setOptions(updatedOptions))
        .setSelectedOptionIdReplacements(replacements)
        .setFields("title,options")));

BatchUpdateDocumentRequest body = new BatchUpdateDocumentRequest().setRequests(requests);
BatchUpdateDocumentResponse response = docsService.documents()
    .batchUpdate(DOCUMENT_ID, body)
    .execute();

Excluir uma definição de menu suspenso

Para remover um modelo de menu suspenso não usado, use DeleteDropdownDefinitionRequest.

O exemplo de código a seguir demonstra como excluir uma definição de menu suspenso:

Python

requests = [
    {
        "deleteDropdownDefinition": {
            "dropdownDefinitionId": "kix.review_status",
            "tabId": "t.0",
        }
    }
]

result = service.documents().batchUpdate(
    documentId=DOCUMENT_ID,
    body={"requests": requests}
).execute()

Java

List<Request> requests = new ArrayList<>();
requests.add(new Request().setDeleteDropdownDefinition(
    new DeleteDropdownDefinitionRequest()
        .setDropdownDefinitionId("kix.review_status")
        .setTabId("t.0")));

BatchUpdateDocumentRequest body = new BatchUpdateDocumentRequest().setRequests(requests);
BatchUpdateDocumentResponse response = docsService.documents()
    .batchUpdate(DOCUMENT_ID, body)
    .execute();

Tratamento de erros e solução de problemas

Condições de erro e soluções comuns:

Código de status Causa Resolução
400 INVALID_ARGUMENT Prefixo de ID ou formato de expressão regular inválido. Verifique se dropdownDefinitionId começa com kix. (6 a 18 caracteres) e optionId começa com dropdownItem. (15 a 27 caracteres).
400 INVALID_ARGUMENT A contagem de opções está fora dos limites. Uma definição de menu suspenso precisa ter entre 2 e 50 opções.
400 INVALID_ARGUMENT O título ou o valor de exibição está vazio ou excede 200 caracteres. Forneça uma string não vazia com 1 a 200 caracteres.
400 INVALID_ARGUMENT Propriedade de estilo de texto incompatível na opção. Somente foregroundColor e backgroundColor são compatíveis com opções de menu suspenso. Remova a fonte, o tamanho ou outros atributos.
400 INVALID_ARGUMENT Mapeamento de substituição de opção ausente na exclusão. Ao excluir uma opção selecionada por um ícone, forneça uma substituição válida em selectedOptionIdReplacements.
400 INVALID_ARGUMENT Tentativa de excluir uma definição em uso. Exclua ou redefina todas as instâncias de ícone de menu suspenso que fazem referência à definição antes de excluí-la.
400 INVALID_ARGUMENT ID duplicado fornecido pelo usuário. Verifique se os IDs fornecidos pelo usuário são exclusivos na guia "Documento".