├── .gitignore ├── LICENSE ├── README.md ├── package.json └── src ├── app.js ├── components ├── divs │ ├── groupButtons.js │ ├── headerCreate.js │ ├── headerEdit.js │ ├── headerHome.js │ ├── headerList.js │ ├── headerUser.js │ ├── listUsers.js │ ├── mainDiv.js │ └── mainDivList.js └── forms │ ├── createForm.js │ └── editForm.js ├── factories ├── button.factory.js ├── div.factory.js ├── form.factory.js ├── input.factory.js └── text.factory.js ├── pages ├── create.js ├── edit.js ├── home.js ├── user.js └── users.js ├── prototypes ├── button.prototype.js ├── common.prototype.js ├── div.prototype.js ├── form.prototype.js └── input.prototype.js └── utils ├── initFiles.js └── reset.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.html -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 João Victor Negreiros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | Projeto HTML
5 | 100% Javascript 6 |

7 |

8 | Front-end feito "sem HTML" 9 |

10 | Language Top 11 | 12 | License 13 | 14 |
15 | 16 | --- 17 | 18 |

Conteúdos

19 | 20 | [➜ Sobre o projeto](#mag_right-sobre-o-projeto)
21 | [➜ O que aprendi](#books-o-que-aprendi)
22 | [➜ Como usar](#information_source-como-usar)
23 | 24 | --- 25 | 26 | ## :mag_right: Sobre o projeto 27 | 28 | [Voltar ao topo](#conteudos)
29 | 30 | O objetivo dessa aplicação era criar um frontend feito totalmente em Javascript, sem nenhum arquivo HTML ou CSS pré-criado. Para gerar as páginas em HTML usei o próprio JS para criá-las e inserir os componentes necessários. Além disso, esse projeto também é o frontend da minha API 100% NodeJS (sem dependências). 31 | 32 | --- 33 | 34 | ### ➡ API 35 | A API utilizada é feita totalmente em NodeJS, ou seja, todo esse projeto (frontend e backend) foi feito sem nenhuma dependência externa. 36 |

37 | Para ver mais detalhes de como usar a API acesse o repositório: [joaovictornsv/http-node-api](https://github.com/joaovictornsv/http-node-api) 38 | 39 | --- 40 | 41 | ### ➡ Arquitetura 42 | Para gerar todos os componentes necessários criei uma arquitetura que se resume basicamente em: 43 | - Prototypes 44 | - Factories 45 | - Components 46 | - Pages 47 | 48 | 49 | ### Prototypes 50 | Os *prototypes* são adições nos métodos dos elementos HTML que fiz para facilitar a criação de componentes estilizados. 51 | 52 |
53 | 54 |
55 | 56 | Um exemplo de como um prototype funciona: 57 | 58 | 59 | ```javascript 60 | // Mudando a cor de um botao 61 | const button = document.getElementById('btn') 62 | 63 | 64 | // ❌ SEM PROTOTYPE 65 | button.style.color = 'blue' 66 | 67 | 68 | // ✅ COM PROTOTYPE 69 | 70 | // Criando uma função nova no prototype da tag 'button' 71 | HTMLButtonElement.prototype.setCSS = function setCSS(new_css) { 72 | // add CSS in element 73 | } 74 | 75 | // Usando a função criada 76 | button.setCSS({ color: 'blue'}) 77 | 78 | ``` 79 |
80 | 81 | ### Factories 82 | As *factories* são **closures**, ou seja, funções que "se lembram" do escopo de quando foram criadas. Funcionam semelhante a uma espécie de classe. Não escolhi trabalhar com classes para poder pôr em prática o que havia estudado sobre as closures. Caso deseje ler mais sobre closures [clique aqui](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Closures). 83 | 84 |
85 | 86 |
87 | 88 | Um exemplo de como uma closure funciona: 89 | 90 | 91 | ```javascript 92 | // Cria um botão escrito 'Botão 1' 93 | const button1 = makeButton({ value: 'Botão 1' }); 94 | 95 | // Cria outro botão escrito 'Botão 2' 96 | const button2 = makeButton({ value: 'Botão 2' }); 97 | 98 | 99 | 100 | // Muda a cor do texto do Botão 1 para azul 101 | button1.setCSS({ color: 'blue' }) 102 | 103 | // Muda a cor do texto do Botão 2 para Vermelho 104 | button2.setCSS({ color: 'red' }); 105 | 106 | 107 | // Ambos os botões tem seus escopos próprios 108 | ``` 109 | 110 |
111 | 112 | ### Components 113 | Os *components* são os elementos HTML já criados e estilizados prontos para uso (assim como ocorre com o [styled-components](https://styled-components.com/)). 114 | 115 |
116 | 117 |
118 | 119 | Um exemplo de como um component funciona: 120 | 121 | 122 | ```javascript 123 | // Criação de um elemento 'h1' 124 | const title = makeText({tag: 'h1', value: 'Título Princiapl'}); 125 | 126 | // Estilização do elemento 127 | titleMain.setCSS({ 128 | fontSize: '22px', 129 | color: 'black', 130 | fontFamily: 'Arial, sans-serif' 131 | }); 132 | 133 | export default title; 134 | ``` 135 | 136 |
137 | 138 | ### Pages 139 | As *pages* são os conjuntos de componentes organizados para formar uma página completa. 140 | 141 |
142 | 143 |
144 | 145 | Um exemplo de como uma página funciona: 146 | 147 | 148 | ```javascript 149 | // Criação de um elemento 'h1' 150 | import header from './components/header.js' 151 | import title from './components/title.js' 152 | import subtitle from './components/subtitle.js' 153 | 154 | function HomePage() { 155 | // Adicionando components na div 'header' 156 | header.append(title); 157 | header.append(subtitle); 158 | } 159 | 160 | export default HomePage; 161 | ``` 162 | 163 |
164 | 165 | --- 166 | 167 | ### ➡ Funcionalidades ✔️ 168 | A aplicação realiza o CRUD da API (Create, Read, Update and Delete) e possui as seguintes funcionalidades: 169 | 170 | #### Funções principais: 171 | - Listagem de usuários ou usuário único 172 | - Criação de novo usuário 173 | - Alteração de dados de um usuário 174 | - Remoção de um usuário 175 | 176 | #### Outras funcionalidades: 177 | - Criação de arquivos HTML a partir do Javascript 178 | - Tratamento de erros retornados da api 179 | 180 | --- 181 | 182 | ## :books: O que aprendi 183 | 184 | [Voltar ao topo](#conteudos)
185 | 186 | Além de aprender como funcionam as closures, também fixei mais alguns conceitos: 187 | 188 | - Uso dos Prototypes 189 | - Uso da função Object.entries 190 | - Requisições com AJAX 191 | - Tratamento de erros com AJAX 192 | - Correção de erros de CORS da API 193 | - Uso das classes `URL` e `URLSearchParams` 194 | - Organização de código no geral 195 | 196 | --- 197 | 198 | ## :information_source: Como usar 199 | 200 | [Voltar ao topo](#conteudos)
201 | 202 | Para testar essa aplicação na sua máquina você precisa atender aos requisitos: 203 | - Baixar o repositório da API 204 | - Instalar a extensão [LiveServer](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) no seu Visual Studio Code 205 | 206 | 207 | ```bash 208 | # Clone this repository 209 | $ git clone https://github.com/joaovictornsv/http-node-api-web 210 | 211 | # Clone the server repository 212 | $ git clone https://github.com/joaovictornsv/http-node-api 213 | 214 | 215 | #### 🟡 BACK-END 216 | 217 | # Go into the server repository 218 | $ cd http-node-api 219 | 220 | # Run the server 221 | $ yarn start 222 | 223 | 224 | #### 🔵 FRONT-END 225 | 226 | # Back to this repository 227 | 228 | # Generate HTML files 229 | $ yarn start 230 | 231 | # Run frontend server 232 | ➜ Use 'Live Server' to run (Visual Studio Code Extension) 233 | ``` 234 | 235 |
236 | 237 |
238 | 239 | --- 240 | 241 |
242 | Profile 243 | Made with 💙 by João Victor 244 |
245 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-node-api-web", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "repository": "https://github.com/joaovictornsv/http-node-api-web.git", 6 | "author": "Joao Victor Negreiros ", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "node src/app.js" 10 | }, 11 | "type": "module" 12 | } 13 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import initFiles from './utils/initFiles.js'; 2 | 3 | (async () => await initFiles())(); -------------------------------------------------------------------------------- /src/components/divs/groupButtons.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | import makeButton from '../../factories/button.factory.js'; 3 | 4 | const groupButtons = makeDiv({ className: 'group-buttons'}); 5 | 6 | groupButtons.setCSS({ 7 | height: '160px', 8 | justifyContent: 'space-evenly', 9 | flexDirection: 'column' 10 | }); 11 | 12 | const listButton = makeButton({ value: 'Listar usuários', className: 'list-button' }); 13 | 14 | listButton.setCSS({ 15 | backgroundColor: '#3490de', 16 | borderRadius: '5px', 17 | border: 'none', 18 | color: 'white', 19 | fontSize: '18px', 20 | width: '150px', 21 | height: '50px', 22 | 23 | transition: 'background-color 0.2s' 24 | }, { 25 | backgroundColor: '#18568b' 26 | }); 27 | 28 | listButton.setOnclick(() => { 29 | window.location.assign('/users.html') 30 | }); 31 | 32 | 33 | const createButton = makeButton({ value: 'Criar usuário', className: 'create-button' }); 34 | 35 | createButton.setCSS({ 36 | backgroundColor: '#3fc1c9', 37 | border: 'none', 38 | borderRadius: '5px', 39 | color: 'white', 40 | fontSize: '18px', 41 | width: '150px', 42 | height: '50px', 43 | 44 | transition: 'background-color 0.2s' 45 | }, { 46 | backgroundColor: '#2B9197' 47 | }); 48 | 49 | createButton.setOnclick(() => { 50 | window.location.assign('/create.html') 51 | }); 52 | 53 | 54 | groupButtons.append(listButton); 55 | groupButtons.append(createButton); 56 | 57 | 58 | export default groupButtons; -------------------------------------------------------------------------------- /src/components/divs/headerCreate.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | import makeText from '../../factories/text.factory.js'; 3 | 4 | const headerCreate = makeDiv({}) 5 | 6 | export const titleMain = makeText({tag: 'h1', value: 'Criar um novo usuário'}); 7 | titleMain.setCSS({ 8 | fontSize: '22px', 9 | margin: 0, 10 | padding: 0, 11 | color: 'white', 12 | }); 13 | 14 | headerCreate.setCSS({ 15 | flexDirection: 'column', 16 | justifyContent: 'center', 17 | marginBottom: '40px', 18 | }); 19 | 20 | headerCreate.append(titleMain); 21 | 22 | export default headerCreate; -------------------------------------------------------------------------------- /src/components/divs/headerEdit.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | import makeText from '../../factories/text.factory.js'; 3 | 4 | const headerCreate = makeDiv({}) 5 | 6 | export const titleMain = makeText({tag: 'h1', value: 'Editar usuário'}); 7 | titleMain.setCSS({ 8 | fontSize: '22px', 9 | margin: 0, 10 | padding: 0, 11 | color: 'white', 12 | }); 13 | 14 | headerCreate.setCSS({ 15 | flexDirection: 'column', 16 | justifyContent: 'center', 17 | marginBottom: '40px', 18 | }); 19 | 20 | headerCreate.append(titleMain); 21 | 22 | export default headerCreate; -------------------------------------------------------------------------------- /src/components/divs/headerHome.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | import makeText from '../../factories/text.factory.js'; 3 | 4 | const headerMain = makeDiv({}) 5 | 6 | export const titleMain = makeText({tag: 'h1', value: 'HTTP NODE API'}); 7 | titleMain.setCSS({ 8 | fontSize: '22px', 9 | margin: 0, 10 | padding: 0, 11 | color: 'white', 12 | }); 13 | 14 | export const subtitleMain = makeText({tag: 'h2', value: 'Interface'}); 15 | 16 | subtitleMain.setCSS({ 17 | fontSize: '18px', 18 | margin: 0, 19 | padding: 0, 20 | color: 'white', 21 | }); 22 | 23 | headerMain.setCSS({ 24 | flexDirection: 'column', 25 | justifyContent: 'center', 26 | marginBottom: '40px', 27 | }); 28 | 29 | headerMain.append(titleMain); 30 | headerMain.append(subtitleMain); 31 | 32 | export default headerMain; -------------------------------------------------------------------------------- /src/components/divs/headerList.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | import makeText from '../../factories/text.factory.js'; 3 | 4 | const headerList = makeDiv({}) 5 | 6 | export const titleMain = makeText({tag: 'h1', value: 'Lista de usuários'}); 7 | titleMain.setCSS({ 8 | fontSize: '22px', 9 | margin: 0, 10 | padding: 0, 11 | color: 'white', 12 | }); 13 | 14 | 15 | headerList.setCSS({ 16 | flexDirection: 'column', 17 | justifyContent: 'center', 18 | margin: '120px auto 40px', 19 | }); 20 | 21 | headerList.append(titleMain); 22 | 23 | export default headerList; -------------------------------------------------------------------------------- /src/components/divs/headerUser.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | import makeText from '../../factories/text.factory.js'; 3 | 4 | const headerUser = makeDiv({}) 5 | 6 | export const titleMain = makeText({tag: 'h1', value: 'Detalhes do usuário'}); 7 | titleMain.setCSS({ 8 | fontSize: '22px', 9 | margin: 0, 10 | padding: 0, 11 | color: 'white', 12 | }); 13 | 14 | 15 | headerUser.setCSS({ 16 | flexDirection: 'column', 17 | justifyContent: 'center', 18 | margin: '80px auto 40px', 19 | }); 20 | 21 | headerUser.append(titleMain); 22 | 23 | export default headerUser; -------------------------------------------------------------------------------- /src/components/divs/listUsers.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | 3 | const listUsers = makeDiv({ className: 'list-users'}) 4 | 5 | listUsers.setCSS({ 6 | flexDirection: 'column' 7 | }); 8 | 9 | export default listUsers; -------------------------------------------------------------------------------- /src/components/divs/mainDiv.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | 3 | const mainDiv = makeDiv({ className: 'main'}) 4 | 5 | mainDiv.setCSS({ 6 | minHeight: '100vh', 7 | backgroundColor: '#1a293b', 8 | flexDirection: 'column' 9 | }); 10 | 11 | export default mainDiv; -------------------------------------------------------------------------------- /src/components/divs/mainDivList.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../../factories/div.factory.js'; 2 | 3 | const mainDivList = makeDiv({ className: 'main'}) 4 | 5 | mainDivList.setCSS({ 6 | minHeight: '100vh', 7 | backgroundColor: '#1a293b', 8 | flexDirection: 'column', 9 | justifyContent: 'start' 10 | }); 11 | 12 | export default mainDivList; -------------------------------------------------------------------------------- /src/components/forms/createForm.js: -------------------------------------------------------------------------------- 1 | import makeForm from '../../factories/form.factory.js'; 2 | import makeInput from '../../factories/input.factory.js'; 3 | import makeButton from '../../factories/button.factory.js'; 4 | 5 | const createUser = (query) => { 6 | const xhr = new XMLHttpRequest(); 7 | return new Promise((resolve, reject) => { 8 | try { 9 | xhr.open('POST', `http://localhost:3333/users/data?${query}`, true); 10 | xhr.send(); 11 | 12 | xhr.onreadystatechange = function(){ 13 | if (xhr.readyState === 4) { 14 | if (xhr.status === 201) { 15 | resolve(JSON.parse(xhr.responseText)); 16 | } 17 | else { 18 | if (xhr.responseText) { 19 | reject(JSON.parse(xhr.responseText).error); 20 | } else { 21 | reject('Erro na requisição'); 22 | } 23 | } 24 | } 25 | } 26 | } catch(err) { 27 | alert(err) 28 | } 29 | }) 30 | } 31 | 32 | const createForm = makeForm(); 33 | 34 | createForm.setOnSubmit(async (e) => { 35 | e.preventDefault(); 36 | 37 | const newName = document.getElementById('input_name').value; 38 | const newEmail = document.getElementById('input_email').value; 39 | const newAge = document.getElementById('input_age').value; 40 | const newCity = document.getElementById('input_city').value; 41 | 42 | const query = `name=${newName}&email=${newEmail}&age=${newAge}&city=${newCity}`; 43 | 44 | await createUser(query) 45 | .then(() => { 46 | alert(`Usuário '${newName}' criado!`); 47 | window.location.assign('/users.html'); 48 | }) 49 | .catch((err) =>{ 50 | alert(err) 51 | }); 52 | }) 53 | 54 | const inputCSS = { 55 | width: '250px', 56 | fontSize: '18px', 57 | padding: '10px', 58 | borderRadius: '4px', 59 | border: '2px solid #fff', 60 | outline: 'none', 61 | margin: '5px auto' 62 | } 63 | 64 | const inputClickCSS = { 65 | border: '2px solid #3fc1c9' 66 | } 67 | 68 | createForm.setCSS({ 69 | flexDirection: 'column' 70 | }) 71 | 72 | const nameInput = makeInput({ id: 'input_name', placeholder: 'Nome'}); 73 | const emailInput = makeInput({ id: 'input_email', placeholder: 'Email'}); 74 | const ageInput = makeInput({ id: 'input_age', placeholder: 'Idade', type: 'number'}); 75 | const cityInput = makeInput({ id: 'input_city', placeholder: 'Cidade'}); 76 | 77 | nameInput.setCSS(inputCSS, inputClickCSS); 78 | emailInput.setCSS(inputCSS, inputClickCSS); 79 | ageInput.setCSS(inputCSS, inputClickCSS); 80 | cityInput.setCSS(inputCSS, inputClickCSS); 81 | 82 | const submitButton = makeButton({ value: 'Criar'}); 83 | submitButton.setCSS({ 84 | backgroundColor: '#3fc1c9', 85 | border: 'none', 86 | borderRadius: '5px', 87 | color: 'white', 88 | fontSize: '18px', 89 | marginTop: '20px', 90 | transition: 'background-color 0.2s', 91 | width: '150px', 92 | height: '50px', 93 | }, { 94 | backgroundColor: '#2B9197' 95 | }); 96 | 97 | createForm.append(nameInput); 98 | createForm.append(emailInput); 99 | createForm.append(ageInput); 100 | createForm.append(cityInput); 101 | createForm.append(submitButton); 102 | 103 | export default createForm; -------------------------------------------------------------------------------- /src/components/forms/editForm.js: -------------------------------------------------------------------------------- 1 | import makeForm from '../../factories/form.factory.js'; 2 | import makeInput from '../../factories/input.factory.js'; 3 | import makeButton from '../../factories/button.factory.js'; 4 | 5 | const params = new URLSearchParams(document.location.search.substring(1)); 6 | 7 | const editForm = makeForm(); 8 | 9 | const nameValue = params.get('name') || ''; 10 | const ageValue = params.get('age') || ''; 11 | const emailValue = params.get('email') || ''; 12 | const cityValue = params.get('city') || ''; 13 | const idValue = params.get('id') || ''; 14 | 15 | const updateUser = (query) => { 16 | const xhr = new XMLHttpRequest(); 17 | return new Promise((resolve, reject) => { 18 | try { 19 | xhr.open('PUT', `http://localhost:3333/users/${idValue}/data?${query}`, true); 20 | xhr.send(); 21 | 22 | xhr.onreadystatechange = function(){ 23 | if (xhr.readyState === 4) { 24 | if (xhr.status === 200) { 25 | resolve(JSON.parse(xhr.responseText)); 26 | } 27 | if (xhr.responseText) { 28 | reject(JSON.parse(xhr.responseText).error); 29 | } else { 30 | reject('Erro na requisição'); 31 | } 32 | } 33 | } 34 | }catch(err) { 35 | alert(err) 36 | } 37 | }) 38 | } 39 | 40 | const inputCSS = { 41 | width: '250px', 42 | fontSize: '18px', 43 | padding: '10px', 44 | borderRadius: '4px', 45 | border: '2px solid #fff', 46 | outline: 'none', 47 | margin: '5px auto' 48 | } 49 | 50 | const inputClickCSS = { 51 | border: '2px solid #3fc1c9' 52 | } 53 | 54 | editForm.setCSS({ 55 | flexDirection: 'column' 56 | }) 57 | 58 | editForm.setOnSubmit(async (e) => { 59 | e.preventDefault(); 60 | 61 | const newName = document.getElementById('input_name').value; 62 | const newEmail = document.getElementById('input_email').value; 63 | const newAge = document.getElementById('input_age').value; 64 | const newCity = document.getElementById('input_city').value; 65 | 66 | const query = `name=${newName}&email=${newEmail}&age=${newAge}&city=${newCity}`; 67 | 68 | await updateUser(query) 69 | .then(() => { 70 | alert(`Usuário '${newName}' atualizado!`); 71 | window.location.assign('/users.html'); 72 | }) 73 | .catch((err) =>{ 74 | alert(err) 75 | }); 76 | }) 77 | 78 | const nameInput = makeInput({ id: 'input_name', placeholder: 'Nome', value: nameValue}); 79 | const emailInput = makeInput({ id: 'input_email', placeholder: 'Email', value: emailValue}); 80 | const ageInput = makeInput({ id: 'input_age', placeholder: 'Idade', type: 'number', value: ageValue}); 81 | const cityInput = makeInput({ id: 'input_city', placeholder: 'Cidade', value: cityValue}); 82 | 83 | nameInput.setCSS(inputCSS, inputClickCSS); 84 | emailInput.setCSS(inputCSS, inputClickCSS); 85 | ageInput.setCSS(inputCSS, inputClickCSS); 86 | cityInput.setCSS(inputCSS, inputClickCSS); 87 | 88 | const submitButton = makeButton({ value: 'Salvar', type: 'submit' }); 89 | submitButton.setCSS({ 90 | backgroundColor: '#3fc1c9', 91 | border: 'none', 92 | borderRadius: '5px', 93 | color: 'white', 94 | fontSize: '18px', 95 | marginTop: '20px', 96 | transition: 'background-color 0.2s', 97 | width: '150px', 98 | height: '50px', 99 | }, { 100 | backgroundColor: '#2B9197' 101 | }); 102 | 103 | editForm.append(nameInput); 104 | editForm.append(emailInput); 105 | editForm.append(ageInput); 106 | editForm.append(cityInput); 107 | editForm.append(submitButton); 108 | 109 | export default editForm; -------------------------------------------------------------------------------- /src/factories/button.factory.js: -------------------------------------------------------------------------------- 1 | import buttonPrototype from '../prototypes/button.prototype.js' 2 | 3 | function makeButton(config={}) { 4 | let initialValue = config.value || 'Button'; 5 | let initialFunc = null; 6 | let initialType = config.type || null; 7 | 8 | let initialCss = { 9 | background: 'white', 10 | borderRadius: '8px', 11 | cursor: 'pointer', 12 | } 13 | 14 | const component = document.createElement('button'); 15 | component.setValue(initialValue); 16 | component.setCSS(initialCss); 17 | initialType && component.setType(initialType); 18 | initialFunc && component.setOnclick(initialFunc); 19 | 20 | return component 21 | } 22 | 23 | export default makeButton; -------------------------------------------------------------------------------- /src/factories/div.factory.js: -------------------------------------------------------------------------------- 1 | import common from '../prototypes/common.prototype.js' 2 | import div from '../prototypes/div.prototype.js' 3 | 4 | function makeDiv(config={}) { 5 | let initialClassName = config.className; 6 | 7 | let initialCss = { 8 | width: '100%', 9 | height: '100%', 10 | display: 'flex', 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | } 14 | 15 | const component = document.createElement('div'); 16 | config.className && component.setClass(initialClassName); 17 | component.setCSS(initialCss); 18 | 19 | return component; 20 | } 21 | 22 | 23 | export default makeDiv; -------------------------------------------------------------------------------- /src/factories/form.factory.js: -------------------------------------------------------------------------------- 1 | import formPrototype from '../prototypes/form.prototype.js' 2 | 3 | function makeForm(config={}) { 4 | let initialClassName = config.className || 'myForm'; 5 | let initialFunc = null; 6 | 7 | let initialCss = { 8 | width: '100%', 9 | height: '100%', 10 | display: 'flex', 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | } 14 | 15 | const component = document.createElement('form'); 16 | config.className && component.setClass(initialClassName); 17 | initialFunc && component.setOnSubmit(initialFunc); 18 | component.setCSS(initialCss); 19 | 20 | return component; 21 | } 22 | 23 | export default makeForm; -------------------------------------------------------------------------------- /src/factories/input.factory.js: -------------------------------------------------------------------------------- 1 | import inputPrototype from '../prototypes/input.prototype.js' 2 | 3 | function makeInput(config={}) { 4 | let initialPlaceholder = config.placeholder || 'Type here'; 5 | let initialValue = config.value || null; 6 | let initialId = config.id || 'input_id'; 7 | let initialType = config.type || 'text'; 8 | 9 | const component = document.createElement('input'); 10 | component.required = true; 11 | initialValue && component.setValue(initialValue); 12 | component.setType(initialType) 13 | component.setID(initialId); 14 | component.setPlaceholder(initialPlaceholder); 15 | component.setMinMax(0, 200); 16 | 17 | return component 18 | } 19 | 20 | export default makeInput; -------------------------------------------------------------------------------- /src/factories/text.factory.js: -------------------------------------------------------------------------------- 1 | function makeText(config={}) { 2 | let initialValue = config.value || `Text ${tag}`; 3 | let tag = config.tag || 'h1'; 4 | 5 | let initialCss = { 6 | fontSize: '14px', 7 | color: 'black', 8 | fontFamily: 'Arial, sans-serif' 9 | } 10 | 11 | const component = document.createElement(tag); 12 | component.innerText = initialValue; 13 | component.setCSS(initialCss); 14 | 15 | 16 | return component; 17 | } 18 | 19 | 20 | export default makeText; -------------------------------------------------------------------------------- /src/pages/create.js: -------------------------------------------------------------------------------- 1 | import mainDiv from '../components/divs/mainDiv.js'; 2 | import resetCSS from '../utils/reset.js' 3 | import createForm from '../components/forms/createForm.js' 4 | import headerCreate from '../components/divs/headerCreate.js' 5 | 6 | resetCSS(); 7 | 8 | function CreatePage() { 9 | mainDiv.initOnRoot(); 10 | mainDiv.append(headerCreate); 11 | mainDiv.append(createForm); 12 | } 13 | 14 | export default CreatePage(); 15 | -------------------------------------------------------------------------------- /src/pages/edit.js: -------------------------------------------------------------------------------- 1 | import mainDiv from '../components/divs/mainDiv.js'; 2 | import resetCSS from '../utils/reset.js' 3 | import editForm from '../components/forms/editForm.js' 4 | import headerEdit from '../components/divs/headerEdit.js' 5 | 6 | resetCSS(); 7 | 8 | function EditPage() { 9 | mainDiv.initOnRoot(); 10 | mainDiv.append(headerEdit); 11 | mainDiv.append(editForm); 12 | } 13 | 14 | export default EditPage(); 15 | -------------------------------------------------------------------------------- /src/pages/home.js: -------------------------------------------------------------------------------- 1 | import mainDiv from '../components/divs/mainDiv.js'; 2 | import groupButtons from '../components/divs/groupButtons.js' 3 | import headerHome from '../components/divs/headerHome.js' 4 | 5 | import resetCSS from '../utils/reset.js' 6 | 7 | resetCSS(); 8 | 9 | function HomePage() { 10 | mainDiv.initOnRoot(); 11 | mainDiv.append(headerHome); 12 | mainDiv.append(groupButtons); 13 | } 14 | 15 | export default HomePage(); 16 | -------------------------------------------------------------------------------- /src/pages/user.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../factories/div.factory.js'; 2 | import makeButton from '../factories/button.factory.js'; 3 | import makeText from '../factories/text.factory.js'; 4 | 5 | import mainDivList from '../components/divs/mainDivList.js'; 6 | import resetCSS from '../utils/reset.js'; 7 | import headerUser from '../components/divs/headerUser.js'; 8 | import listUsers from '../components/divs/listUsers.js'; 9 | 10 | resetCSS(); 11 | 12 | const params = new URLSearchParams(document.location.search.substring(1)); 13 | const idValue = params.get('id'); 14 | 15 | 16 | const getUser = (id) => { 17 | const xhr = new XMLHttpRequest(); 18 | return new Promise((resolve, reject) => { 19 | try { 20 | xhr.open('GET', `http://localhost:3333/users/${id}`, true); 21 | xhr.send(); 22 | 23 | xhr.onreadystatechange = function(){ 24 | if (xhr.readyState === 4) { 25 | if (xhr.status === 200) { 26 | resolve(JSON.parse(xhr.responseText)); 27 | } 28 | if (xhr.responseText) { 29 | reject(JSON.parse(xhr.responseText).error); 30 | } else { 31 | reject('Erro na requisição'); 32 | } 33 | } 34 | } 35 | } catch(err) { 36 | alert(err) 37 | } 38 | }) 39 | } 40 | 41 | 42 | function renderUserInfo(user) { 43 | let card = makeDiv({ className: 'groupFields'}); 44 | card.setCSS({ 45 | width: '450px', 46 | padding: '10px', 47 | height: '300px', 48 | flexDirection: 'column', 49 | justifyContent: 'space-between', 50 | marginBottom: '40px' 51 | }); 52 | 53 | let nameField = makeDiv(); 54 | nameField.setCSS({ 55 | alignItems: 'start', 56 | flexDirection: 'column', 57 | height: 'max-content', 58 | }); 59 | 60 | let nameLegend = makeText({tag: 'span', value: 'Nome:'}); 61 | nameLegend.setCSS({ 62 | color: 'white', 63 | fontSize: '16px', 64 | fontWeight: 'bold', 65 | marginBottom: '6px' 66 | }); 67 | let nameValue = makeText({tag: 'span', value: user.name}); 68 | nameValue.setCSS({ 69 | color: 'white', 70 | fontSize: '22px', 71 | marginBottom: '4px' 72 | }); 73 | 74 | 75 | let emailField = makeDiv(); 76 | emailField.setCSS({ 77 | alignItems: 'start', 78 | flexDirection: 'column', 79 | height: 'max-content', 80 | }); 81 | 82 | let emailLegend = makeText({tag: 'span', value: 'Email:'}); 83 | emailLegend.setCSS({ 84 | color: 'white', 85 | fontSize: '16px', 86 | fontWeight: 'bold', 87 | marginBottom: '6px' 88 | }); 89 | let emailValue = makeText({tag: 'span', value: user.email}); 90 | emailValue.setCSS({ 91 | color: 'white', 92 | fontSize: '22px', 93 | marginBottom: '4px' 94 | }); 95 | 96 | 97 | let ageField = makeDiv(); 98 | ageField.setCSS({ 99 | alignItems: 'start', 100 | flexDirection: 'column', 101 | height: 'max-content', 102 | }); 103 | 104 | let ageLegend = makeText({tag: 'span', value: 'Idade:'}); 105 | ageLegend.setCSS({ 106 | color: 'white', 107 | fontSize: '16px', 108 | fontWeight: 'bold', 109 | marginBottom: '6px' 110 | }); 111 | let ageValue = makeText({tag: 'span', value: user.age}); 112 | ageValue.setCSS({ 113 | color: 'white', 114 | fontSize: '22px', 115 | marginBottom: '4px' 116 | }); 117 | 118 | 119 | let cityField = makeDiv(); 120 | cityField.setCSS({ 121 | alignItems: 'start', 122 | flexDirection: 'column', 123 | height: 'max-content', 124 | }); 125 | 126 | let cityLegend = makeText({tag: 'span', value: 'Cidade:'}); 127 | cityLegend.setCSS({ 128 | color: 'white', 129 | fontSize: '16px', 130 | fontWeight: 'bold', 131 | marginBottom: '6px' 132 | }); 133 | let cityValue = makeText({tag: 'span', value: user.city}); 134 | cityValue.setCSS({ 135 | color: 'white', 136 | fontSize: '22px', 137 | marginBottom: '4px' 138 | }); 139 | 140 | 141 | nameField.append(nameLegend, nameValue); 142 | emailField.append(emailLegend, emailValue); 143 | ageField.append(ageLegend, ageValue); 144 | cityField.append(cityLegend, cityValue); 145 | 146 | card.append(nameField, emailField, ageField, cityField); 147 | 148 | listUsers.append(card); 149 | } 150 | 151 | 152 | function renderNotFoundUser(text){ 153 | let message = makeText({tag: 'span', value: text}); 154 | message.setCSS({ 155 | color: 'white', 156 | fontSize: '22px', 157 | margin: '40px 0 60px' 158 | }); 159 | listUsers.append(message); 160 | } 161 | 162 | async function UserPage() { 163 | mainDivList.initOnRoot(); 164 | mainDivList.append(headerUser); 165 | mainDivList.append(listUsers); 166 | 167 | await getUser(idValue) 168 | .then((response) => { 169 | renderUserInfo(response); 170 | }) 171 | .catch(() =>{ 172 | renderNotFoundUser('Erro na requisição do usuário') 173 | }); 174 | 175 | const backButton = makeButton({ value: 'Voltar' }); 176 | backButton.setCSS({ 177 | backgroundColor: '#3fc1c9', 178 | border: 'none', 179 | borderRadius: '5px', 180 | color: 'black', 181 | fontSize: '18px', 182 | marginTop: '20px', 183 | transition: 'background-color 0.2s', 184 | width: '150px', 185 | height: '50px', 186 | }, { 187 | backgroundColor: '#2B9197' 188 | }); 189 | 190 | backButton.setOnclick(() => { 191 | window.location.assign('/users.html') 192 | }) 193 | 194 | listUsers.append(backButton); 195 | 196 | } 197 | 198 | export default UserPage(); 199 | -------------------------------------------------------------------------------- /src/pages/users.js: -------------------------------------------------------------------------------- 1 | import makeDiv from '../factories/div.factory.js'; 2 | import makeButton from '../factories/button.factory.js'; 3 | import makeText from '../factories/text.factory.js'; 4 | 5 | import mainDivList from '../components/divs/mainDivList.js'; 6 | import resetCSS from '../utils/reset.js'; 7 | import headerList from '../components/divs/headerList.js'; 8 | import listUsers from '../components/divs/listUsers.js'; 9 | 10 | resetCSS(); 11 | 12 | 13 | const getUsers = () => { 14 | const xhr = new XMLHttpRequest(); 15 | return new Promise((resolve, reject) => { 16 | try { 17 | xhr.open('GET', 'http://localhost:3333/users', true); 18 | xhr.send(); 19 | 20 | xhr.onreadystatechange = function(){ 21 | if (xhr.readyState === 4) { 22 | if (xhr.status === 200) { 23 | resolve(JSON.parse(xhr.responseText)); 24 | } 25 | if (xhr.responseText) { 26 | reject(JSON.parse(xhr.responseText).error); 27 | } else { 28 | reject('Erro na requisição'); 29 | } 30 | } 31 | } 32 | }catch(err) { 33 | alert(err) 34 | } 35 | }) 36 | } 37 | 38 | const deleteUser = (id) => { 39 | const xhr = new XMLHttpRequest(); 40 | return new Promise((resolve, reject) => { 41 | try { 42 | xhr.open('DELETE', `http://localhost:3333/users/${id}`, true); 43 | xhr.send(); 44 | 45 | xhr.onreadystatechange = function(){ 46 | if (xhr.readyState === 4) { 47 | if (xhr.status === 200) { 48 | resolve(JSON.parse(xhr.responseText)); 49 | } 50 | else { 51 | reject(JSON.parse(xhr.responseText).error); 52 | } 53 | } 54 | } 55 | } catch(err) { 56 | renderNotFoundUsers(err) 57 | } 58 | }) 59 | } 60 | 61 | function renderUsers(users) { 62 | users.forEach(user => { 63 | let card = makeDiv(); 64 | card.setCSS({ 65 | width: '450px', 66 | padding: '10px', 67 | borderBottom: '1px solid #cecece', 68 | }); 69 | 70 | let groupText = makeDiv(); 71 | groupText.setCSS({ 72 | alignItems: 'start', 73 | flexDirection: 'column', 74 | }); 75 | 76 | let name = makeText({tag: 'span', value: user.name}); 77 | name.setCSS({ 78 | color: 'white', 79 | fontSize: '20px', 80 | fontWeight: 'bold', 81 | marginBottom: '4px' 82 | }); 83 | 84 | let email = makeText({tag: 'span', value: user.email}); 85 | email.setCSS({ 86 | color: 'white', 87 | fontSize: '16px' 88 | }); 89 | 90 | groupText.append(name, email); 91 | 92 | 93 | let groupButton = makeDiv(); 94 | groupButton.setCSS({ 95 | justifyContent: 'space-between', 96 | flexDirection: 'row', 97 | }); 98 | 99 | let seeButton = makeButton({ value: 'Ver' }); 100 | 101 | seeButton.setCSS({ 102 | backgroundColor: '#227FCE', 103 | borderRadius: '5px', 104 | border: 'none', 105 | color: 'white', 106 | fontSize: '14px', 107 | padding: '10px 15px', 108 | transition: 'background-color 0.2s' 109 | }, { 110 | backgroundColor: '#1A619E' 111 | }); 112 | 113 | seeButton.setOnclick(() => { 114 | window.location.assign(`/user.html?id=${user.id}`) 115 | }); 116 | 117 | let editButton = makeButton({ value: 'Editar' }); 118 | editButton.setCSS({ 119 | backgroundColor: '#21DEC1', 120 | borderRadius: '5px', 121 | border: 'none', 122 | color: 'black', 123 | fontSize: '14px', 124 | padding: '10px 15px', 125 | transition: 'background-color 0.2s' 126 | }, { 127 | backgroundColor: '#1AAD97' 128 | }); 129 | 130 | editButton.setOnclick(() => { 131 | window.location.assign(`/edit.html?name=${user.name}&age=${user.age}&email=${user.email}&city=${user.city}&id=${user.id}`) 132 | }); 133 | 134 | 135 | let deleteButton = makeButton({ value: 'Deletar' }); 136 | 137 | deleteButton.setCSS({ 138 | backgroundColor: '#e23e57', 139 | borderRadius: '5px', 140 | border: 'none', 141 | color: 'white', 142 | fontSize: '14px', 143 | padding: '10px 15px', 144 | transition: 'background-color 0.2s' 145 | }, { 146 | backgroundColor: '#BA1C34' 147 | }); 148 | 149 | deleteButton.setOnclick(async () => { 150 | let sure = confirm(`Tem certeza que deseja deletar '${user.name}'?`); 151 | 152 | if (sure) { 153 | await deleteUser(user.id) 154 | .then(() => { 155 | alert(`Usuário '${user.name}' deletado!`); 156 | window.location.reload() 157 | }) 158 | .catch((err) =>{ 159 | alert(err) 160 | });; 161 | } 162 | }); 163 | 164 | groupButton.append(seeButton, editButton, deleteButton); 165 | 166 | card.append(groupText); 167 | card.append(groupButton); 168 | 169 | listUsers.append(card); 170 | }) 171 | } 172 | 173 | 174 | function renderNotFoundUsers(text=''){ 175 | let message = makeText({tag: 'span', value: text}); 176 | message.setCSS({ 177 | color: 'white', 178 | fontSize: '22px', 179 | margin: '40px 0 60px' 180 | }); 181 | listUsers.append(message); 182 | } 183 | 184 | async function CreatePage() { 185 | mainDivList.initOnRoot(); 186 | mainDivList.append(headerList); 187 | mainDivList.append(listUsers); 188 | 189 | await getUsers() 190 | .then((response) => { 191 | if (response.length > 0) { 192 | renderUsers(response); 193 | } else { 194 | renderNotFoundUsers('Sem usuários') 195 | } 196 | }) 197 | .catch(() =>{ 198 | renderNotFoundUsers('Erro na requisição') 199 | }); 200 | 201 | const backButton = makeButton({ value: 'Voltar' }); 202 | backButton.setCSS({ 203 | backgroundColor: '#3fc1c9', 204 | border: 'none', 205 | borderRadius: '5px', 206 | color: 'black', 207 | fontSize: '18px', 208 | margin: '20px 0', 209 | transition: 'background-color 0.2s', 210 | width: '150px', 211 | height: '50px', 212 | }, { 213 | backgroundColor: '#2B9197' 214 | }); 215 | 216 | backButton.setOnclick(() => { 217 | window.location.assign('/index.html') 218 | }) 219 | 220 | listUsers.append(backButton); 221 | 222 | } 223 | 224 | export default CreatePage(); 225 | -------------------------------------------------------------------------------- /src/prototypes/button.prototype.js: -------------------------------------------------------------------------------- 1 | HTMLButtonElement.prototype.setValue = function setValue(new_value) { 2 | this.innerText = new_value; 3 | } 4 | 5 | HTMLButtonElement.prototype.setOnclick = function setOnclick(new_func) { 6 | this.onclick = new_func; 7 | } 8 | 9 | HTMLButtonElement.prototype.setCSS = function setCSS(new_css, hover_css={ 10 | background: 'gray' 11 | }) { 12 | this.overwriteCSS(new_css); 13 | this.addEventListener('mouseenter', () => this.overwriteCSS(hover_css)); 14 | this.addEventListener('mouseout', () => this.overwriteCSS(new_css)); 15 | } 16 | 17 | HTMLButtonElement.prototype.setType = function setType(new_type) { 18 | this.type = new_type; 19 | } 20 | 21 | export default HTMLButtonElement.prototype; -------------------------------------------------------------------------------- /src/prototypes/common.prototype.js: -------------------------------------------------------------------------------- 1 | HTMLElement.prototype.overwriteCSS = function overwriteCSS(new_css) { 2 | for (let [key, value] of Object.entries(new_css)) { 3 | this.style[key] = value 4 | } 5 | } 6 | 7 | HTMLElement.prototype.setClass = function setClass(new_name) { 8 | this.className = new_name; 9 | } 10 | 11 | HTMLElement.prototype.setCSS = function setCSS(new_css) { 12 | this.overwriteCSS(new_css); 13 | } 14 | 15 | export default HTMLElement.prototype; -------------------------------------------------------------------------------- /src/prototypes/div.prototype.js: -------------------------------------------------------------------------------- 1 | HTMLDivElement.prototype.initOnRoot = function initOnRoot() { 2 | const div = document.getElementById('root'); 3 | div.appendChild(this); 4 | } 5 | 6 | export default HTMLDivElement.prototype; 7 | -------------------------------------------------------------------------------- /src/prototypes/form.prototype.js: -------------------------------------------------------------------------------- 1 | HTMLFormElement.prototype.setOnSubmit = function setOnSubmit(new_func) { 2 | this.onsubmit = new_func; 3 | } 4 | 5 | export default HTMLFormElement.prototype; -------------------------------------------------------------------------------- /src/prototypes/input.prototype.js: -------------------------------------------------------------------------------- 1 | HTMLInputElement.prototype.setID = function setID(new_id) { 2 | this.id = new_id; 3 | } 4 | 5 | HTMLInputElement.prototype.setMinMax = function setMinMax(min, max) { 6 | this.min = min; 7 | this.max = max; 8 | } 9 | 10 | HTMLInputElement.prototype.setType = function setType(new_type) { 11 | this.type = new_type; 12 | } 13 | 14 | HTMLInputElement.prototype.setValue = function setValue(new_value) { 15 | this.value = new_value; 16 | } 17 | 18 | HTMLInputElement.prototype.setPlaceholder = function setPlaceholder(text) { 19 | this.placeholder = text; 20 | } 21 | 22 | HTMLInputElement.prototype.setCSS = function setCSS(new_css, focus_css={ 23 | border: 'black' 24 | }) { 25 | this.overwriteCSS(new_css); 26 | this.addEventListener('focusin', () => this.overwriteCSS(focus_css)); 27 | this.addEventListener('focusout', () => this.overwriteCSS(new_css)); 28 | } 29 | 30 | export default HTMLInputElement.prototype; -------------------------------------------------------------------------------- /src/utils/initFiles.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import path from 'path'; 3 | 4 | const filesHTML = { 5 | index:` 6 | 7 | 8 | 9 | 10 | 11 | 12 | HTTP NODE API WEB 13 | 14 | 15 |
16 | 17 | 18 | 19 | `, 20 | 21 | create:` 22 | 23 | 24 | 25 | 26 | 27 | 28 | HTTP NODE API WEB 29 | 30 | 31 |
32 | 33 | 34 | 35 | `, 36 | 37 | edit:` 38 | 39 | 40 | 41 | 42 | 43 | 44 | HTTP NODE API WEB 45 | 46 | 47 |
48 | 49 | 50 | 51 | `, 52 | 53 | users:` 54 | 55 | 56 | 57 | 58 | 59 | 60 | HTTP NODE API WEB 61 | 62 | 63 |
64 | 65 | 66 | 67 | `, 68 | 69 | user:` 70 | 71 | 72 | 73 | 74 | 75 | 76 | HTTP NODE API WEB 77 | 78 | 79 |
80 | 81 | 82 | 83 | ` 84 | } 85 | async function initFiles(log=true) { 86 | log && console.log('⌛ Generating HTML files...'); 87 | for(let [key, value] of Object.entries(filesHTML)) { 88 | const pathFile = (new URL(`../../${key}.html`, import.meta.url)); 89 | await fs.writeFile(pathFile, value); 90 | log && console.log(`✅ ${key}.html created`); 91 | } 92 | } 93 | 94 | export default initFiles; -------------------------------------------------------------------------------- /src/utils/reset.js: -------------------------------------------------------------------------------- 1 | function resetCSS() { 2 | const root = document.getElementById('root'); 3 | const body = document.querySelector('body'); 4 | 5 | body.style.margin = '0'; 6 | body.style.padding = '0'; 7 | body.style.boxSizing = 'border-box'; 8 | 9 | root.style.margin = '0'; 10 | root.style.padding = '0'; 11 | root.style.boxSizing = 'border-box'; 12 | } 13 | 14 | export default resetCSS; --------------------------------------------------------------------------------