Pular para o conteúdo

A primeira escola especializada em tecnologia do Alto Solimões, com corpo técnico formado na área.

Módulo 5: JavaScript na página

Eventos: clique, digitação e envio

Reagir ao que a pessoa faz

Registrar um ouvinte

const botao = document.querySelector("#calcular");

botao.addEventListener("click", () => {
console.log("clicou");
});

Três coisas: o elemento, o nome do evento, e a função que roda quando ele acontece.

Os eventos mais usados

// Clique
botao.addEventListener("click", aoClicar);

// Campo mudou - a cada tecla
campo.addEventListener("input", aoDigitar);

// Campo mudou e a pessoa saiu dele
campo.addEventListener("change", aoMudar);

// Formulário enviado
formulario.addEventListener("submit", aoEnviar);

// Teclado
document.addEventListener("keydown", aoTeclar);

// Foco
campo.addEventListener("focus", aoFocar);
campo.addEventListener("blur", aoSair);

O objeto do evento

A função recebe um objeto com informações sobre o que aconteceu:

campo.addEventListener("input", (event) => {
console.log(event.target.value); // o valor atual
console.log(event.type); // "input"
});

document.addEventListener("keydown", (event) => {
if (event.key === "Escape") fecharModal();
if (event.key === "Enter" && event.ctrlKey) enviar();
});

event.target é o elemento em que o evento aconteceu.

preventDefault

const formulario = document.querySelector("#inscricao");

formulario.addEventListener("submit", (event) => {
event.preventDefault(); // sem isto, a página recarrega

const dados = new FormData(formulario);
console.log(Object.fromEntries(dados));
});

O comportamento padrão do submit é enviar o formulário e recarregar a página. Sem preventDefault, o seu código nem termina de rodar.

FormData com Object.fromEntries recolhe todos os campos de uma vez: usando o name de cada um como chave. É outra razão para todo campo ter name.

Delegação

Cem produtos, cem botões "remover". Cem ouvintes?

// Um ouvinte só, no pai
const lista = document.querySelector("#produtos");

lista.addEventListener("click", (event) => {
const botao = event.target.closest(".remover");
if (!botao) return; // clicou em outra coisa

const item = botao.closest(".produto");
const id = item.dataset.id;

removerProduto(id);
item.remove();
});

O evento sobe do elemento clicado até os ancestrais, e por isso o ouvinte no pai enxerga o clique no filho. closest descobre em qual filho foi.

Duas vantagens: um ouvinte em vez de cem, e, a que mais importa, funciona para itens criados depois, que um ouvinte registrado no começo nunca alcançaria.

data- para guardar informação

<li class="produto" data-id="acai" data-preco="2500">
Açaí
<button class="remover" aria-label="Remover açaí">✕</button>
</li>
const item = document.querySelector(".produto");
console.log(item.dataset.id); // → "acai"
console.log(Number(item.dataset.preco)); // → 2500

data-preco vira dataset.preco. E é sempre texto: converta antes de usar em conta.

O teclado não pode ficar de fora

// Ruim: só funciona com mouse
const cartao = document.querySelector(".cartao");
cartao.addEventListener("click", abrirDetalhe);

// Bom: é um botão de verdade
<button class="cartao" type="button">Ver detalhes</button>

Um <button> já recebe foco pelo Tab, já responde ao Enter e ao espaço, e já é anunciado como botão pelo leitor de tela. Uma <div> com click não faz nenhuma das três: e para igualar seria preciso tabindex, role e um ouvinte de teclado, dos quais você vai esquecer pelo menos um.

Uma calculadora inteira

<form id="frete">
<label for="peso">Peso em quilos</label>
<input type="number" id="peso" name="peso" min="0" step="0.1" required />

<label for="destino">Destino</label>
<select id="destino" name="destino">
<option value="tabatinga">Tabatinga</option>
<option value="atalaia">Atalaia do Norte</option>
<option value="manaus">Manaus</option>
</select>

<button type="submit">Calcular</button>
</form>

<p id="resultado" role="status"></p>
const PRECO_POR_KG_EM_CENTAVOS = {
tabatinga: 350,
atalaia: 250,
manaus: 1200,
};

const TAXA_FIXA_EM_CENTAVOS = 500;

const formatar = (centavos) =>
(centavos / 100).toLocaleString("pt-BR", { style: "currency", currency: "BRL" });

function calcularFrete(pesoEmKg, destino) {
const precoPorKg = PRECO_POR_KG_EM_CENTAVOS[destino];
if (precoPorKg === undefined) return null;
if (pesoEmKg <= 0) return null;

return Math.ceil(pesoEmKg) * precoPorKg + TAXA_FIXA_EM_CENTAVOS;
}

const formulario = document.querySelector("#frete");
const resultado = document.querySelector("#resultado");

formulario?.addEventListener("submit", (event) => {
event.preventDefault();

const dados = new FormData(formulario);
const peso = Number(dados.get("peso"));
const destino = String(dados.get("destino"));

const freteEmCentavos = calcularFrete(peso, destino);

if (freteEmCentavos === null) {
resultado.textContent = "Informe um peso maior que zero.";
return;
}

resultado.textContent = `Frete para ${peso} kg: ${formatar(freteEmCentavos)}`;
});

Repare em três coisas:

calcularFrete é pura. Ela não conhece a página: recebe dois números e devolve um. Dá para testá-la sozinha, e é onde a regra de negócio mora.

role="status" no parágrafo do resultado faz o leitor de tela anunciar o valor quando ele muda. Sem isso, quem não vê a tela não fica sabendo que a conta foi feita.

A guarda ?. evita o erro caso o formulário não exista naquela página.

Remover um ouvinte

function aoClicar() {
console.log("uma vez só");
}

botao.addEventListener("click", aoClicar);
botao.removeEventListener("click", aoClicar);

// Ou, para um evento único
botao.addEventListener("click", aoClicar, { once: true });

removeEventListener precisa da mesma referência de função: o que não funciona com arrow escrita direto na chamada.

Exercícios

  1. Registre um clique num botão que mostre uma mensagem.
  2. Acompanhe a digitação num campo com input e mostre o valor em tempo real.
  3. Trate o submit de um formulário com preventDefault e FormData.
  4. Use delegação para tratar cliques em itens de uma lista.
  5. Guarde um id em data-id e leia-o pelo dataset.
  6. Feche uma caixa com a tecla Escape.
  7. Monte a calculadora de frete completa, com role="status" no resultado.
  8. Troque uma <div> clicável por um <button> e navegue com o teclado.

Referências

Testa o que você leu

5 perguntas sobre esta aula. Errar aqui não custa nada - a explicação vem junto da correção.

Pergunta 1 de 5
Qual é a forma recomendada de registrar um evento?