Pular para o conteúdo
murad.guide

As Três Camadas

Capítulo 19

O DOM e os eventos

5 min de leitura 833 palavras 1 gravura

Você já usou JavaScript no console e em páginas. Agora vamos entender como o programa encontra e altera os elementos da página. O DOM (Document Object Model, modelo de objeto do documento) é a árvore que o navegador monta a partir do HTML. Cada tag vira um nó. O script pega um nó, muda texto, classe ou atributo, e a tela acompanha.

O exemplo está em exemplos/19-dom/. É um interruptor: o botão acende e apaga o fundo. Enter e Espaço também alternam quando o botão está focado. O navegador já oferece esse comportamento ao button.

Trabalhe na cópia inicio desta etapa. Leia e digite o código indicado; mude apenas uma coisa. Antes de recarregar, escreva o resultado que espera. Depois compare com a referência completa ao lado. Se algo falhar, confira primeiro o arquivo salvo e o caminho aberto.

O que você vai aprender

A árvore da página

O arquivo HTML no disco é texto. O navegador lê esse texto e monta uma árvore. document é o objeto global que representa essa árvore. Filhos de body são cabeçalho, main, botões, parágrafos.

Quando o script roda depois do HTML, os nós já existem. Por isso o <script src="script.js"> fica no fim do body neste exemplo. Se o script rodar no head sem esperar, getElementById devolve null.

Erro comum: o script no head tenta pegar #interruptor antes do botão nascer. A página quebra com “Cannot read properties of null”. Mova o script para o fim do body ou use o evento DOMContentLoaded.

Árvore do documento: querySelector acha o primeiro nó; o evento espera o clique

querySelector e querySelectorAll

querySelector recebe um seletor CSS e devolve o primeiro elemento que combina.

const botao = document.querySelector("#interruptor");
const status = document.querySelector("#status");

#interruptor é o id. .aceso seria uma classe. button seria o tipo.

querySelectorAll devolve todos os que combinam, numa lista parecida com array. Você percorre com for e length.

const alvos = document.querySelectorAll(".js-alvo");
for (let i = 0; i < alvos.length; i += 1) {
  alvos[i].textContent = "Atualizado";
}

Se ninguém combinar, querySelector devolve null. querySelectorAll devolve uma lista vazia, não null. Sempre confira antes de usar o resultado.

textContent e classList

textContent lê ou grava o texto visível do nó. Não interpreta HTML. Para um status de lâmpada, é o que você quer.

classList é a lista de classes CSS daquele elemento. add, remove e toggle mexem nela sem apagar as outras classes.

document.body.classList.toggle("aceso", aceso);
status.textContent = aceso
  ? "A lâmpada está acesa."
  : "A lâmpada está apagada.";
botao.textContent = aceso ? "Apagar" : "Acender";

O segundo argumento de toggle força o estado: true adiciona, false tira. O CSS do exemplo pinta o fundo quando body tem .aceso.

Não escreva className = "aceso" se o elemento já tem outras classes: você as apagaria todas.

addEventListener: click e keydown

Um evento é um aviso: a pessoa clicou, apertou uma tecla, a imagem carregou. Um ouvinte (addEventListener) diz qual função roda quando aquele aviso chega.

const botao = document.getElementById("interruptor");
let aceso = false;

function alternar() {
  aceso = !aceso;
  document.body.classList.toggle("aceso", aceso);
  status.textContent = aceso
    ? "A lâmpada está acesa."
    : "A lâmpada está apagada.";
  botao.textContent = aceso ? "Apagar" : "Acender";
}

botao.addEventListener("click", alternar);

!aceso inverte o boolean: ligado vira desligado.

keydown dispara quando a tecla é pressionada. O objeto evento traz evento.key ("Enter", " " para espaço).

// O button já transforma Enter e Espaço em clique.

Com o foco no botão, Enter e Espaço já disparam o clique. Basta escutar o evento click; um segundo atalho poderia alternar o estado duas vezes.

Pare e experimente. Clique no botão. Depois use Tab para deixar o foco no botão e aperte Enter. O fundo deve alternar nas duas ações. Recarregue se o script não atualizar.

O interruptor completo

No HTML do exemplo:

<p id="status">A lâmpada está apagada.</p>
<p>
  <button type="button" id="interruptor">Acender</button>
</p>

type="button" impede o botão de se comportar como envio de formulário. O rótulo muda no JavaScript, então quem usa leitor de tela ouve o estado atual.

O teclado não é extra: é o mesmo interruptor. Se só o mouse funciona, a página ainda não está pronta.

Erro comum: escutar keyup esperando o espaço e ver a página rolar. Prefira o botão nativo: focado, ele oferece Enter e Espaço sem um atalho global que atrapalhe links.

Atividades

  1. Qual a diferença entre querySelector e querySelectorAll?
  2. Por que o script deste exemplo fica no fim do body?
  3. O que classList.toggle("aceso", false) faz?
  4. No exemplo, mude a mensagem acesa para “O terreiro está iluminado.” Recarregue e clique. O que você espera ver?

Respostas comentadas

  1. O primeiro devolve um nó ou null. O segundo devolve uma lista com zero ou mais nós. Para um interruptor, um id basta. Para vários cartões, use All.
  2. O HTML do botão e do parágrafo precisa existir antes do script perguntar por eles. No fim do body, a árvore já está montada.
  3. Tira a classe aceso se ela estiver lá. O segundo argumento false significa “não quero essa classe”.
  4. O parágrafo #status mostra a frase nova quando aceso é verdadeiro. O fundo continua dependendo da classe no body.

Referências

O arquivo completo

Referência do exemplo 19-dom, reproduzida aqui para que você possa conferir sem sair da página.

index.html

<!DOCTYPE html>

<html lang="pt-BR">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>DOM</title>
<link href="lab.css" rel="stylesheet"/>
<style>
      .aceso { background: #c45c26; color: #fff; padding: 1rem; }
    </style>
<style>body{overflow-wrap:anywhere}img,input,textarea,select,iframe{max-width:100%}img{height:auto}fieldset,.cartao{min-width:0}pre{max-width:100%;overflow:auto}:focus-visible{outline:3px solid #005fcc;outline-offset:3px}.atalho-conteudo{position:absolute;left:.5rem;top:-6rem;background:white;color:#172f50;padding:.5rem;z-index:999}.atalho-conteudo:focus{top:.5rem}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}</style></head>
<body><a class="atalho-conteudo" href="#conteudo">Pular para o conteúdo</a><main id="conteudo" tabindex="-1">
<h1>O DOM e os eventos</h1>
<p id="status">A lâmpada está apagada.</p>
<p>
<button id="interruptor" type="button">Acender</button>
</p>

</main><script src="script.js"></script></body>
</html>

script.js

const status = document.getElementById("status");
const botao = document.getElementById("interruptor");
let aceso = false;

function alternar() {
  aceso = !aceso;
  document.body.classList.toggle("aceso", aceso);
  status.textContent = aceso ? "A lâmpada está acesa." : "A lâmpada está apagada.";
  botao.textContent = aceso ? "Apagar" : "Acender";
}

botao.addEventListener("click", alternar);