├── .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 |
11 |
12 |
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 |
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;
--------------------------------------------------------------------------------