View on GitHub

aulas-programacao-web

Materiais de Aula - Programação de Computadores com tecnologias Web

DOM - Document Object Model

📽 Veja esta vídeo-aula no Youtube

Ambiente de execução JavaScript

Códigos JavaScript são executados em um ambiente como um navegador (Chrome, Firefox), um servidor (Node, Deno) ou qualquer outro tipo de plataforma.

Quando executado em um navegador, é criada uma estrutura de objetos chamada BOM (Browser Object Model) que pode ser manipulada livremente. O objeto window é a raiz dessa estrutura, e contém informações sobre a janela e métodos para sua manipulação. É também o container para todos os objetos globais criados pela aplicação.

Contém, entre outras coisas, os objetos:

DOM - Document Object Model

É o nome dado à estrutura de objetos padronizada pelo WHATWG como ponto de acesso a todo o conteúdo do documento. O seu objeto raiz é document.

Em document existe todo o conteúdo do documento HTML de origem, incluíndo textos dentro e fora de tags, comentários e scripts, todos eles chamados de nós (nodes).

Você pode visualizar os nós de um documento nas ferramentas de desenvolvedor, aba Elements:

🍌 As ferramentas de desenvolvedor do navegador ocultam nós que só contenham espaços e/ou quebras de linha (por simplicidade), mas eles existem no DOM.

Para garantir a integridade do DOM algumas autocorreções são feitas:

Tudo no DOM são nós. Cada nó possui um tipo, quase sempre baseado na tag utilizada para declará-la. Eles seguem a seguinte hierarquia de classes:

Os principais tipos de nós são:

Em relação à sua posição na árvore, chamamos de:

Obtendo elementos do DOM

Alguns elementos possuem acesso direto. Por exemplo:

🍌 É criada automaticamente uma variável global para cada elemento com o atributo id, com o nome equivalente. Seu uso não é recomendado pois se outra variável for criada com o mesmo nome ela possui precedência.

Há alguns auxiliares específicos para tabelas:

De maneira geral, para acessar os um nó específico precisamos navegar pela árvore do DOM ou realizar uma busca.

NodeList e HTMLCollection

No DOM há dois tipos principais de coleções de nós. Elas podem ser estáticas (não reflete o estado atual do momento do acesso, mas do momento da criação da lista) ou vivas (reflete o estado atual do momento do acesso, não do momento da criação da lista).

Todos os nós permitem navegação, usando as propriedades/métodos abaixo. Atente-se ao fato de que todos eles são somente-para-leitura.

Todos os nós Somente elementos Descrição
childNodes children coleção com todos os filhos (childNodes é um NodeList e children é um HTMLCollection)
childNodes[n] children[n] n-ésimo filho
firstChild firstElementChild primeiro filho
lastChild lastElementChild último filho
parentNode parentElement o pai (document.documentElement.parentNode === document mas document.documentElement.parentElement === null)
nextSibling nextElementSibling o próximo irmão
previousSibling previousElementSibling o irmão anterior

Use:

Ex.:

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DOM</title>
  </head>
  <body>
    <h1>Aprendendo DOM</h1>
    <ul>
      <li>BOM</li>
      <li>Elementos</li>
      <li>Eventos</li>
    </ul>
    <p>Muito obrigado!</p>
  </body>
</html>

Obtendo um elemento usando o atributo id

Use document.getElementById('meuIdUnico') para obter o elemento de id igual a meuIdUnico, não importa em que parte do documento.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <div>
      <p>Este parágrafo não está identificado.</p>
      <p id="meuParagrafo">Este parágrafo está identificado.</p>
    </div>
  </body>

🍌 Se um id não é único, todas as técnicas de obtenção por id se tornam imprevisíveis.

Obtendo elementos usando seletores CSS

Em CSS você pode referenciar qualquer elemento ou conjunto de elementos utilizando os seletores. Podemos aproveitar esse conhecimento para obter elementos a partir de um elemento pai:

Qualquer seletor CSS pode ser utilizado com esses métodos.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <div>
      <p class="tipo1">Lorem ipsum dolor sit amet.</p>
      <p>Fugiat, natus. Culpa, modi porro.</p>
      <p id="diferente">Temporibus eligendi consequuntur id suscipit!</p>
      <p class="tipo2">Ut reiciendis ullam cumque ea.</p>
      <p class="tipo1 tipo2">Odit nobis doloremque quisquam ipsum.</p>
    </div>
  </body>

