Engenharia de IA Aplicada à Programação Web
Aula 4 de 6
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 1.1 Como a Web funciona
  2. 1.2 HTML semântico
  3. 1.3 CSS responsivo
  4. 1.4 JavaScript no navegador
  5. 1.5 DevTools e segurança
  6. 1.6 Publicação e apresentação

Módulo 1 · Aula 1.4

JavaScript no navegador

2 horas Teoria + laboratório Fundamentos da Web
Ver fonte Markdown

Identificação

Introdução

O que é JavaScript?

JavaScript é uma linguagem de programação criada para adicionar comportamento às páginas Web. Com ela, a aplicação pode reagir a ações, validar formulários, atualizar conteúdos, fazer requisições e controlar estados.

Sem JavaScript, a página ainda pode possuir conteúdo, links e formulários. Com JavaScript, ela passa a responder de maneira mais dinâmica.

JavaScript é a mesma coisa que Java?

Não. São linguagens diferentes, com histórias, ambientes e características diferentes. A semelhança no nome não significa que uma seja versão da outra.

Onde o JavaScript funciona?

Todo navegador moderno possui um mecanismo capaz de executar JavaScript. O código pode acessar a representação da página, chamada DOM, e utilizar recursos oferecidos pelo navegador.

JavaScript também pode funcionar fora do navegador. Node.js, por exemplo, permite executar JavaScript no servidor. Nesta aula trabalharemos com o ambiente do navegador.

JavaScript no navegador -> interface e interação
JavaScript no servidor -> APIs, regras e integrações

Preciso de uma biblioteca?

Não. O navegador já oferece DOM, eventos, formulários, Fetch e várias APIs. Frameworks serão introduzidos depois que os fundamentos da linguagem e do ambiente estiverem claros.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. explicar o papel do JavaScript no navegador;
  2. carregar um arquivo como módulo;
  3. selecionar elementos do DOM;
  4. reagir a eventos;
  5. ler dados de um formulário;
  6. utilizar validação nativa;
  7. controlar estados de carregamento, sucesso e erro;
  8. atualizar a interface sem recarregar a página;
  9. preservar acessibilidade ao apresentar retorno;
  10. evitar exposição desnecessária de dados pessoais.

Pré-requisitos

  • HTML semântico;
  • formulário com rótulos associados;
  • CSS responsivo;
  • noções iniciais de variáveis, funções e objetos.

Pergunta orientadora

Como transformar uma página visual em uma interface que reage às ações do usuário de forma previsível e acessível?

Roteiro sugerido

Etapa Duração
JavaScript e o navegador 15 min
DOM e seleção de elementos 20 min
Eventos 20 min
Formulários e validação 25 min
Estados da interface 15 min
Laboratório e testes 25 min

1. Responsabilidade do JavaScript

Na aplicação:

HTML -> estrutura e significado
CSS -> apresentação
JavaScript -> comportamento

JavaScript pode:

  • reagir a cliques e digitação;
  • validar dados;
  • atualizar elementos;
  • fazer requisições;
  • controlar estados;
  • armazenar pequenas preferências;
  • integrar recursos do navegador.

JavaScript não deve corrigir uma estrutura HTML inadequada. Um formulário continua precisando de form, label, input e button.

2. Carregando o arquivo

Antes do fechamento de body:

<script type="module" src="assets/main.js"></script>

type="module" permite:

  • utilizar import e export;
  • evitar variáveis globais acidentais;
  • carregar o script de maneira deferida;
  • organizar o código em arquivos.

3. DOM

DOM significa Document Object Model. O navegador representa o HTML como objetos que podem ser consultados e modificados.

Considere:

<form id="waitlist-form">
  <button type="submit">Quero participar</button>
</form>

Seleção:

const form = document.querySelector("#waitlist-form");
const button = form?.querySelector("button[type='submit']");

Seletores úteis

document.querySelector("#identificador");
document.querySelector(".classe");
document.querySelector("[data-state='success']");
document.querySelectorAll(".feature-card");

querySelector retorna o primeiro elemento correspondente ou null.

Verificação

if (!form || !button) {
  throw new Error("O formulário não foi encontrado.");
}

Falhar de maneira explícita durante o desenvolvimento facilita a identificação de mudanças incompatíveis no HTML.

4. Leitura e atualização

const title = document.querySelector("h1");

if (title) {
  title.textContent = "Novo título";
}

Prefira textContent para inserir texto.

Evite inserir conteúdo não confiável com innerHTML:

// Perigoso quando message contém entrada externa
status.innerHTML = message;
// Insere apenas texto
status.textContent = message;

