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

O DOM: achar e mudar elementos

JavaScript conversando com a página

O que é o DOM

Quando o navegador lê o HTML, ele monta na memória uma árvore de objetos: o DOM. Cada tag vira um objeto que o JavaScript pode ler e alterar.

Alterar o DOM altera o que está na tela na hora, sem recarregar a página.

Achar elementos

// Um elemento, pelo seletor CSS
const titulo = document.querySelector("#titulo");
const primeiroCartao = document.querySelector(".cartao");
const botao = document.querySelector('button[type="submit"]');

// Todos os que casam
const cartoes = document.querySelectorAll(".cartao");

// A forma antiga, específica de id
const titulo2 = document.getElementById("titulo");

querySelector aceita qualquer seletor CSS: o mesmo que você aprendeu no curso anterior. É o suficiente para quase tudo.

const elemento = document.querySelector("#nao-existe");
console.log(elemento); // → null

elemento.textContent = "x"; // TypeError: Cannot read properties of null

Percorrer uma lista de elementos

const cartoes = document.querySelectorAll(".cartao");

for (const cartao of cartoes) {
cartao.classList.add("visivel");
}

console.log(cartoes.length);

querySelectorAll devolve uma NodeList, que não é um array: ela tem forEach e dá para percorrer com for...of, mas não tem map nem filter. Para usá-los:

const textos = [...cartoes].map((c) => c.textContent);

Ler e escrever texto

const titulo = document.querySelector("#titulo");

console.log(titulo.textContent);
titulo.textContent = "Feira aberta hoje";
const campo = document.querySelector("#quantidade");

console.log(campo.value); // "3" - sempre texto
console.log(Number(campo.value)); // 3
campo.value = "5";

Elemento comum usa textContent. Campo de formulário usa value.

textContent e innerHTML

const alvo = document.querySelector("#saida");

alvo.textContent = "<strong>Olá</strong>";
// → mostra literalmente: <strong>Olá</strong>

alvo.innerHTML = "<strong>Olá</strong>";
// → mostra Olá em negrito

innerHTML interpreta o texto como HTML. Isso é útil; e perigoso:

// PERIGOSO: o comentário veio de outra pessoa
const comentario = pegarComentarioDoServidor();
alvo.innerHTML = comentario;

Se o comentário contiver <img src=x onerror="...">, o código embutido roda no navegador de quem abrir a página, com o acesso que a pessoa tem. É o ataque chamado XSS.

Atributos, classes e estilo

const imagem = document.querySelector("#foto");

imagem.setAttribute("src", "feira.jpg");
imagem.setAttribute("alt", "Bancas de frutas na orla");
console.log(imagem.getAttribute("alt"));

const cartao = document.querySelector(".cartao");

cartao.classList.add("destaque");
cartao.classList.remove("oculto");
cartao.classList.toggle("aberto"); // liga/desliga
console.log(cartao.classList.contains("destaque")); // → true

Prefira mexer em classes a mexer em style direto:

// Evite: o estilo fica espalhado entre o CSS e o JavaScript
cartao.style.backgroundColor = "yellow";
cartao.style.padding = "16px";

// Prefira: o estilo continua no CSS, e o JS só liga o estado
cartao.classList.add("destaque");

Criar elementos

const item = document.createElement("li");
item.textContent = "Farinha d'água - R$ 12,50/kg";
item.classList.add("produto");

const lista = document.querySelector("#produtos");
lista.append(item);
MétodoO que faz
appendno fim, dentro do elemento
prependno começo, dentro
beforeantes do elemento
afterdepois
removetira o próprio elemento
replaceWithtroca por outro

Muitos elementos de uma vez

const PRODUTOS = [
{ nome: "Farinha d'água", precoEmCentavos: 1250 },
{ nome: "Açaí", precoEmCentavos: 2500 },
{ nome: "Tucumã", precoEmCentavos: 800 },
];

const lista = document.querySelector("#produtos");
const fragmento = document.createDocumentFragment();

for (const produto of PRODUTOS) {
const item = document.createElement("li");
item.textContent = `${produto.nome} - R$ ${(produto.precoEmCentavos / 100).toFixed(2)}/kg`;
fragmento.append(item);
}

lista.replaceChildren(fragmento);

Duas coisas importam aqui:

O fragmento. Inserir cem itens um a um faz o navegador recalcular o layout cem vezes. Montar tudo num fragmento e inserir uma vez é um cálculo só: e num aparelho simples a diferença se nota.

replaceChildren. Limpa o que havia e põe o novo, numa operação. Substitui o antigo lista.innerHTML = "" seguido do preenchimento.

const item = document.querySelector(".produto");

item.parentElement; // o pai
item.children; // os filhos
item.nextElementSibling; // o irmão seguinte
item.closest(".cartao"); // o ancestral mais próximo que casa

closest é o mais útil dos quatro: a partir de um botão clicado, ele acha o cartão inteiro a que aquele botão pertence.

Uma listagem completa

<main>
<h1>Feira do produtor</h1>
<p id="resumo"></p>
<ul id="produtos"></ul>
</main>
const PRODUTOS = [
{ nome: "Farinha d'água", precoEmCentavos: 1250, estoqueEmKg: 40 },
{ nome: "Açaí", precoEmCentavos: 2500, estoqueEmKg: 15 },
{ nome: "Tucumã", precoEmCentavos: 800, estoqueEmKg: 0 },
];

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

function montarItem(produto) {
const item = document.createElement("li");
item.classList.add("produto");

const nome = document.createElement("strong");
nome.textContent = produto.nome;

const preco = document.createElement("span");
preco.textContent = ` - ${formatar(produto.precoEmCentavos)}/kg`;

item.append(nome, preco);

if (produto.estoqueEmKg === 0) {
item.classList.add("esgotado");
const aviso = document.createElement("em");
aviso.textContent = " (esgotado)";
item.append(aviso);
}

return item;
}

function renderizar(produtos) {
const lista = document.querySelector("#produtos");
const resumo = document.querySelector("#resumo");
if (!lista || !resumo) return;

const disponiveis = produtos.filter((p) => p.estoqueEmKg > 0);
resumo.textContent = `${disponiveis.length} de ${produtos.length} produtos disponíveis`;

const fragmento = document.createDocumentFragment();
for (const produto of produtos) {
fragmento.append(montarItem(produto));
}

lista.replaceChildren(fragmento);
}

renderizar(PRODUTOS);

Repare: tudo com textContent, nada com innerHTML. A guarda if (!lista) no começo evita o erro de null.

Exercícios

  1. Troque o texto de um <h1> pelo JavaScript.
  2. Selecione todos os elementos de uma classe e acrescente outra a cada um.
  3. Leia o valor de um campo e mostre-o num parágrafo.
  4. Escreva <strong>oi</strong> com textContent e com innerHTML. Compare.
  5. Crie uma lista de cinco itens a partir de um array, usando fragmento.
  6. Use closest para achar o cartão a partir de um elemento interno.
  7. Selecione um id que não existe e leia o erro ao usar o resultado.

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 diferença entre `querySelector` e `querySelectorAll`?