Como construir um aplicativo da Web CRUD com Vanilla JavaScript

Neste tutorial, criaremos um aplicativo CRUD na forma de um aplicativo de anotações. Também exploraremos como usar o armazenamento local para salvar anotações em nosso navegador.

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
   class="new-note">
3
     type="text" name="title" id="title" placeholder="Title">
4
     name="content" id="content" placeholder="Start writing">
5
     id="form-error">
6
  
7
   class="button-container">
8
     class="add-btn" onclick="addNote()">Add note
9
  
10
   id="notes-wrapper">
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:

nossa demonstração do aplicativo de anotaçõesnossa demonstração do aplicativo de anotaçõesnossa demonstração do aplicativo de anotações

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));

localStorage suporta apenas dados em formato de string, então usamos o JSON.stringify() método para converter nossa matriz em um formato adequado.

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
    
${title}
7
    
8
      
9
        Edit

10
      

11
      
12
        Save

13
      

14
      
15
        Delete

16
      

17
    

18
    
${text}
19
    
${date}
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).

Deixe uma resposta