O que é CRUD?
Você já deve estar se perguntando: “o que significa CRUD?!” Um aplicativo CRUD é a forma mais comum de qualquer aplicativo de software. CRUD significa Crio, Ler, Atualizar e Excluir e se aplica a qualquer software capaz de permitir que os usuários criem e visualizem dados em sua interface, bem como façam alterações e excluam dados existentes.
Os aplicativos CRUD podem variar de um simples aplicativo de lista de tarefas a uma plataforma de mídia social mais complexa. Também é comum que os desenvolvedores criem aplicativos CRUD para exibir sua compreensão de uma determinada estrutura ou linguagem.
1. Criando a marcação
Para nossa marcação, teremos dois elementos principais: o new-note
contêiner que contém o campo de entrada e área de texto para inserir o conteúdo de nossas anotações e o notes-wrapper
container que conterá as notas criadas. Nós também temos um form-error
div para exibir mensagens de erro e um botão que chama o addNote()
função que definiremos mais tarde.
1 |
|
2 |
|
3 |
type="text" name="title" id="title" placeholder="Title">
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
2. Estilizando o layout
Este é o CSS para nosso layout inicial:
1 |
.new-note { |
2 |
margin-bottom: 32px; |
3 |
background-color: white; |
4 |
border: 1px solid #dadada; |
5 |
border-radius: 6px; |
6 |
padding: 16px; |
7 |
position: relative; |
8 |
}
|
9 |
|
10 |
.new-note input { |
11 |
width: 100%; |
12 |
background: transparent; |
13 |
border: none; |
14 |
outline: none; |
15 |
font-size: 1.5rem; |
16 |
font-weight: 700; |
17 |
padding: 0 0 12px; |
18 |
}
|
19 |
|
20 |
.new-note textarea { |
21 |
display: block; |
22 |
width: 100%; |
23 |
min-height: 100px; |
24 |
border: none; |
25 |
resize: none; |
26 |
background: transparent; |
27 |
outline: none; |
28 |
padding: 0; |
29 |
}
|
30 |
|
31 |
#form-error { |
32 |
color: #b33030; |
33 |
position: absolute; |
34 |
bottom: -32px; |
35 |
left: 0; |
36 |
}
|
37 |
|
38 |
.button-container { |
39 |
display: flex; |
40 |
justify-content: center; |
41 |
margin-bottom: 32px; |
42 |
}
|
43 |
|
44 |
.add-btn { |
45 |
border: 1px solid rgb(95, 95, 95); |
46 |
background-color: transparent; |
47 |
transition: background-color 250ms, color 250ms; |
48 |
border-radius: 16px; |
49 |
cursor: pointer; |
50 |
padding: 8px 12px; |
51 |
}
|
52 |
|
53 |
.add-btn:hover { |
54 |
background-color: rgb(95, 95, 95); |
55 |
color: white; |
56 |
}
|
57 |
|
58 |
#notes-wrapper { |
59 |
display: flex; |
60 |
flex-direction: column; |
61 |
gap: 32px; |
62 |
}
|
63 |
|
64 |
.note { |
65 |
position: relative; |
66 |
overflow: hidden; |
67 |
background: white; |
68 |
border: 1px solid #dadada; |
69 |
border-radius: 6px; |
70 |
padding: 16px; |
71 |
}
|
72 |
|
73 |
.note-title { |
74 |
font-size: 1.5em; |
75 |
font-weight: 700; |
76 |
margin-bottom: 12px; |
77 |
width: 100%; |
78 |
display: inline-block; |
79 |
overflow-wrap: break-word; |
80 |
white-space: pre-wrap; |
81 |
}
|
82 |
|
83 |
.note-title:last-child { |
84 |
margin-bottom: 0; |
85 |
}
|
86 |
|
87 |
.note-text { |
88 |
margin-bottom: 16px; |
89 |
background-color: white; |
90 |
width: 100%; |
91 |
overflow-wrap: break-word; |
92 |
white-space: pre-wrap; |
93 |
background-color: white; |
94 |
overflow: hidden; |
95 |
width: 100%; |
96 |
}
|
97 |
|
98 |
.note-date { |
99 |
font-size: 0.75em; |
100 |
text-align: right; |
101 |
border-top: 1px solid #dadada; |
102 |
padding-top: 16px; |
103 |
width: 100%; |
104 |
margin-top: auto; |
105 |
}
|
106 |
|
107 |
.note-controls { |
108 |
position: absolute; |
109 |
right: 0; |
110 |
top: 0; |
111 |
background-color: white; |
112 |
font-size: 0.75rem; |
113 |
column-gap: 8px; |
114 |
padding: 8px; |
115 |
display: flex; |
116 |
opacity: 0; |
117 |
transition: opacity 350ms; |
118 |
}
|
119 |
|
120 |
.note:hover .note-controls, |
121 |
.note-controls:focus-within { |
122 |
opacity: 1; |
123 |
}
|
124 |
|
125 |
.note-controls button { |
126 |
padding: 0; |
127 |
border: none; |
128 |
background-color: transparent; |
129 |
cursor: pointer; |
130 |
padding: 0.5rem; |
131 |
}
|
132 |
|
133 |
.note-controls button:hover { |
134 |
filter: brightness(0.85) |
135 |
}
|
136 |
|
137 |
.note-delete { |
138 |
color: #bb0000; |
139 |
}
|
140 |
|
141 |
.note-edit { |
142 |
color: #00bb00; |
143 |
}
|
144 |
|
145 |
.note-save { |
146 |
color: #0000bb; |
147 |
}
|
148 |
|
149 |
.note-save[disabled="true"] { |
150 |
color: #dfdfdf; |
151 |
pointer-events: none; |
152 |
user-select: none; |
153 |
cursor: not-allowed; |
154 |
}
|
Nesta demonstração, removemos o estilo de contorno em nosso input
e textarea
campos para evitar interferir com o estilo. No entanto, se desejar, você pode deixar o estilo do contorno para fins de acessibilidade.
É assim que nosso layout se parece atualmente:
3. Criação e leitura de dados
Agora podemos começar a trabalhar na lógica da nossa aplicação!
Elementos Globais
Primeiro, vamos obter todos os elementos globais de que precisamos.
1 |
const notesWrapper = document.getElementById("notes-wrapper"); |
2 |
const title = document.getElementById("title"); |
3 |
const content = document.getElementById("content"); |
4 |
const error = document.getElementById("form-error"); |
Em seguida, definiremos uma variável global para armazenar nossos dados de anotações.
Criando e armazenando dados
Para criar os dados, vamos definir um addNote()
função a ser chamada quando o botão é clicado. Isso é o que irá manipular os dados do campo de entrada e colocá-los em um novo elemento de nota em um formato legível.
Para começar, vamos incluir uma condição para verificar se há algum conteúdo a ser adicionado em uma nova nota. Se os campos de título e conteúdo estiverem vazios, exibiremos uma mensagem de erro.
1 |
const addNote = () => { |
2 |
if (title.value.trim().length == 0 && content.value.trim().length == 0) { |
3 |
error.innerText = "Note cannot be empty"; |
4 |
return; |
5 |
}
|
6 |
};
|
Em seguida, criamos um objeto para conter a data de uma nova nota. Neste tutorial, geraremos um valor de id exclusivo (uid) para cada nota usando o date.getTime()
método. Isso retorna o milissegundo exato em que a nota foi criada e é usado para garantir que não haja duas notas com o mesmo id.
Também incluiremos o valor da data de quando a nota foi criada usando o date.toLocaleDateString()
método. É assim que nossa função atualizada se parece:
1 |
const noteObj = { |
2 |
uid: new Date().getTime().toString(), |
3 |
title: title.value, |
4 |
text: content.value, |
5 |
date: new Date().toLocaleDateString() |
6 |
};
|
Agora que temos o objeto note, podemos armazenar esses dados no array notesData e também no localStorage do navegador.
1 |
notesData.push(noteObj); |
2 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
Exibindo Dados
Vamos definir uma função createNote()
para lidar com a anexação da nova nota ao notesWrapper
recipiente. O elemento de nota exibirá o título, conteúdo e data com base na entrada do usuário. Também terá um botão editar, salvar e excluir para realizar as funções correspondentes com base no id exclusivo da nota.
1 |
const createNote = (uid, title, text, date) => { |
2 |
const note = document.createElement("div"); |
3 |
note.className = "note"; |
4 |
note.id = uid; |
5 |
note.innerHTML = ` |
6 |
|
7 |
|
8 |
uid})"> |
9 |
Edit
|
10 |
|
11 |
uid})"> |
12 |
Save
|
13 |
|
14 |
uid})"> |
15 |
Delete
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
`; |
21 |
|
22 |
notesWrapper.insertBefore(note, notesWrapper.firstChild); |
23 |
};
|
Nesta função, usamos o .insertBefore()
método para garantir que a nota mais recente seja colocada na parte superior do contêiner notesWrapper.
Redefinir o título e o conteúdo da próxima nota
Finalmente, podemos atualizar nosso addNote()
função para criar uma nova nota e também redefinir os elementos de título, conteúdo e erro quando o botão é clicado.
1 |
const addNote = () => { |
2 |
if (title.value.trim().length == 0 && content.value.trim().length == 0) { |
3 |
error.innerText = "Note cannot be empty"; |
4 |
return; |
5 |
}
|
6 |
|
7 |
const noteObj = { |
8 |
uid: new Date().getTime().toString(), |
9 |
title: title.value, |
10 |
text: content.value, |
11 |
date: new Date().toLocaleDateString() |
12 |
};
|
13 |
|
14 |
createNote(noteObj.uid, noteObj.title, noteObj.text, noteObj.date); |
15 |
|
16 |
error.innerText = ""; |
17 |
content.value = ""; |
18 |
title.value = ""; |
19 |
};
|
Verifique os dados existentes
Como estamos usando localStorage, também podemos incluir uma condição para verificar se já existem dados para nossas anotações em localStorage e exibi-los na página assim que ela for carregada. o JSON.parse()
O método é usado para converter nossos dados stringificados de volta ao seu formato original.
1 |
window.addEventListener("load", () => { |
2 |
notesData = localStorage.getItem("notes") |
3 |
? JSON.parse(localStorage.getItem("notes")) |
4 |
: []; |
5 |
|
6 |
notesData.forEach((note) => { |
7 |
createNote(note.uid, note.title, note.text, note.date); |
8 |
});
|
9 |
});
|
4. Atualizando dados
Neste ponto, lidamos com as partes “C” e “R” do CRUD, conseguindo criar notas com sucesso com base na entrada do usuário e exibir as notas existentes. Agora vamos voltar nossa atenção para “U” definindo uma função que nos permite editar e salvar (atualizar) uma nota existente.
Editando..
quando nós o definirmos createNote()
função, incluímos dois elementos de botão para lidar com a edição e salvamento de uma nota com base em um ID exclusivo, então agora podemos criar o editNote()
e saveNote()
funções. Quando o botão de edição for clicado, ocultaremos o botão de edição e exibiremos o botão de salvar:
1 |
const editNote = (uid) => { |
2 |
const note = document.getElementById(uid); |
3 |
|
4 |
const noteTitle = note.querySelector(".note-title"); |
5 |
const noteText = note.querySelector(".note-text"); |
6 |
const noteSave = note.querySelector(".note-save"); |
7 |
const noteEdit = note.querySelector(".note-edit"); |
8 |
|
9 |
noteTitle.contentEditable = "true"; |
10 |
noteText.contentEditable = "true"; |
11 |
noteEdit.style.display = "none"; |
12 |
noteSave.style.display = "block"; |
13 |
noteText.focus(); |
14 |
};
|
Nesta função, usamos o uid
para encontrar o elemento note no DOM. Em seguida, direcionamos o título e os elementos de texto dentro da nota de destino e usamos o contentEditable
para nos permitir fazer alterações no conteúdo. contentEditable
é um atributo embutido do navegador que permite que um usuário altere o conteúdo de qualquer elemento se definido como verdadeiro.
Podemos atualizar nosso CSS para incluir estilo no elemento de nota durante a edição.
1 |
.note > *[contenteditable="true"] { |
2 |
color: #5f5f5f; |
3 |
width: 100%; |
4 |
outline: none; |
5 |
}
|
..e salvando novamente
Para nós saveNote()
função, precisaremos atualizar nosso valor notesData e dados localStorage. Podemos fazer isso usando o .forEach()
método para encontrar a nota que possui o uid correspondente e atualizar o conteúdo. Em seguida, enviaremos nosso array atualizado para localStorage para substituir o valor antigo.
1 |
notesData.forEach((note) => { |
2 |
if (note.uid == uid) { |
3 |
note.title = noteTitle.innerText; |
4 |
note.text = noteText.innerText; |
5 |
}
|
6 |
});
|
7 |
|
8 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
Em seguida, usaremos a mesma lógica do editNote()
função, só que desta vez estaremos definindo os atributos contentEditable como false e ocultando o botão salvar enquanto exibimos o botão editar. Também usaremos a mesma condição que usamos em nosso addNote()
função para garantir que os usuários não possam salvar uma nota em branco.
1 |
const saveNote = (uid) => { |
2 |
const note = document.getElementById(uid); |
3 |
|
4 |
const noteTitle = note.querySelector(".note-title"); |
5 |
const noteText = note.querySelector(".note-text"); |
6 |
const noteSave = note.querySelector(".note-save"); |
7 |
const noteEdit = note.querySelector(".note-edit"); |
8 |
|
9 |
if ( |
10 |
noteTitle.innerText.trim().length == 0 && |
11 |
noteText.value.trim().length == 0 |
12 |
) { |
13 |
error.innerHTML = "Note cannot be empty"; |
14 |
return; |
15 |
}
|
16 |
|
17 |
notesData.forEach((note) => { |
18 |
if (note.uid == uid) { |
19 |
note.title = noteTitle.innerText; |
20 |
note.text = noteText.innerText; |
21 |
}
|
22 |
});
|
23 |
|
24 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
25 |
|
26 |
noteTitle.contentEditable = "false"; |
27 |
noteText.contentEditable = "false"; |
28 |
noteEdit.style.display = "block"; |
29 |
noteSave.style.display = "none"; |
30 |
error.innerText = ""; |
31 |
};
|
5. Excluindo dados
E por último, a parte “D” do CRUD.
Para a implementação final de nosso aplicativo, trataremos da exclusão de dados. Para esta função, removeremos o elemento note do DOM e também deletaremos o objeto note de nosso notesData
variedade. Podemos lidar com a remoção do objeto da matriz usando o método .filter()
método.
1 |
const deleteNote = (uid) => { |
2 |
let confirmDelete = confirm("Are you sure you want to delete this note?"); |
3 |
if (!confirmDelete) { |
4 |
return; |
5 |
}
|
6 |
|
7 |
const note = document.getElementById(uid); |
8 |
note.parentNode.removeChild(note); |
9 |
|
10 |
notesData = notesData.filter((note) => { |
11 |
return note.uid != uid; |
12 |
});
|
13 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
14 |
};
|
Um dos benefícios de construir um aplicativo da Web é que podemos aproveitar os recursos existentes do navegador. Essa função usa o método confirm() para exibir um modal embutido que pode manipular a confirmação da entrada do usuário, sem a necessidade de criar um modal personalizado e detectar uma resposta por conta própria.
Conclusão
E é isso! O que acabamos de codificar era complexo o suficiente para ser desafiador, mas simples o suficiente para entender e a demonstração perfeita do CRUD. Implementamos as operações básicas de um aplicativo de software usando JavaScript vanilla (bem feito).