Programação Por Equipe DevBras

Capa do artigo: Campo com sugestões em HTML: datalist que ajuda sem virar cadastro bagunçado

Campo com sugestões em HTML: datalist que ajuda sem virar cadastro bagunçado

Use datalist para sugerir valores sem prender a pessoa, carregue opções do servidor sob demanda e normalize no backend para "São Paulo" e "Sao Paulo" não virarem duas cidades.

O relatório de vendas por cidade mostra "São Paulo", "Sao Paulo", "SAO PAULO" e "São Paulo " com espaço no fim. Quatro linhas para a mesma cidade, porque o campo era texto livre. Trocar por um select resolveria a grafia, mas trava quem mora numa cidade que não está na lista. A resposta curta: use <input list> com <datalist> para sugerir, e normalize no servidor para decidir o que é igual. A lista no HTML é conforto. A regra mora no backend.

O exemplo deste guia é o cadastro de uma transportadora com dois campos: cidade de entrega, que precisa bater com uma tabela, e cargo do contato, que aceita qualquer texto mas deve sair padronizado.

O que o datalist faz e o que ele não faz

O datalist liga um campo de texto comum a uma lista de opções. Ao focar ou digitar, o navegador mostra as opções que combinam com o que já foi escrito. A pessoa pode escolher uma delas ou continuar digitando outra coisa. O que vai para o servidor é sempre o texto que ficou no campo.

Três consequências práticas:

  • Não existe id escondido. Diferente do select, não há separação entre o que aparece e o que é enviado. Se você precisa do id da cidade, o servidor tem de encontrar esse id a partir do texto.
  • O filtro e a aparência variam por navegador. Cada um decide como compara o texto digitado com as opções e como desenha a lista. Não conte que digitar "Sao" vai sugerir "São Paulo" em todos; teste nos navegadores que o seu público usa, principalmente no celular.
  • Nada impede um valor fora da lista. O HTML não valida que o texto pertence às opções. Isso é intencional e é a vantagem do componente.

Quando usar datalist, select ou busca no servidor

Use select quando o valor é fechado e curto: estado, tipo de documento, status. Use datalist quando a maioria escolhe de uma lista conhecida, mas um valor novo é legítimo: cargo, marca, tag. Para catálogo grande e fechado, como os mais de cinco mil municípios do país, carregue sugestões do servidor conforme a pessoa digita e valide contra a tabela no envio.

Como montar o campo passo a passo

  1. Crie o rótulo e o campo. O label continua obrigatório; a lista não substitui o nome do campo.
  2. Ligue o campo à lista pelo atributo list, que recebe o id do datalist.
  3. Preencha as opções com os valores mais comuns, já na grafia oficial.
  4. Para listas grandes, busque no servidor a partir de duas letras, com um pequeno atraso entre teclas.
  5. No envio, normalize e decida: encontrou na tabela, grava o id; não encontrou, recusa (catálogo fechado) ou grava o texto limpo (catálogo aberto).

O HTML do cargo, com lista fixa

<label for="cargo">Cargo do contato</label>
<input id="cargo" name="cargo" list="cargos" maxlength="80">
<datalist id="cargos">
  <option value="Gerente de logística"></option>
  <option value="Comprador"></option>
  <option value="Analista fiscal"></option>
  <option value="Proprietário"></option>
</datalist>

A cidade, com sugestões vindas do servidor

<label for="cidade">Cidade de entrega</label>
<input id="cidade" name="cidade" list="cidades" autocomplete="address-level2" required>
<datalist id="cidades"></datalist>

<script>
const campo = document.getElementById('cidade');
const lista = document.getElementById('cidades');
let espera;

campo.addEventListener('input', () => {
  clearTimeout(espera);
  const termo = campo.value.trim();
  if (termo.length < 2) return;

  espera = setTimeout(async () => {
    const resp = await fetch('/api/cidades?q=' + encodeURIComponent(termo));
    if (!resp.ok) return;
    const nomes = await resp.json(); // ["São Paulo - SP", "São Pedro - SP", ...]
    lista.replaceChildren(...nomes.map((nome) => {
      const opcao = document.createElement('option');
      opcao.value = nome;
      return opcao;
    }));
  }, 250);
});
</script>

