# Construir uma App Bancária Parte 2: Criar um Formulário de Login e Registo ## Questionário Pré-Aula [Questionário pré-aula](https://ff-quizzes.netlify.app/web/quiz/43) ### Introdução Em quase todas as aplicações web modernas, é possível criar uma conta para ter um espaço privado. Como múltiplos utilizadores podem aceder a uma aplicação web ao mesmo tempo, é necessário um mecanismo para armazenar os dados pessoais de cada utilizador separadamente e selecionar quais informações exibir. Não vamos abordar como gerir [identidade de utilizador de forma segura](https://en.wikipedia.org/wiki/Authentication), pois é um tópico extenso por si só, mas garantiremos que cada utilizador possa criar uma (ou mais) conta bancária na nossa aplicação. Nesta parte, utilizaremos formulários HTML para adicionar login e registo à nossa aplicação web. Veremos como enviar os dados para uma API de servidor programaticamente e, por fim, como definir regras básicas de validação para os dados introduzidos pelo utilizador. ### Pré-requisitos É necessário ter concluído a parte de [templates HTML e rotas](../1-template-route/README.md) da aplicação web para esta lição. Também é necessário instalar o [Node.js](https://nodejs.org) e [executar a API do servidor](../api/README.md) localmente para poder enviar dados e criar contas. **Nota importante** Terás dois terminais a funcionar ao mesmo tempo, conforme listado abaixo: 1. Para a aplicação bancária principal que construímos na lição de [templates HTML e rotas](../1-template-route/README.md) 2. Para a [API do servidor da aplicação bancária](../api/README.md) que acabámos de configurar acima. É necessário que ambos os servidores estejam ativos para seguir o resto da lição. Eles estão a ouvir em portas diferentes (porta `3000` e porta `5000`), por isso tudo deve funcionar corretamente. Podes testar se o servidor está a funcionar corretamente executando este comando num terminal: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## Formulário e controlos O elemento `
` encapsula uma secção de um documento HTML onde o utilizador pode introduzir e submeter dados através de controlos interativos. Existem vários tipos de controlos de interface de utilizador (UI) que podem ser usados num formulário, sendo os mais comuns os elementos `` e `
``` Usando o atributo `value`, podemos definir um valor padrão para um determinado campo. Repara também que o campo para `balance` tem o tipo `number`. Parece diferente dos outros campos? Experimenta interagir com ele. ✅ Consegues navegar e interagir com os formulários usando apenas o teclado? Como o farias? ## Submeter dados ao servidor Agora que temos uma interface funcional, o próximo passo é enviar os dados ao servidor. Vamos fazer um teste rápido usando o nosso código atual: o que acontece se clicares no botão *Login* ou *Register*? Notaste a mudança na secção de URL do teu navegador? ![Captura de ecrã mostrando a mudança no URL do navegador após clicar no botão Register](../../../../translated_images/click-register.e89a30bf0d4bc9ca867dc537c4cea679a7c26368bd790969082f524fed2355bc.pt.png) A ação padrão de um `
` é submeter o formulário ao URL atual do servidor usando o [método GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3), anexando os dados do formulário diretamente ao URL. Este método tem algumas limitações: - Os dados enviados são muito limitados em tamanho (cerca de 2000 caracteres) - Os dados são visíveis diretamente no URL (não é ideal para palavras-passe) - Não funciona com uploads de ficheiros Por isso, podes alterá-lo para usar o [método POST](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5), que envia os dados do formulário ao servidor no corpo da requisição HTTP, sem as limitações anteriores. > Embora o POST seja o método mais utilizado para enviar dados, [em alguns cenários específicos](https://www.w3.org/2001/tag/doc/whenToUseGet.html) é preferível usar o método GET, como ao implementar um campo de pesquisa. ### Tarefa Adiciona as propriedades `action` e `method` ao formulário de registo: ```html ``` Agora tenta registar uma nova conta com o teu nome. Após clicares no botão *Register*, deverás ver algo como isto: ![Janela do navegador no endereço localhost:5000/api/accounts, mostrando uma string JSON com os dados do utilizador](../../../../translated_images/form-post.61de4ca1b964d91a9e338416e19f218504dd0af5f762fbebabfe7ae80edf885f.pt.png) Se tudo correr bem, o servidor deverá responder à tua requisição com uma resposta [JSON](https://www.json.org/json-en.html) contendo os dados da conta que foi criada. ✅ Tenta registar novamente com o mesmo nome. O que acontece? ## Submeter dados sem recarregar a página Como provavelmente notaste, há um pequeno problema com a abordagem que acabámos de usar: ao submeter o formulário, saímos da nossa aplicação e o navegador redireciona para o URL do servidor. Estamos a tentar evitar todos os recarregamentos de página na nossa aplicação web, já que estamos a criar uma [Aplicação de Página Única (SPA)](https://en.wikipedia.org/wiki/Single-page_application). Para enviar os dados do formulário ao servidor sem forçar um recarregamento da página, temos de usar código JavaScript. Em vez de colocar um URL na propriedade `action` de um elemento ``, podes usar qualquer código JavaScript precedido pela string `javascript:` para realizar uma ação personalizada. Usar isto também significa que terás de implementar algumas tarefas que anteriormente eram feitas automaticamente pelo navegador: - Recuperar os dados do formulário - Converter e codificar os dados do formulário num formato adequado - Criar a requisição HTTP e enviá-la ao servidor ### Tarefa Substitui o `action` do formulário de registo por: ```html ``` Abre o `app.js` e adiciona uma nova função chamada `register`: ```js function register() { const registerForm = document.getElementById('registerForm'); const formData = new FormData(registerForm); const data = Object.fromEntries(formData); const jsonData = JSON.stringify(data); } ``` Aqui recuperamos o elemento do formulário usando `getElementById()` e utilizamos o auxiliar [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) para extrair os valores dos controlos do formulário como um conjunto de pares chave/valor. Depois, convertemos os dados para um objeto regular usando [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) e, finalmente, serializamos os dados para [JSON](https://www.json.org/json-en.html), um formato comumente usado para troca de dados na web. Os dados estão agora prontos para serem enviados ao servidor. Cria uma nova função chamada `createAccount`: ```js async function createAccount(account) { try { const response = await fetch('//localhost:5000/api/accounts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: account }); return await response.json(); } catch (error) { return { error: error.message || 'Unknown error' }; } } ``` O que esta função faz? Primeiro, repara na palavra-chave `async` aqui. Isto significa que a função contém código que será executado [**assincronamente**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). Quando usada juntamente com a palavra-chave `await`, permite esperar que o código assíncrono seja executado - como esperar pela resposta do servidor aqui - antes de continuar. Aqui está um vídeo rápido sobre o uso de `async/await`: [![Async e Await para gerir promessas](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)](https://youtube.com/watch?v=YwmlRkrxvkk "Async e Await para gerir promessas") > 🎥 Clica na imagem acima para ver um vídeo sobre async/await. Usamos a API `fetch()` para enviar dados JSON ao servidor. Este método recebe 2 parâmetros: - O URL do servidor, por isso colocamos aqui `//localhost:5000/api/accounts`. - As configurações da requisição. É aqui que definimos o método como `POST` e fornecemos o `body` para a requisição. Como estamos a enviar dados JSON ao servidor, também precisamos definir o cabeçalho `Content-Type` como `application/json` para que o servidor saiba como interpretar o conteúdo. Como o servidor responderá à requisição com JSON, podemos usar `await response.json()` para analisar o conteúdo JSON e retornar o objeto resultante. Nota que este método é assíncrono, por isso usamos a palavra-chave `await` aqui antes de retornar para garantir que quaisquer erros durante a análise também sejam capturados. Agora adiciona algum código à função `register` para chamar `createAccount()`: ```js const result = await createAccount(jsonData); ``` Como usamos a palavra-chave `await` aqui, precisamos adicionar a palavra-chave `async` antes da função register: ```js async function register() { ``` Por fim, vamos adicionar alguns logs para verificar o resultado. A função final deve ficar assim: ```js async function register() { const registerForm = document.getElementById('registerForm'); const formData = new FormData(registerForm); const jsonData = JSON.stringify(Object.fromEntries(formData)); const result = await createAccount(jsonData); if (result.error) { return console.log('An error occurred:', result.error); } console.log('Account created!', result); } ``` Foi um pouco longo, mas chegámos lá! Se abrires as [ferramentas de desenvolvimento do navegador](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools) e tentares registar uma nova conta, não deverás ver nenhuma mudança na página web, mas uma mensagem aparecerá na consola confirmando que tudo funciona. ![Captura de ecrã mostrando uma mensagem de log na consola do navegador](../../../../translated_images/browser-console.efaf0b51aaaf67782a29e1a0bb32cc063f189b18e894eb5926e02f1abe864ec2.pt.png) ✅ Achas que os dados são enviados ao servidor de forma segura? E se alguém conseguir intercetar a requisição? Podes ler sobre [HTTPS](https://en.wikipedia.org/wiki/HTTPS) para saber mais sobre comunicação de dados segura. ## Validação de dados Se tentares registar uma nova conta sem definir primeiro um nome de utilizador, podes ver que o servidor retorna um erro com o código de estado [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).). Antes de enviar dados a um servidor, é uma boa prática [validar os dados do formulário](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) previamente, sempre que possível, para garantir que envias uma requisição válida. Os controlos de formulário HTML5 fornecem validação integrada usando vários atributos: - `required`: o campo precisa ser preenchido, caso contrário o formulário não pode ser submetido. - `minlength` e `maxlength`: define o número mínimo e máximo de caracteres em campos de texto. - `min` e `max`: define o valor mínimo e máximo de um campo numérico. - `type`: define o tipo de dados esperado, como `number`, `email`, `file` ou [outros tipos integrados](https://developer.mozilla.org/docs/Web/HTML/Element/input). Este atributo também pode alterar a renderização visual do controlo do formulário. - `pattern`: permite definir um padrão de [expressão regular](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions) para testar se os dados introduzidos são válidos ou não. > Dica: pode personalizar a aparência dos seus controlos de formulário dependendo se são válidos ou não, utilizando as pseudo-classes CSS `:valid` e `:invalid`. ### Tarefa Existem 2 campos obrigatórios para criar uma nova conta válida: o nome de utilizador e a moeda. Os outros campos são opcionais. Atualize o HTML do formulário, utilizando tanto o atributo `required` como texto no rótulo do campo para que: ```html ... ``` Embora esta implementação específica do servidor não imponha limites específicos no comprimento máximo dos campos, é sempre uma boa prática definir limites razoáveis para qualquer entrada de texto do utilizador. Adicione um atributo `maxlength` aos campos de texto: ```html ... ... ``` Agora, se pressionar o botão *Registar* e algum campo não cumprir uma regra de validação que definimos, deverá ver algo como isto: ![Captura de ecrã mostrando o erro de validação ao tentar submeter o formulário](../../../../translated_images/validation-error.8bd23e98d416c22f80076d04829a4bb718e0e550fd622862ef59008ccf0d5dce.pt.png) A validação como esta, realizada *antes* de enviar qualquer dado para o servidor, é chamada de validação **do lado do cliente**. Mas note que nem sempre é possível realizar todas as verificações sem enviar os dados. Por exemplo, não podemos verificar aqui se já existe uma conta com o mesmo nome de utilizador sem enviar um pedido ao servidor. Validações adicionais realizadas no servidor são chamadas de validação **do lado do servidor**. Normalmente, ambas precisam de ser implementadas, e embora a validação do lado do cliente melhore a experiência do utilizador ao fornecer feedback instantâneo, a validação do lado do servidor é crucial para garantir que os dados do utilizador que manipula são válidos e seguros. --- ## 🚀 Desafio Mostre uma mensagem de erro no HTML se o utilizador já existir. Aqui está um exemplo de como a página de login final pode ficar após um pouco de estilização: ![Captura de ecrã da página de login após adicionar estilos CSS](../../../../translated_images/result.96ef01f607bf856aa9789078633e94a4f7664d912f235efce2657299becca483.pt.png) ## Questionário Pós-Aula [Questionário pós-aula](https://ff-quizzes.netlify.app/web/quiz/44) ## Revisão e Estudo Individual Os programadores têm sido muito criativos nos seus esforços de construção de formulários, especialmente no que diz respeito a estratégias de validação. Aprenda sobre diferentes fluxos de formulários explorando o [CodePen](https://codepen.com); consegue encontrar alguns formulários interessantes e inspiradores? ## Tarefa [Estilize a sua aplicação bancária](assignment.md) --- **Aviso Legal**: Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.