On this tutorial, we’ll be constructing a CRUD utility within the type of a observe taking app. We’ll additionally discover how you can use native storage to avoid wasting notes in our browser.
What’s CRUD?
Chances are you’ll already be questioning: “what does CRUD imply?!” A CRUD utility is the commonest type of any software program utility. CRUD stands for Create, Learn, Replace and Delete and it applies to any software program that’s able to letting customers create and examine information on its interface, in addition to make modifications to, and delete current information.
CRUD purposes can vary from a easy to-do checklist utility to a extra advanced social media platform. It’s additionally frequent for builders to construct CRUD purposes to show their grasp of a selected framework or language.
1. Creating the Markup
For our markup, we’ll have two foremost parts: the new-note
container that comprises the enter and textual content space subject for coming into the content material for our notes, and the notes-wrapper
container that’ll include the created notes. We even have a form-error
div for displaying error messages and a button that calls the addNote()
perform we’ll outline later.
1 |
<foremost>
|
2 |
<div class="new-note"> |
3 |
<enter kind="textual content" identify="title" id="title" placeholder="Title"> |
4 |
<textarea identify="content material" id="content material" placeholder="Begin writing"></textarea> |
5 |
<div id="form-error"></div> |
6 |
</div>
|
7 |
<div class="button-container"> |
8 |
<button class="add-btn" onclick="addNote()">Add observe</button> |
9 |
</div>
|
10 |
<div id="notes-wrapper"></div> |
11 |
</foremost>
|
2. Styling the Structure
That is the CSS for our preliminary format:
1 |
.new-note { |
2 |
margin-bottom: 32px; |
3 |
background-color: white; |
4 |
border: 1px strong #dadada; |
5 |
border-radius: 6px; |
6 |
padding: 16px; |
7 |
place: relative; |
8 |
}
|
9 |
|
10 |
.new-note enter { |
11 |
width: 100%; |
12 |
background: clear; |
13 |
border: none; |
14 |
define: none; |
15 |
font-size: 1.5rem; |
16 |
font-weight: 700; |
17 |
padding: 0 0 12px; |
18 |
}
|
19 |
|
20 |
.new-note textarea { |
21 |
show: block; |
22 |
width: 100%; |
23 |
min-height: 100px; |
24 |
border: none; |
25 |
resize: none; |
26 |
background: clear; |
27 |
define: none; |
28 |
padding: 0; |
29 |
}
|
30 |
|
31 |
#form-error { |
32 |
colour: #b33030; |
33 |
place: absolute; |
34 |
backside: -32px; |
35 |
left: 0; |
36 |
}
|
37 |
|
38 |
.button-container { |
39 |
show: flex; |
40 |
justify-content: middle; |
41 |
margin-bottom: 32px; |
42 |
}
|
43 |
|
44 |
.add-btn { |
45 |
border: 1px strong rgb(95, 95, 95); |
46 |
background-color: clear; |
47 |
transition: background-color 250ms, colour 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 |
colour: white; |
56 |
}
|
57 |
|
58 |
#notes-wrapper { |
59 |
show: flex; |
60 |
flex-direction: column; |
61 |
hole: 32px; |
62 |
}
|
63 |
|
64 |
.observe { |
65 |
place: relative; |
66 |
overflow: hidden; |
67 |
background: white; |
68 |
border: 1px strong #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 |
show: 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: proper; |
101 |
border-top: 1px strong #dadada; |
102 |
padding-top: 16px; |
103 |
width: 100%; |
104 |
margin-top: auto; |
105 |
}
|
106 |
|
107 |
.note-controls { |
108 |
place: absolute; |
109 |
proper: 0; |
110 |
prime: 0; |
111 |
background-color: white; |
112 |
font-size: 0.75rem; |
113 |
column-gap: 8px; |
114 |
padding: 8px; |
115 |
show: flex; |
116 |
opacity: 0; |
117 |
transition: opacity 350ms; |
118 |
}
|
119 |
|
120 |
.observe: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: clear; |
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 |
colour: #bb0000; |
139 |
}
|
140 |
|
141 |
.note-edit { |
142 |
colour: #00bb00; |
143 |
}
|
144 |
|
145 |
.note-save { |
146 |
colour: #0000bb; |
147 |
}
|
148 |
|
149 |
.note-save[disabled="true"] { |
150 |
colour: #dfdfdf; |
151 |
pointer-events: none; |
152 |
user-select: none; |
153 |
cursor: not-allowed; |
154 |
}
|
On this demo, we’ve eliminated the define styling on our enter
and textarea
fields to keep away from interfering with the styling. Nevertheless, if you want, you possibly can depart the define styling for accessibility functions.
That is what our format seems to be like at the moment:
3. Creating and Studying Information
Now we are able to begin engaged on the logic of our utility!
International Parts
First, let’s get all the worldwide parts we’ll want.
1 |
const notesWrapper = doc.getElementById("notes-wrapper"); |
2 |
const title = doc.getElementById("title"); |
3 |
const content material = doc.getElementById("content material"); |
4 |
const error = doc.getElementById("form-error"); |
Then we’ll outline a worldwide variable to retailer our notes information.
Creating and Storing Information
As a way to create the info, we’ll outline an addNote()
perform to be known as when the button is clicked. That is what’s going to deal with taking the info from the enter subject and placing them into a brand new observe component in a readable format.
To start with, we’ll embody a situation to verify if there’s any content material to be added in a brand new observe. If each the title and content material subject are empty, we’ll show an error message.
1 |
const addNote = () => { |
2 |
if (title.worth.trim().size == 0 && content material.worth.trim().size == 0) { |
3 |
error.innerText = "Observe can't be empty"; |
4 |
return; |
5 |
}
|
6 |
};
|
Subsequent, we create an object to include the date for a brand new observe. On this tutorial, we’ll be producing a novel id (uid) worth for every observe utilizing the date.getTime()
methodology. This returns the precise millisecond the observe was created and is used to ensure that no two notes have the identical id.
We’ll even be together with the date worth of when the observe was created utilizing the date.toLocaleDateString()
methodology. That is what our up to date perform seems to be like:
1 |
const noteObj = { |
2 |
uid: new Date().getTime().toString(), |
3 |
title: title.worth, |
4 |
textual content: content material.worth, |
5 |
date: new Date().toLocaleDateString() |
6 |
};
|
Now that now we have the observe object, we are able to retailer that information within the notesData array and in addition within the browser localStorage.
1 |
notesData.push(noteObj); |
2 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
Displaying Information
Let’s outline a perform createNote()
to deal with appending the brand new observe to the notesWrapper
container. The observe component will show the title, content material and date based mostly on consumer enter. It should even have an edit, save and delete button for finishing up the corresponding capabilities based mostly on the observe distinctive id.
1 |
const createNote = (uid, title, textual content, date) => { |
2 |
const observe = doc.createElement("div"); |
3 |
observe.className = "observe"; |
4 |
observe.id = uid; |
5 |
observe.innerHTML = ` |
6 |
<div class="note-title">${title}</div> |
7 |
<div class="note-controls">
|
8 |
<button class="note-edit" onclick="editNote(${uid})"> |
9 |
Edit
|
10 |
</button>
|
11 |
<button class="note-save" fashion="show:none" onclick="saveNote(${uid})"> |
12 |
Save
|
13 |
</button>
|
14 |
<button class="note-delete" onclick="deleteNote(${uid})"> |
15 |
Delete
|
16 |
</button>
|
17 |
</div>
|
18 |
<div class="note-text">${textual content}</div> |
19 |
<div class="note-date">${date}</div> |
20 |
`; |
21 |
|
22 |
notesWrapper.insertBefore(observe, notesWrapper.firstChild); |
23 |
};
|
On this perform, we use the .insertBefore()
methodology to ensure that the latest observe in positioned on the prime of the notesWrapper container.
Reset the Title and Content material for Subsequent Observe
Lastly, we are able to replace our addNote()
perform to create a brand new observe and in addition reset the title, content material and error parts when the button is clicked.
1 |
const addNote = () => { |
2 |
if (title.worth.trim().size == 0 && content material.worth.trim().size == 0) { |
3 |
error.innerText = "Observe can't be empty"; |
4 |
return; |
5 |
}
|
6 |
|
7 |
const noteObj = { |
8 |
uid: new Date().getTime().toString(), |
9 |
title: title.worth, |
10 |
textual content: content material.worth, |
11 |
date: new Date().toLocaleDateString() |
12 |
};
|
13 |
|
14 |
createNote(noteObj.uid, noteObj.title, noteObj.textual content, noteObj.date); |
15 |
|
16 |
error.innerText = ""; |
17 |
content material.worth = ""; |
18 |
title.worth = ""; |
19 |
};
|
Test for Present Information
Since we’re utilizing localStorage, we are able to additionally embody a situation to verify if there’s already current information for our notes in localStorage and show that on the web page as soon as the web page masses. The JSON.parse()
methodology is used to transform our stringified information again to its authentic format.
1 |
window.addEventListener("load", () => { |
2 |
notesData = localStorage.getItem("notes") |
3 |
? JSON.parse(localStorage.getItem("notes")) |
4 |
: []; |
5 |
|
6 |
notesData.forEach((observe) => { |
7 |
createNote(observe.uid, observe.title, observe.textual content, observe.date); |
8 |
});
|
9 |
});
|
4. Updating Information
At this level, we’ve dealt with the “C” and “R” elements of CRUD, having managed to efficiently create notes based mostly on consumer enter, and show current notes. Now let’s flip our consideration to “U” by defining a perform that lets us edit and save (replace) an current observe.
Modifying..
Once we outlined out createNote()
perform, we included two button parts to deal with modifying and saving a observe based mostly on a novel id so now we are able to create the editNote()
and saveNote()
capabilities. When the edit button is clicked, we’ll cover the edit button and show the save button:
1 |
const editNote = (uid) => { |
2 |
const observe = doc.getElementById(uid); |
3 |
|
4 |
const noteTitle = observe.querySelector(".note-title"); |
5 |
const noteText = observe.querySelector(".note-text"); |
6 |
const noteSave = observe.querySelector(".note-save"); |
7 |
const noteEdit = observe.querySelector(".note-edit"); |
8 |
|
9 |
noteTitle.contentEditable = "true"; |
10 |
noteText.contentEditable = "true"; |
11 |
noteEdit.fashion.show = "none"; |
12 |
noteSave.fashion.show = "block"; |
13 |
noteText.focus(); |
14 |
};
|
On this perform, we use the uid
to search out the observe component within the DOM. Then we goal the title and textual content parts contained in the goal observe and use the contentEditable
methodology to permit us make modifications to the content material. contentEditable
is an inbuilt browser attribute that enables a consumer change the content material of any component if set to true.
We are able to replace our CSS to incorporate styling on the observe component when modifying.
1 |
.observe > *[contenteditable="true"] { |
2 |
colour: #5f5f5f; |
3 |
width: 100%; |
4 |
define: none; |
5 |
}
|
..and Resaving
For our saveNote()
perform, we’ll have to replace our notesData worth and localStorage information. We are able to do that utilizing the .forEach()
methodology to search out the observe that has the corresponding uid and replace the content material. Then we’ll push our up to date array into localStorage to interchange the previous worth.
1 |
notesData.forEach((observe) => { |
2 |
if (observe.uid == uid) { |
3 |
observe.title = noteTitle.innerText; |
4 |
observe.textual content = noteText.innerText; |
5 |
}
|
6 |
});
|
7 |
|
8 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
Then we’ll use the identical logic because the editNote()
perform, solely this time we’ll be setting the contentEditable attributes to false and hiding the save button whereas displaying the edit button. We’ll additionally use the identical situation we utilized in our addNote()
perform to ensure customers can’t save a clean observe.
1 |
const saveNote = (uid) => { |
2 |
const observe = doc.getElementById(uid); |
3 |
|
4 |
const noteTitle = observe.querySelector(".note-title"); |
5 |
const noteText = observe.querySelector(".note-text"); |
6 |
const noteSave = observe.querySelector(".note-save"); |
7 |
const noteEdit = observe.querySelector(".note-edit"); |
8 |
|
9 |
if ( |
10 |
noteTitle.innerText.trim().size == 0 && |
11 |
noteText.worth.trim().size == 0 |
12 |
) { |
13 |
error.innerHTML = "Observe can't be empty"; |
14 |
return; |
15 |
}
|
16 |
|
17 |
notesData.forEach((observe) => { |
18 |
if (observe.uid == uid) { |
19 |
observe.title = noteTitle.innerText; |
20 |
observe.textual content = noteText.innerText; |
21 |
}
|
22 |
});
|
23 |
|
24 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
25 |
|
26 |
noteTitle.contentEditable = "false"; |
27 |
noteText.contentEditable = "false"; |
28 |
noteEdit.fashion.show = "block"; |
29 |
noteSave.fashion.show = "none"; |
30 |
error.innerText = ""; |
31 |
};
|
5. Deleting Information
And lastly, the “D” a part of CRUD.
For the ultimate implementation of our utility, we’ll be dealing with deleting information. For this perform, we’ll take away the observe component from the DOM and in addition delete the observe object from our notesData
array. We are able to deal with eradicating the article from the array by utilizing the .filter()
methodology.
1 |
const deleteNote = (uid) => { |
2 |
let confirmDelete = affirm("Are you positive you need to delete this observe?"); |
3 |
if (!confirmDelete) { |
4 |
return; |
5 |
}
|
6 |
|
7 |
const observe = doc.getElementById(uid); |
8 |
observe.parentNode.removeChild(observe); |
9 |
|
10 |
notesData = notesData.filter((observe) => { |
11 |
return observe.uid != uid; |
12 |
});
|
13 |
localStorage.setItem("notes", JSON.stringify(notesData)); |
14 |
};
|
One of many advantages of constructing an online utility is that we are able to benefit from current browser options. This perform makes use of the affirm() methodology to show an inbuilt modal that may deal with confirming consumer enter, with out the necessity to constructed a customized modal and detect a response ourselves.
Conclusion
And that’s that! What we’ve simply coded was advanced sufficient to be difficult, but easy sufficient to grasp, and the proper demonstration of CRUD. We’ve carried out the fundamental operations of a software program utility utilizing vanilla JavaScript (properly carried out).