Outros métodos para obtenção de elementos

Os métodos da família getElement também permitem encontrar elementos no documento, porém são mais complexos e portanto menos utilizados que os baseados em seletores. Geralmente são encontrados em scripts mais antigos.

Na prática, use document.getElementById(id) e evite os demais.

Métodos no singular (getElement_) retornam o primeiro elemento, e no plural (getElements_) uma com todos os elementos (geralmente HTMLCollection, mas alguns navegadores antigos podem retornar um NodeList vivo).

Conteúdo de elementos

Podemos obter e alterar o conteúdo de um elemento usando innerHTML (conteúdo e de seus descendentes) e outerHTML (incluindo também a si próprio).

Para obter somente o texto, sem as tags, use textContent. Gravar usando essa propriedade garante que nenhuma tag será injetada.

🍌 Nós de texto ou comentários não possuem HTML, portanto use data.

🍌🍌 Para obter o valor alterável pelo usuário em elementos de formulário, use value.

Manipulando atributos

Nos elementos são criadas propriedades automaticamente para atributos existentes na especificação HTML. Também podemos usar os seguintes métodos:

Podemos iterar os atributos de um elemento elem usando a coleção elem.attributes. Cada item da coleção possui name (com seu nome) e value (com seu valor).

Os atributos padronizados (que fazem parte da especificação do HTML) possuem sempre um propriedade automática. Ao usar atributos não padronizados, prefira nomeá-los usando o prefixo data-, pois o seu HTML continuará válido. Os atributos data-* estarão disponíveis em elem.dataset.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <p>Linguagens aprendidas:</p>
    <ul id="linguagens">
      <li data-componente="PC1">C#</li>
      <li data-componente="PC2">HTML</li>
      <li data-componente="PC2">CSS</li>
      <li data-componente="PC2">JavaScript</li>
    </ul>
  </body>

Atributo hidden

O atributo hidden possui a mesma especificação de style=display:none. Quando true, o elemento não será visível.

Ex:

<div id="elem">Elemento piscante</div>
<script>
  setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>

Manipulando estilos

Para controlar as características visuais dos elementos podemos utilizar os estilos CSS via JavaScript. Há duas maneiras:

Estilos com classes

Podemos acessar o conteúdo do atributo class usando elem.className. Ele contém todas as classes, como uma string, assim como no atributo HTML.

A propriedade elem.classList contém uma coleção (um array-like, iterável com for...of ou .forEach) cujos itens são strings das classes aplicadas ao elemento. Ela também possui métodos facilitadores:

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DOM</title>
    <style>
      .negrito {
        font-weight: bold;
      }
      .vermelho {
        color: red;
      }
      .verde {
        color: green;
      }
    </style>
  </head>
  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <p class="vermelho">Lorem ipsum dolor sit amet.</p>
    <p class="negrito">Deleniti rem aperiam quisquam! Veniam?</p>
    <p id="semEstilizacao">Officiis cumque unde vel distinctio!</p>
    <p class="verde">Minus deserunt tenetur amet ratione.</p>
    <p class="vermelho negrito">Necessitatibus rerum ut recusandae. Impedit.</p>
  </body>
</html>

A técnica é definir um estilo padrão e criar estilos adicionais vinculados à classes. Adicione ou retire essas classes usando JavaScript.

Estilos com as propriedades style

Os elementos possuem uma propriedade style que por sua vez possui propriedades pré-definidas equivalentes à propriedades CSS.

Todas as propriedades são nomeadas usando camelCase. Onde houver um -, o próximo caracter é maiúsculo.

Exemplos:

💡 Gravar "" faz o navegador resetar a propriedade do elemento.

Só deve ser utilizado quando envolver cálculos. Em outra situações, usar a técnica das classes.

Estilos computados/resolvidos

O valor de elem.style reflete o estilo atribuído ao elemento.

Para obter o valor correto considerando toda a cascata de estilos, já resolvido na unidade padrão para os navegadores, use getComputedStyle(elemento, pseudo) (pseudo é opcional, e pode conter uma pseudoclasse CSS).