5. Eventos

Eventos representam acontecimentos:

  • click;
  • input;
  • change;
  • submit;
  • focus;
  • blur;
  • keydown.

Exemplo:

button.addEventListener("click", () => {
  console.log("Botão ativado");
});

Para formulários, escute submit, não apenas o clique do botão. Assim o fluxo também funciona quando o usuário envia com o teclado.

form.addEventListener("submit", (event) => {
  event.preventDefault();
});

preventDefault impede temporariamente o envio e o recarregamento tradicional.

6. FormData

FormData coleta campos que possuem name:

const formData = new FormData(form);

const name = formData.get("nome");
const email = formData.get("email");
const profile = formData.get("perfil");

Isso demonstra a diferença:

  • id relaciona elementos e permite seleção;
  • name define a chave utilizada no envio.

Normalização

function normalizeName(value) {
  return String(value ?? "")
    .trim()
    .replace(/\s+/g, " ");
}

Normalizar não significa alterar arbitrariamente os dados. Cada transformação precisa de justificativa.

7. Validação

O HTML já contém:

<input type="email" required>

O navegador pode verificar:

if (!form.checkValidity()) {
  form.reportValidity();
  return;
}
  • checkValidity retorna o estado;
  • reportValidity apresenta a mensagem nativa.

Estado inválido

form.addEventListener(
  "invalid",
  (event) => {
    event.target.setAttribute("aria-invalid", "true");
  },
  true,
);

Quando o usuário corrige:

form.addEventListener("input", (event) => {
  event.target.removeAttribute("aria-invalid");
});

O terceiro argumento true permite observar o evento invalid durante a fase de captura.

8. Estados da interface

Uma operação assíncrona normalmente possui:

idle -> loading -> success
                  -> error

O botão pode comunicar carregamento:

button.disabled = true;
button.textContent = "Enviando...";
form.setAttribute("aria-busy", "true");

Ao terminar:

button.disabled = false;
button.textContent = "Quero participar";
form.removeAttribute("aria-busy");

O estado precisa ser restaurado mesmo quando ocorrer erro. Para isso, utilizamos finally.

9. Operações assíncronas

Uma requisição demora algum tempo.

async function submitForm(payload) {
  const response = await fetch("/api/waitlist", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    throw new Error("Não foi possível concluir o cadastro.");
  }

  return response.json();
}

Nesta aula, a API ainda não existe. A página utiliza uma função simulada. O contrato, porém, prepara a integração futura.

10. Retorno acessível

No HTML:

<p
  id="form-status"
  class="form-status"
  role="status"
  aria-live="polite"
  hidden
></p>

No JavaScript:

function setStatus(message, state) {
  status.textContent = message;
  status.dataset.state = state;
  status.hidden = false;
}

aria-live="polite" permite que tecnologias assistivas anunciem a atualização sem interromper imediatamente o usuário.

11. Privacidade

O formulário contém nome e e-mail. Durante o desenvolvimento:

  • não registre os dados no console;
  • não coloque dados pessoais em URLs;
  • não armazene e-mail em localStorage;
  • não envie dados para serviços não autorizados;
  • não mantenha dados além da necessidade;
  • utilize valores fictícios nos testes.

Este código simula o envio e descarta os dados.

12. Implementação completa

Elementos:

const form = document.querySelector("#waitlist-form");
const submitButton = form?.querySelector("button[type='submit']");
const status = document.querySelector("#form-status");

Fluxo:

form.addEventListener("submit", async (event) => {
  event.preventDefault();

  if (!form.checkValidity()) {
    form.reportValidity();
    return;
  }

  const formData = new FormData(form);
  const payload = {
    nome: normalizeName(formData.get("nome")),
    email: String(formData.get("email") ?? "").trim().toLowerCase(),
    perfil: String(formData.get("perfil") ?? ""),
    aceite: formData.get("aceite") === "on",
  };

  setLoading(true);

  try {
    await simulateRequest(payload);
    form.reset();
    setStatus("Cadastro simulado com sucesso.", "success");
  } catch {
    setStatus("Não foi possível concluir. Tente novamente.", "error");
  } finally {
    setLoading(false);
  }
});

Não utilizamos o objeto payload para escrever dados na tela ou no console.

13. Laboratório guiado

Passo 1 - Identificadores

Adicione ao formulário:

id="waitlist-form"

Adicione o elemento de retorno.

Passo 2 - Script

Crie assets/main.js e conecte-o como módulo.

Passo 3 - Seleção segura

Selecione formulário, botão e status. Interrompa a execução com erro claro caso estejam ausentes.

