# JavaScript no cliente e DOM

Sintaxe de JavaScript, funções, DOM, eventos, propagação e validação de formulários.

Página: https://resumos.rgo.pt/cadeiras/ltw/javascript-dom-eventos/

O PHP monta a página no servidor antes de a enviar. O JavaScript corre no navegador depois de a página chegar e mexe nela sem pedir nada ao servidor. Vens de [funções em Python](https://resumos.rgo.pt/cadeiras/fp/funcoes/) e de [tipos estáticos em C++](https://resumos.rgo.pt/cadeiras/p/cpp-fundamentos/): o JavaScript tem funções parecidas com as de Python mas tipagem dinâmica ainda mais solta do que a do PHP, com conversões que surpreendem.

[Vídeo: JavaScript in 100 Seconds](https://www.youtube.com/watch?v=DHjqpvDnNGE)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=DHjqpvDnNGE)

## Sintaxe sem surpresas, quase

Declara variáveis com `const` por omissão e `let` quando o valor muda. O `var` antigo tem regras de alcance estranhas e não o deves usar em código novo:

```
const preco = 8.5;
let qtd = 2;
console.log('Total: ' + preco * qtd);
```

Isto escreve `Total: 17` na consola do navegador, que abres nas ferramentas de desenvolvimento. As funções são valores como outros quaisquer, por isso passam-se como argumentos, o que vais usar em todos os eventos:

```
function soma(a, b) {
  return a + b;
}
const dobro = (x) => x * 2;
```

Quanto às conversões, decora os dois casos clássicos: `'8' + 1` dá `'81'` porque o `+` com texto concatena, mas `'8' - 1` dá `7` porque o `-` só sabe subtrair e converte. Quando um cálculo sair torto, converte explicitamente com `Number()` antes de culpares a lógica.

## Mexer na página pelo DOM

O DOM é a página vista como uma árvore de objetos: cada etiqueta é um nó que podes ler, criar e alterar. A lista de tarefas da mercearia, com adicionar e remover, cabe nestas linhas em `tarefas.js`. Experimenta-a aqui: escreve um item, prime Enter, e remove-o a seguir.

```html
<form id="form"><input id="texto" /><button>Adicionar</button></form> <ul id="tarefas"></ul>
```



```css

```



```js
const lista = document.querySelector("#tarefas"); document.querySelector("#form").addEventListener("submit", (e) => { e.preventDefault(); const texto = document.querySelector("#texto").value.trim(); if (!texto) return; const li = document.createElement("li"); li.textContent = texto; const btn = document.createElement("button"); btn.textContent = "Remover"; btn.addEventListener("click", () => li.remove()); li.append(btn); lista.append(li); });
```

```
const lista = document.querySelector('#tarefas');
document.querySelector('#form').addEventListener('submit', (e) => {
  e.preventDefault();
  const texto = document.querySelector('#texto').value.trim();
  if (!texto) return;
  const li = document.createElement('li');
  li.textContent = texto;
  const btn = document.createElement('button');
  btn.textContent = 'Remover';
  btn.addEventListener('click', () => li.remove());
  li.append(btn);
  lista.append(li);
});
```

O `querySelector` escolhe elementos com a mesma sintaxe do CSS. O `addEventListener` regista uma função que corre quando o evento acontece: aqui, submeter o formulário cria um `li` novo com um botão que se remove a si próprio. O `preventDefault` impede o comportamento normal do formulário, que seria recarregar a página e deitar fora a lista. O texto entra por `textContent`, nunca por `innerHTML` com dados do utilizador, e a diferença entre os dois é esta:

```
li.textContent = '<b>Queijo</b>';
```

O elemento mostra o texto literal `<b>Queijo</b>`, sem etiquetas. É o comportamento seguro para dados do utilizador: nada do que ele escreve vira código. A razão está na página de [segurança](https://resumos.rgo.pt/cadeiras/ltw/javascript-dom-eventos/seguranca-web/).

```
li.innerHTML = '<b>Queijo</b>';
```

O elemento mostra **Queijo** a negrito, porque a cadeia foi interpretada como HTML. Útil para o teu próprio modelo, perigoso para dados do utilizador: uma carga com `script` corria. Só usa com cadeias que escreveste tu.

## A propagação dos eventos

Um clique num botão dentro de um cartão dentro da página atravessa os três: primeiro desce do documento até ao botão (captura) e depois sobe de volta (bolha). Por omissão, os teus tratadores correm na subida.

![Propagação de um clique: desce do documento para a div e desta para o botão na fase de captura, e sobe do botão para a div e desta para o documento na fase de bolha.](https://resumos.rgo.pt/cadeiras/ltw/javascript-dom-eventos/figura-1.svg)

Experimenta este exercício com um botão dentro de uma `div`:

```
document
  .querySelector('#caixa')
  .addEventListener('click', () => console.log('caixa'));
document.querySelector('#botao').addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('botão');
});
```

Clicar no botão escreve só “botão”, porque o `stopPropagation` trava a subida e a caixa nunca fica a saber. Sem essa linha, escrevia “botão” e depois “caixa”, e um tratador na caixa que fecha um menu ou apaga uma seleção disparava sem seres tu a querer. Quando um clique fizer duas coisas em vez de uma, procura o tratador esquecido algures na subida.

## Validar o formulário antes de enviar

O HTML já valida o básico com `required` e `type`, mas regras como “a data de entrega é depois de hoje” pedem JavaScript. Valida no evento de submissão e bloqueia o envio quando falhar:

```
document.querySelector('#encomenda').addEventListener('submit', (e) => {
  const qtd = Number(document.querySelector('#qtd').value);
  if (!(qtd >= 1)) {
    e.preventDefault();
    document.querySelector('#erro').textContent = 'A quantidade mínima é 1.';
  }
});
```

A mensagem aparece num elemento reservado para erros, junto ao campo, em vez de numa caixa de alerta que interrompe tudo. E lembra-te do limite desta validação: ela ajuda o utilizador honesto, mas o servidor volta a validar tudo, porque um pedido pode ser forjado sem passar pelo teu formulário. A validação do cliente é cortesia; a do servidor é segurança.

Depura na consola, não no escuro

Quando nada acontecer ao clicar, abre a consola: o JavaScript para no primeiro erro e diz a linha. Um `console.log` antes e depois da linha suspeita mostra se o tratador correu e com que valores. Nove em cada dez bloqueios são um seletor mal escrito que devolveu `null`.