🍌 Não use propriedades de atalho, como margin ou padding, e sim marginTop ou paddingLeft. Não há padronização entre navegadores.

🍌🍌 O JavaScript não tem acesso à pseudoclasse :visited, por privacidade.

Modificando o documento

Em documentos dinâmicos frequentemente precisaremos incluir/remover nós ou elementos no DOM.

Criando elementos usando métodos de document

Podemos criar elementos usando métodos do objeto document.

O elemento pode ser salvo em uma variável e posteriomente inserido no DOM. Para inserir no DOM, escolha um nó base e chame o método adequado passando um ou mais nós a serem inseridos.

Podemos passar uma string ao invés de criar o nó. Isso não fará o engine processar o texto como HTML criando os elementos necessários, e sim criar um nó de texto.

Os métodos da família insertAdjacent possuem uma sintaxe mais simples para inserção de elementos (insertAdjacentElement), texto (insertAdjacentText) e strings contendo código HTML (insertAdjacentHTML).

Ex.:

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <p>Cidades:</p>
    <ul id="cidades"></ul>
  </body>

Removendo um elemento

Use elem.remove().

Clonando elementos

Para criar cópias idênticas de um elemento:

Eventos

Eventos são sinais de que algo aconteceu. Veja alguns exemplos:

Podemos executar código quando um evente ocorre. Para isso, precisamos de uma função (chamada handler ou manipulador) que será executada a cada vez que o evento ocorrer. Depois, precisamos vincular o handler no evento.

Exemplo: Exibir Olá ao clicar em um botão.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <button id="botaoQueResponde">Clique aqui!</button>
    <script>
      const botao = document.getElementById('botaoQueResponde');
      botao.addEventListener('click', () => alert('Olá, usuário!'));
    </script>
  </body>

Existem várias formas de se adicionar um handler em um evento, porém a mais versátil é elem.addEventListener("evento", handler). Podemos adicionar um handler em mais de um evento, e mais de um handler no mesmo evento.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <button>Clique aqui</button>
    <button>ou aqui</button>
    <script>
      const dizerOla = () => alert("Olá, usuário!");
      const dizerTchau = () => alert("Tchau!");

      // Todas as tags button
      const botoes = document.querySelectorAll("button");

      // Adiciona dizerOla nos eventos de todos os botões
      botoes.forEach((b) => b.addEventListener("click", dizerOla));

      // Adiciona dizerTchau no evento do segundo botão (junto com dizerOla)
      botoes[1].addEventListener("click", dizerTchau);
    </script>
  </body>

Um evento pode disponibilizar outras informações para o handler. Elas são passadas através do parâmetro definido no handler. Por exemplo, para saber qual elemento foi o chamador do evento, podemos acessar .currentTarget.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>
    <button>Clique aqui</button>
    <button>ou aqui</button>
    <script>
      const qualBotao = (evt) =>
        alert(`Você clicou em "${evt.currentTarget.textContent}"`);

      const botoes = document.querySelectorAll("button");

      botoes.forEach((b) => b.addEventListener("click", qualBotao));
    </script>
  </body>

Alguns elementos possuem comportamentos padrão em certos eventos. Uma tag a inicia a navegação para href quando clicada, um botão do tipo submit envia o seu formulário quando clicado, etc.

Para prevenir o comportamento padrão de um elemento, use o método .preventDefault() do objeto de evento.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>

    <a href="https://github.com/ermogenes/aulas-programacao-web/">
      Link para Dev Web
    </a>

    <script>
      document.querySelector("a").addEventListener("click", (evt) => {
        evt.preventDefault();
        evt.currentTarget.insertAdjacentHTML("afterend", "<span>😐</span>");
      });
    </script>
  </body>

Você pode causar o disparo de um evento manualmente usando .dispatchEvent.

  <body>
    <h1 id="titulo">Aprendendo DOM</h1>

    <button id="botaoDisparo">Disparar clique no link</button>
    <br />
    <a href="https://github.com/ermogenes/aulas-programacao-web/">
      Link para Dev Web
    </a>

    <script>
      document.querySelector("a").addEventListener("click", (evento) => {
        evento.preventDefault();
        evento.currentTarget.insertAdjacentHTML("afterend", "<span>😐</span>");
      });

      document.getElementById("botaoDisparo").addEventListener("click", (e) => {
        document.querySelector("a").dispatchEvent(new Event("click"));
      });
    </script>
  </body>