Passo 4 - Validação

Observe os eventos invalid e input. Aplique e remova aria-invalid.

Passo 5 - Envio

Escute submit, gere FormData e normalize os valores.

Passo 6 - Loading

Durante a operação:

  • desabilite o botão;
  • altere seu texto;
  • aplique aria-busy.

Passo 7 - Resultado

Apresente sucesso, limpe o formulário e devolva o foco para o status.

O código de referência utiliza tabindex="-1" no status para permitir foco programático sem colocá-lo na sequência normal de navegação.

14. Testes manuais

Cenário 1 - Campos vazios

  1. Ative o botão sem preencher.
  2. Confirme que o navegador bloqueia o envio.
  3. Identifique o primeiro campo inválido.

Cenário 2 - E-mail inválido

  1. Preencha o nome.
  2. Digite email-invalido.
  3. Selecione o perfil.
  4. Marque o aceite.
  5. Tente enviar.

Cenário 3 - Sucesso

  1. Utilize dados fictícios válidos.
  2. Envie.
  3. Observe o estado de carregamento.
  4. Confirme a mensagem.
  5. Verifique que o formulário foi limpo.

Cenário 4 - Teclado

Repita o cenário de sucesso sem utilizar o mouse.

15. Exercício de fixação

Adicione um contador ao campo de nome:

0 de 80 caracteres

Requisitos:

  • atualizar no evento input;
  • usar textContent;
  • relacionar o contador ao campo com aria-describedby;
  • impedir mais de 80 caracteres com maxlength;
  • não utilizar biblioteca externa.

16. Desafio individual

Implemente um modo de falha na simulação:

  • quando o e-mail terminar em @erro.local, a função deve rejeitar;
  • a interface deve apresentar estado de erro;
  • os dados não devem ser apagados;
  • o botão deve voltar ao estado normal;
  • o usuário deve conseguir corrigir e tentar novamente.

Utilize apenas dados fictícios.

17. Erros comuns

Escutar somente o clique

O formulário também pode ser enviado pelo teclado. Escute submit.

Não verificar elementos nulos

Mudanças no HTML podem fazer o seletor deixar de encontrar o elemento.

Utilizar innerHTML com entrada externa

Prefira textContent.

Esquecer de restaurar o botão

Utilize finally para remover o estado de carregamento.

Registrar dados pessoais

Não use console.log(formData) em código real.

Desabilitar validação sem substituí-la

novalidate remove a apresentação nativa. Utilize apenas quando a aplicação implementar uma alternativa completa e acessível.

Limpar o formulário após erro

Em caso de falha, preserve os dados para que o usuário possa corrigir ou reenviar.

Construir interface apenas com JavaScript

O conteúdo e as ações essenciais devem partir de HTML semântico sempre que possível.

18. Lista de verificação de entrega

  • O JavaScript está em arquivo externo.
  • O script utiliza type="module".
  • O formulário possui identificador estável.
  • O código verifica se os elementos existem.
  • O fluxo escuta submit.
  • A validação nativa continua ativa.
  • Campos inválidos recebem aria-invalid.
  • O estado de loading é visível.
  • O botão fica desabilitado durante a operação.
  • Sucesso e erro possuem mensagens diferentes.
  • O retorno utiliza região viva.
  • O formulário só é limpo após sucesso.
  • Dados pessoais não aparecem no console.
  • O fluxo funciona por teclado.
  • Não existem erros no console.

19. Critérios de aceite

A entrega será aceita quando:

  1. impedir envio de dados inválidos;
  2. aceitar dados fictícios válidos;
  3. exibir estado de carregamento;
  4. exibir mensagem de sucesso;
  5. restaurar o botão após a operação;
  6. preservar dados quando ocorrer erro;
  7. funcionar com teclado;
  8. não registrar nome ou e-mail no console;
  9. utilizar textContent para retorno;
  10. não recarregar a página durante a simulação.

20. Perguntas para revisão

  1. O que é DOM?
  2. Qual é a diferença entre querySelector e querySelectorAll?
  3. Por que devemos escutar submit?
  4. Para que serve preventDefault?
  5. Como FormData identifica os campos?
  6. Qual é a diferença entre id e name?
  7. Para que servem try, catch e finally?
  8. Por que devemos evitar innerHTML com conteúdo externo?
  9. O que aria-live="polite" faz?
  10. Por que não devemos registrar dados pessoais no console?

Próxima aula

Na Aula 1.5, utilizaremos DevTools e auditorias para analisar acessibilidade, rede, desempenho, erros e riscos básicos de segurança da página.