O autocomplete="address-level2" diz ao navegador que o campo é cidade, então o preenchimento automático de endereço também ajuda. O post sobre autocomplete certo explica esses valores. Note que o texto da opção inclui a UF: "São José - SC" e "São José - SP" são lugares diferentes, e a sugestão precisa deixar isso claro.

A decisão no servidor: normalizar e conferir

O texto chega como a pessoa deixou: com espaço duplo, sem acento, em caixa alta. A primeira etapa é limpar o que nunca tem significado. A segunda é comparar com a tabela de um jeito que ignore o que não deveria diferenciar.

<?php
$entrada = $_POST['cidade'] ?? '';
$entrada = trim(preg_replace('/\s+/u', ' ', $entrada));

[$nome, $uf] = array_pad(array_map('trim', explode(' - ', $entrada, 2)), 2, '');

$stmt = $pdo->prepare('SELECT id, nome, uf FROM cidades WHERE nome = ? AND uf = ?');
$stmt->execute([$nome, strtoupper($uf)]);
$cidade = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$cidade) {
    http_response_code(422);
    echo json_encode(['campo' => 'cidade', 'erro' => 'Escolha uma cidade da lista, no formato Cidade - UF.']);
    exit;
}

// grava $cidade['id'] no pedido, nunca o texto digitado

A comparação sem acento e sem diferença de maiúsculas fica por conta da collation da coluna. No MySQL 8, uma coluna utf8mb4 com collation utf8mb4_0900_ai_ci (o "ai" é insensível a acento, o "ci" a caixa) faz "sao paulo" bater com "São Paulo". Se a sua tabela usa outra collation, confira antes de confiar nisso; o post sobre acentos que quebram no banco ajuda a diagnosticar.

Para o cargo, que é catálogo aberto, o servidor faz só a limpeza e grava o texto. Se quiser reduzir variações com o tempo, rode uma consulta periódica agrupando cargos parecidos e acrescente os mais frequentes ao datalist.

Acessibilidade e celular

O campo com datalist é anunciado por leitores de tela como um campo com sugestões, mas o comportamento depende da combinação de navegador e leitor. Garanta o básico: rótulo visível, mensagem de erro ligada ao campo e possibilidade de completar o formulário só digitando. Não coloque instruções apenas nas opções. Se o formato exigido é "Cidade - UF", escreva isso perto do campo.

No celular, a lista pode aparecer como barra acima do teclado ou nem aparecer. Por isso o servidor precisa aceitar a entrada digitada à mão e responder com um erro claro quando não achar o valor. O guia formulário que não cansa trata da posição dessa mensagem.

Erros comuns

  • Tratar o datalist como validação. Qualquer texto passa, e o banco enche de variações do mesmo valor.
  • Gravar o texto quando a regra pede um id. O relatório agrupa errado e a cidade renomeada não atualiza os pedidos antigos.
  • Colocar milhares de opções no HTML. A página pesa, a lista fica impossível de navegar e alguns navegadores travam no celular.
  • Opções ambíguas. "São José" sem UF faz a pessoa escolher a cidade errada com toda a confiança.
  • Usar datalist para valor fechado e curto. Um select resolveria com menos código e sem erro de digitação.
  • Esquecer o label. Sem nome acessível, o leitor de tela anuncia só "campo de edição".

Checklist

  • Cada campo foi classificado como catálogo fechado ou texto com ajuda.
  • Campos fechados validam no servidor e gravam o id, não o texto.
  • O servidor remove espaços extras antes de comparar ou gravar.
  • A collation da coluna de busca ignora acento e caixa, ou a normalização faz isso no código.
  • Listas grandes vêm do servidor sob demanda, com atraso entre teclas.
  • As opções incluem o que desfaz ambiguidade, como a UF.
  • O formulário foi testado no celular com a lista desligada, digitando tudo à mão.

Rode um SELECT cidade, COUNT(*) FROM pedidos GROUP BY cidade no seu banco. Quantas grafias diferentes aparecem para a mesma cidade, e esse campo deveria ser catálogo fechado ou texto com ajuda?

Avalie este artigo

Referência: http://www.w3schools.com/tags/att_input_list.asp