O evento document.DOMContentLoaded é disparado quando o navegador já baixou todo o HTML, o processou e terminou de gerar o DOM, portanto todos os elementos já estão disponíveis. Use-o para garantir que os elementos manipulados já foram carregados.

  <body>
    <script>
      const m = document.getElementById("mensagens");
      console.log(m); // null, poi ainda não foi criado

      document.addEventListener("DOMContentLoaded", () => {
        const m = document.getElementById("mensagens");
        console.log(m); // elemento já está acessível

        m.insertAdjacentHTML(
          "beforeend",
          "<li>Disparado DOMContentLoaded</li>"
        );
      });
    </script>

    <h1 id="titulo">Aprendendo DOM</h1>
    <ul id="mensagens"></ul>
  </body>

Formulários

Há alguns recursos facilitadores para manipulação de formulários.

Para campos input, select e textarea temos a propriedade value com o valor digitado pelo usuário.

Para campos input do tipo checkbox ou radio podemos usar o boolean ckecked.

Para os select, temos ainda:

Em document.forms temos uma coleção dos forms do documento.

Os campos possuem os seguintes eventos:

O form, por sua vez possui o evento submit, para quando for enviado. Caso não quisermos personalizar o seu comportamento, podemos usar preventDefault. Retornar false também garante que os valores não serão enviados.

Exemplo de validação:

Em index.html:

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DOM</title>
    <style>
      form {
        display: flex;
        flex-flow: column;
        max-width: 280px;
      }
      form > * {
        padding: 5px;
      }
      .invalido {
        color: red;
      }
      input[type="checkbox"].invalido + label {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1 id="titulo">Cadastro de usuário</h1>
    <form id="cadastro">
      <label for="usuario">Usuário:</label>
      <input type="text" id="usuario" name="usuario" />

      <label for="senha">Senha:</label>
      <input type="password" id="senha" name="senha" />

      <label for="repetesenha">Repetir senha:</label>
      <input type="password" id="repetesenha" name="repetesenha" />

      <label for="cidade">Cidade:</label>
      <select id="cidade" name="cidade">
        <option selected>-- selecione --</option>
        <option value="1">Mongaguá</option>
        <option value="2">Praia Grande</option>
        <option value="3">Itanhaém</option>
      </select>

      <div>
        <input type="checkbox" id="deacordo" name="deacordo" />
        <label for="deacordo">Concordo com os termos de uso</label>
      </div>

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

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

Em index.js:

const removeErro = (evt) => evt.currentTarget.classList.remove("invalido");

const processarForm = (evt) => {
  evt.preventDefault();

  const usuario = document.getElementById("usuario");
  const senha = document.getElementById("senha");
  const repetesenha = document.getElementById("repetesenha");
  const cidade = document.getElementById("cidade");
  const deacordo = document.getElementById("deacordo");

  if (usuario.value.trim().length === 0) {
    usuario.classList.add("invalido");
    usuario.focus();
    return;
  }

  if (senha.value.trim().length < 8) {
    senha.classList.add("invalido");
    senha.focus();
    return;
  }

  if (repetesenha.value !== senha.value) {
    repetesenha.classList.add("invalido");
    repetesenha.focus();
    return;
  }

  if (!["1", "2", "3"].includes(cidade.value)) {
    cidade.classList.add("invalido");
    cidade.focus();
    return;
  }

  if (!deacordo.checked) {
    deacordo.classList.add("invalido");
    deacordo.focus();
    return;
  }

  const novoUsuario = {
    usuario: usuario.value.trim(),
    senha: senha.value,
    cidade: cidade.value,
  };

  console.log(novoUsuario);
};

const iniciar = () => {
  document.forms[0].addEventListener("submit", processarForm);

  document
    .querySelectorAll("input,select")
    .forEach((campo) => campo.addEventListener("input", removeErro));
};

document.addEventListener("DOMContentLoaded", iniciar);