liewchooichin commited on
Commit
8196597
1 Parent(s): 80d53d3

simple notes app

Browse files
simple-notes/index.html ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>IndexedDB demo</title>
7
+ <link href="../favicon.svg" rel="icon">
8
+ <link href="./style.css" rel="stylesheet">
9
+ <script src="./index.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <header>
13
+ <h1>Simple Notes</h1>
14
+ </header>
15
+
16
+ <main>
17
+ <section class="note-display">
18
+ <h2>Notes</h2>
19
+ <ul>
20
+
21
+ </ul>
22
+ </section>
23
+ <section class="new-note">
24
+ <h2>Enter a new note</h2>
25
+ <form>
26
+ <div>
27
+ <label for="title">Title</label>
28
+ <input id="title" type="text" required placeholder="Breathe">
29
+ </div>
30
+ <div>
31
+ <label for="date">Date</label>
32
+ <input id="date" type="text" required placeholder="Everyday">
33
+ </div>
34
+ <div>
35
+ <label for="content">Note</label>
36
+ <input id="content" type="text" required placeholder="Take 3 calm breathe everyday.">
37
+ </div>
38
+ <div>
39
+ <button id="newBtn">Create a new note</button>
40
+ </div>
41
+ </form>
42
+ </section>
43
+ </main>
44
+
45
+ <footer>
46
+ <p>
47
+ Trying to use IndexedDB to create mini database that is stored at the
48
+ client side.
49
+ </p>
50
+ <p>Content sourced from <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage#storing_complex_data_%E2%80%94_indexeddb">MDN</a>.
51
+ </p>
52
+ <p>Simple Notes by Chooi Chin Liew is marked with CC0 1.0 license.</p>
53
+ </footer>
54
+ </body>
55
+ </html>
simple-notes/index.js ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Create needed constants
2
+ const list = document.querySelector('ul');
3
+ const titleInput = document.querySelector('#title');
4
+ const dateInput = document.querySelector('#date');
5
+ const contentInput = document.querySelector('#content');
6
+ const form = document.querySelector('form');
7
+ const newBtn = document.querySelector('#newBtn');
8
+ // Create an instance of a db object for us to store the open database in
9
+ let db;
10
+ // Open our database; it is created if it doesn't already exist
11
+ // (see the upgradeneeded handler below)
12
+ const openRequest = window.indexedDB.open("notes_db", 1);
13
+ // error handler signifies that the database didn't open successfully
14
+ openRequest.addEventListener("error", () => console.error("Database failed to open"));
15
+ // Create a submit event handler so that when the form is submitted the addData() function is run
16
+ form.addEventListener("submit", addData);
17
+ // success handler signifies that the database opened successfully
18
+ openRequest.addEventListener("success", () => {
19
+ console.log("Database opened successfully");
20
+ // Store the opened database object in the db variable.
21
+ // This is used a lot below.
22
+ db = openRequest.result;
23
+ // Run the displayData() function to display the notes already in the IDB
24
+ displayData();
25
+ });
26
+ // Finally for this section, we'll add probably the most important event
27
+ // handler for setting up the database: upgradeneeded. This handler runs if the
28
+ // database has not already been set up, or if the database is opened with a
29
+ // bigger version number than the existing stored database (when performing an
30
+ // upgrade). Add the following code, below your previous handler:
31
+ // Set up the database tables if this has not already been done.
32
+ // Here we first grab a reference to the existing database from the result
33
+ // property of the event's target (e.target.result), which is the request
34
+ // object. This is equivalent to the line db = openRequest.result; inside the
35
+ // success event handler, but we need to do this separately here because the
36
+ // upgradeneeded event handler (if needed) will run before the success event
37
+ // handler, meaning that the db value wouldn't be available if we didn't do
38
+ // this.
39
+ openRequest.addEventListener("upgradeneeded", (e) => {
40
+ // Grab a reference to the opened database
41
+ db = e.target.result;
42
+ // Create an objectStore in our database to store notes and an
43
+ // auto-incrementing key
44
+ // An objectStore is similar to a 'table' in a relational database
45
+ const objectStore = db.createObjectStore("notes_os", {
46
+ keyPath: "id",
47
+ autoIncrement: true,
48
+ });
49
+ // Define what data items the objectStore will contain
50
+ objectStore.createIndex("title", "title", { unique: false });
51
+ objectStore.createIndex("content", "content", { unique: false });
52
+ objectStore.createIndex("date", "date", { unique: false });
53
+ // Print the outcome of the setup
54
+ console.log("Database setup completed.");
55
+ });
56
+ // Define the addData() function
57
+ function addData(e) {
58
+ // prevent default - we don't want the form to submit in the conventional way
59
+ e.preventDefault();
60
+ // grab the values entered into the form fields and store them in an object ready for being inserted into the DB
61
+ const newItem = {
62
+ title: titleInput.value,
63
+ content: contentInput.value,
64
+ date: dateInput.value
65
+ };
66
+ // open a read/write db transaction, ready for adding the data,
67
+ // name of the db: notes_os
68
+ // IDBDatabase.transaction()
69
+ const transaction = db.transaction(["notes_os"], "readwrite");
70
+ // call an object store that's already been added to the database
71
+ // IDBTransaction.objectStore()
72
+ const objectStore = transaction.objectStore("notes_os");
73
+ // Make a request to add our newItem object to the object store
74
+ // IDBObjectStore.add()
75
+ const addRequest = objectStore.add(newItem);
76
+ // If the transaction is successful
77
+ addRequest.addEventListener("success", () => {
78
+ // Clear the form, ready for adding the next entry
79
+ titleInput.value = "";
80
+ contentInput.value = "";
81
+ dateInput.value = "";
82
+ });
83
+ // Report on the success of the transaction completing, when everything is done
84
+ transaction.addEventListener("complete", () => {
85
+ console.log("Transaction completed: database modification finished.");
86
+ // update the display of data to show the newly added item, by running displayData() again.
87
+ displayData();
88
+ });
89
+ // If there is error in the transaction
90
+ transaction.addEventListener("error", () => console.log("Transaction not opened due to error"));
91
+ }
92
+ // Define the displayData() function
93
+ function displayData() {
94
+ // Here we empty the contents of the list element each time the display is updated
95
+ // If you didn't do this, you'd get duplicates listed each time a new note is added
96
+ while (list.firstChild) {
97
+ list.removeChild(list.firstChild);
98
+ }
99
+ // Open our object store and then get a cursor - which iterates through all the
100
+ // different data items in the store
101
+ const transaction = db.transaction("notes_os");
102
+ const objectStore = transaction.objectStore("notes_os");
103
+ // IDBObjectStore: openCursor(). Returns an IDBRequest object, and, in a
104
+ // separate thread, returns a new IDBCursorWithValue object. Used for
105
+ // iterating through an object store with a cursor.
106
+ objectStore.openCursor().addEventListener("success", (e) => {
107
+ // Get a reference to the cursor
108
+ const cursor = e.target.result;
109
+ // If there is still another data item to iterate through, keep running this code,
110
+ // In short:
111
+ // if (cursor) {
112
+ // cursor.value contains the current record being iterated through
113
+ // this is where you'd do something with the result
114
+ // cursor.continue();
115
+ //}
116
+ // else {
117
+ // no more results
118
+ //}
119
+ if (cursor) {
120
+ // Create a list item, h3, and p to put each data item inside when displaying it
121
+ // structure the HTML fragment, and append it inside the list
122
+ const listItem = document.createElement("li");
123
+ const h3 = document.createElement("h3");
124
+ const para = document.createElement("p");
125
+ listItem.appendChild(h3);
126
+ listItem.appendChild(para);
127
+ list.appendChild(listItem);
128
+ // Put the data from the cursor inside the h3 and para
129
+ h3.textContent = cursor.value.title;
130
+ para.textContent = `${cursor.value.date}: ${cursor.value.content}`;
131
+ // Store the ID of the data item inside an attribute on the listItem, so we know
132
+ // which item it corresponds to. This will be useful later when we want to delete items
133
+ listItem.setAttribute("data-note-id", cursor.value.id);
134
+ // Create a button and place it inside each listItem
135
+ const deleteBtn = document.createElement("button");
136
+ listItem.appendChild(deleteBtn);
137
+ deleteBtn.textContent = "Delete";
138
+ // Set an event handler so that when the button is clicked, the deleteItem()
139
+ // function is run
140
+ deleteBtn.addEventListener("click", deleteItem);
141
+ // Iterate to the next item in the cursor
142
+ cursor.continue();
143
+ }
144
+ else {
145
+ // Again, if list item is empty, display a 'No notes stored' message
146
+ if (!list.firstChild) {
147
+ const listItem = document.createElement("li");
148
+ listItem.textContent = "No notes stored.";
149
+ list.appendChild(listItem);
150
+ }
151
+ // if there are no more cursor items to iterate through, say so
152
+ console.log("Notes all displayed");
153
+ }
154
+ });
155
+ }
156
+ // Define the deleteItem() function
157
+ function deleteItem(e) {
158
+ // retrieve the name of the task we want to delete. We need
159
+ // to convert it to a number before trying to use it with IDB; IDB key
160
+ // values are type-sensitive.
161
+ const ulNode = e.target.parentNode;
162
+ // const noteId = Number(e.target.parentNode.getAttribute("data-note-id"));
163
+ // We do however need to pass the attribute through the global built-in
164
+ // Number() object as it is of datatype string, and therefore wouldn't be
165
+ // recognized by the database, which expects a number.
166
+ const noteId = Number(ulNode.getAttribute("data-note-id"));
167
+ // open a database transaction and delete the task, finding it using the id we retrieved above
168
+ const transaction = db.transaction(["notes_os"], "readwrite");
169
+ const objectStore = transaction.objectStore("notes_os");
170
+ const deleteRequest = objectStore.delete(noteId);
171
+ // report that the data item has been deleted
172
+ transaction.addEventListener("complete", () => {
173
+ // delete the parent of the button
174
+ // which is the list item, so it is no longer displayed
175
+ //e.target.parentNode.parentNode.removeChild(e.target.parentNode);
176
+ ulNode.parentNode.removeChild(ulNode);
177
+ console.log(`Note ${noteId} deleted.`);
178
+ // Again, if list item is empty, display a 'No notes stored' message
179
+ if (!list.firstChild) {
180
+ const listItem = document.createElement("li");
181
+ listItem.textContent = "No notes stored.";
182
+ list.appendChild(listItem);
183
+ }
184
+ });
185
+ }
simple-notes/index.ts ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Create needed constants
2
+ const list = document.querySelector('ul') as HTMLUListElement;
3
+ const titleInput = document.querySelector('#title') as HTMLInputElement;
4
+ const dateInput = document.querySelector('#date') as HTMLInputElement;
5
+ const contentInput = document.querySelector('#content') as HTMLInputElement;
6
+ const form = document.querySelector('form') as HTMLFormElement;
7
+ const newBtn = document.querySelector('#newBtn') as HTMLButtonElement;
8
+
9
+ // Create an instance of a db object for us to store the open database in
10
+ let db : IDBDatabase;
11
+
12
+ // Open our database; it is created if it doesn't already exist
13
+ // (see the upgradeneeded handler below)
14
+ const openRequest = window.indexedDB.open("notes_db", 1) as IDBOpenDBRequest;
15
+
16
+ // error handler signifies that the database didn't open successfully
17
+ openRequest.addEventListener("error", () =>
18
+ console.error("Database failed to open"),
19
+ );
20
+ // Create a submit event handler so that when the form is submitted the addData() function is run
21
+ form.addEventListener("submit", addData);
22
+
23
+
24
+ // success handler signifies that the database opened successfully
25
+ openRequest.addEventListener("success", () => {
26
+ console.log("Database opened successfully");
27
+ // Store the opened database object in the db variable.
28
+ // This is used a lot below.
29
+ db = openRequest.result;
30
+ // Run the displayData() function to display the notes already in the IDB
31
+ displayData();
32
+ });
33
+
34
+ // Finally for this section, we'll add probably the most important event
35
+ // handler for setting up the database: upgradeneeded. This handler runs if the
36
+ // database has not already been set up, or if the database is opened with a
37
+ // bigger version number than the existing stored database (when performing an
38
+ // upgrade). Add the following code, below your previous handler:
39
+ // Set up the database tables if this has not already been done.
40
+ // Here we first grab a reference to the existing database from the result
41
+ // property of the event's target (e.target.result), which is the request
42
+ // object. This is equivalent to the line db = openRequest.result; inside the
43
+ // success event handler, but we need to do this separately here because the
44
+ // upgradeneeded event handler (if needed) will run before the success event
45
+ // handler, meaning that the db value wouldn't be available if we didn't do
46
+ // this.
47
+ openRequest.addEventListener("upgradeneeded", (e: Event) => {
48
+ // Grab a reference to the opened database
49
+ db = (e.target as IDBOpenDBRequest).result as IDBDatabase;
50
+
51
+ // Create an objectStore in our database to store notes and an
52
+ // auto-incrementing key
53
+ // An objectStore is similar to a 'table' in a relational database
54
+ const objectStore = db.createObjectStore("notes_os", {
55
+ keyPath: "id",
56
+ autoIncrement: true,
57
+ });
58
+
59
+ // Define what data items the objectStore will contain
60
+ objectStore.createIndex("title", "title", { unique: false });
61
+ objectStore.createIndex("content", "content", { unique: false });
62
+ objectStore.createIndex("date", "date", {unique: false});
63
+ // Print the outcome of the setup
64
+ console.log("Database setup completed.");
65
+ });
66
+
67
+ // Define the addData() function
68
+ function addData(e: Event) {
69
+ // prevent default - we don't want the form to submit in the conventional way
70
+ e.preventDefault();
71
+
72
+ // grab the values entered into the form fields and store them in an object ready for being inserted into the DB
73
+ const newItem = {
74
+ title: titleInput.value,
75
+ content: contentInput.value,
76
+ date: dateInput.value
77
+ };
78
+
79
+ // open a read/write db transaction, ready for adding the data,
80
+ // name of the db: notes_os
81
+ // IDBDatabase.transaction()
82
+ const transaction = db.transaction(["notes_os"], "readwrite");
83
+
84
+ // call an object store that's already been added to the database
85
+ // IDBTransaction.objectStore()
86
+ const objectStore = transaction.objectStore("notes_os");
87
+
88
+ // Make a request to add our newItem object to the object store
89
+ // IDBObjectStore.add()
90
+ const addRequest = objectStore.add(newItem);
91
+
92
+ // If the transaction is successful
93
+ addRequest.addEventListener("success", () => {
94
+ // Clear the form, ready for adding the next entry
95
+ titleInput.value = "";
96
+ contentInput.value = "";
97
+ dateInput.value = "";
98
+ });
99
+
100
+ // Report on the success of the transaction completing, when everything is done
101
+ transaction.addEventListener("complete", () => {
102
+ console.log("Transaction completed: database modification finished.");
103
+
104
+ // update the display of data to show the newly added item, by running displayData() again.
105
+ displayData();
106
+ });
107
+
108
+ // If there is error in the transaction
109
+ transaction.addEventListener("error", () =>
110
+ console.log("Transaction not opened due to error"),
111
+ );
112
+ }
113
+
114
+ // Define the displayData() function
115
+ function displayData() {
116
+ // Here we empty the contents of the list element each time the display is updated
117
+ // If you didn't do this, you'd get duplicates listed each time a new note is added
118
+ while (list.firstChild) {
119
+ list.removeChild(list.firstChild);
120
+ }
121
+
122
+ // Open our object store and then get a cursor - which iterates through all the
123
+ // different data items in the store
124
+ const transaction = db.transaction("notes_os");
125
+ const objectStore = transaction.objectStore("notes_os");
126
+ // IDBObjectStore: openCursor(). Returns an IDBRequest object, and, in a
127
+ // separate thread, returns a new IDBCursorWithValue object. Used for
128
+ // iterating through an object store with a cursor.
129
+ objectStore.openCursor().addEventListener("success", (e) => {
130
+ // Get a reference to the cursor
131
+ const cursor = (e.target as IDBRequest).result as IDBCursorWithValue;
132
+
133
+ // If there is still another data item to iterate through, keep running this code,
134
+ // In short:
135
+ // if (cursor) {
136
+ // cursor.value contains the current record being iterated through
137
+ // this is where you'd do something with the result
138
+ // cursor.continue();
139
+ //}
140
+ // else {
141
+ // no more results
142
+ //}
143
+ if (cursor) {
144
+ // Create a list item, h3, and p to put each data item inside when displaying it
145
+ // structure the HTML fragment, and append it inside the list
146
+ const listItem = document.createElement("li");
147
+ const h3 = document.createElement("h3");
148
+ const para = document.createElement("p");
149
+
150
+ listItem.appendChild(h3);
151
+ listItem.appendChild(para);
152
+ list.appendChild(listItem);
153
+
154
+ // Put the data from the cursor inside the h3 and para
155
+ h3.textContent = cursor.value.title;
156
+ para.textContent = `${cursor.value.date}: ${cursor.value.content}`;
157
+
158
+ // Store the ID of the data item inside an attribute on the listItem, so we know
159
+ // which item it corresponds to. This will be useful later when we want to delete items
160
+ listItem.setAttribute("data-note-id", cursor.value.id);
161
+
162
+ // Create a button and place it inside each listItem
163
+ const deleteBtn = document.createElement("button");
164
+ listItem.appendChild(deleteBtn);
165
+ deleteBtn.textContent = "Delete";
166
+
167
+ // Set an event handler so that when the button is clicked, the deleteItem()
168
+ // function is run
169
+ deleteBtn.addEventListener("click", deleteItem);
170
+
171
+ // Iterate to the next item in the cursor
172
+ cursor.continue();
173
+ } else {
174
+ // Again, if list item is empty, display a 'No notes stored' message
175
+ if (!list.firstChild) {
176
+ const listItem = document.createElement("li");
177
+ listItem.textContent = "No notes stored.";
178
+ list.appendChild(listItem);
179
+ }
180
+ // if there are no more cursor items to iterate through, say so
181
+ console.log("Notes all displayed");
182
+ }
183
+ });
184
+ }
185
+
186
+ // Define the deleteItem() function
187
+ function deleteItem(e: Event) {
188
+ // retrieve the name of the task we want to delete. We need
189
+ // to convert it to a number before trying to use it with IDB; IDB key
190
+ // values are type-sensitive.
191
+ const ulNode = (e.target as HTMLLIElement).parentNode as HTMLUListElement;
192
+ // const noteId = Number(e.target.parentNode.getAttribute("data-note-id"));
193
+ // We do however need to pass the attribute through the global built-in
194
+ // Number() object as it is of datatype string, and therefore wouldn't be
195
+ // recognized by the database, which expects a number.
196
+ const noteId = Number(ulNode.getAttribute("data-note-id"));
197
+
198
+ // open a database transaction and delete the task, finding it using the id we retrieved above
199
+ const transaction = db.transaction(["notes_os"], "readwrite");
200
+ const objectStore = transaction.objectStore("notes_os");
201
+ const deleteRequest = objectStore.delete(noteId);
202
+
203
+ // report that the data item has been deleted
204
+ transaction.addEventListener("complete", () => {
205
+ // delete the parent of the button
206
+ // which is the list item, so it is no longer displayed
207
+ //e.target.parentNode.parentNode.removeChild(e.target.parentNode);
208
+ ulNode.parentNode.removeChild(ulNode);
209
+ console.log(`Note ${noteId} deleted.`);
210
+
211
+ // Again, if list item is empty, display a 'No notes stored' message
212
+ if (!list.firstChild) {
213
+ const listItem = document.createElement("li");
214
+ listItem.textContent = "No notes stored.";
215
+ list.appendChild(listItem);
216
+ }
217
+ });
218
+ }
219
+
220
+
221
+
simple-notes/style.css ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /* Mobile first design */
3
+ html {
4
+ font-family: sans-serif;
5
+ }
6
+
7
+ body {
8
+ max-width: 100%;
9
+ }
10
+ header, footer {
11
+ background-color: green;
12
+ color: white;
13
+ padding: 0 20px;
14
+ }
15
+ header {
16
+ line-height: 80px;
17
+ }
18
+ footer {
19
+ font-size: 10pt;
20
+ }
21
+ form div {
22
+ display: grid;
23
+ grid-template-columns: 1fr 4fr;
24
+ grid-template-rows: repeat(3, auto);
25
+ justify-content: space-evenly;
26
+ }
27
+ /* long spacious button */
28
+ #newBtn {
29
+ grid-column: 2/5;
30
+ }
31
+ form div label {
32
+ text-align: right;
33
+ margin-right: 1em;
34
+ }
35
+ .new-note, .note-display {
36
+ padding: 20px;
37
+ }
38
+ .new-note {
39
+ background: lightgreen;
40
+ }
41
+
42
+ h1 {
43
+ margin: 0;
44
+ }
45
+
46
+ ul {
47
+ list-style-type: none;
48
+ }
49
+
50
+ div {
51
+ margin-bottom: 10px;
52
+ }
53
+
54
+ /* For wider display */
55
+ @media (min-width: 600px) {
56
+ body {
57
+ margin: 0 auto;
58
+ max-width: 800px;
59
+ }
60
+ }
simple-notes/tsconfig.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ }
6
+ }