-
Notifications
You must be signed in to change notification settings - Fork 0
/
indexedDB.js
556 lines (459 loc) · 18.8 KB
/
indexedDB.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
//Polyfills??
// window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
// window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
// window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange
// import { resolve } from 'path';
import { openDB, whiteCharHandler } from "./utility.js";
let elementEntrySubmit = document.querySelector("#value-submit"); //dynamic update
let elementEntryDone = document.querySelector("#value-done");
let elementEntryTodo = document.querySelector("#value-todo");
elementEntrySubmit.innerHTML = 0;
elementEntryDone.innerHTML = 0;
elementEntryTodo.innerHTML = 0;
let exclamations = document.querySelectorAll(
".content-2-input .input-section .fa-exclamation-circle"
); //all Exclamations
let warnings = document.querySelectorAll(
".content .content-2 .content-2-input .input-section .warning"
); //warning symbols
export let submitBtn = document.querySelector(".expense-submit");
const form = document.querySelector("form");
//3 input fields
export const merchantInput = document.querySelector("#input-merchant");
export const dateInput = document.querySelector("#input-date");
export const amountInput = document.querySelector("#input-amount");
//the ul we append detail li item to
const contentParent = document.querySelector(".indexedDb");
//Merchant Dropdown list
export let merchantsDropdown = document.querySelector(".merchantDropdownList");
let expenseTypeDropdown = document.querySelector(".expenseTypeDropdownList");
// uncomment to reset by deleting the database
// let requestDeleteAll = indexedDB.deleteDatabase('expense_db');
// requestDeleteAll.onsuccess = () => {
// console.log('deleted');
// };
// let requestDeleteAll_2 = indexedDB.deleteDatabase('expense_mt');
// requestDeleteAll_2.onsuccess = () => {
// console.log('deleted');
// };
let db;
window.onload = function () {
//create database if it does not exist by creating a "request object"
let request = window.indexedDB.open("expense_db", 1);
//onerror
request.onerror = () => console.log("failed to load main database");
//onsuccess
request.onsuccess = () => {
console.log("main database load complete");
//store opened database object
db = request.result; //has to let db in global as onupgradeneede runs before onsuccess
displayData();
};
//onupgradeneeded will run before onsuccess!!!!
//setup database tables
request.onupgradeneeded = e => {
//a reference to the opened database;
let db = e.target.result;
//create a table (called "object store"), the method is literally called createObjectStore
let objectStore = db.createObjectStore("expense_os", {
keyPath: "id",
autoIncrement: true,
});
//Define what data items (transactional data)
objectStore.createIndex("merchant", "merchant", { unique: false }); //merchant
objectStore.createIndex("date", "date", { unique: false }); //date
objectStore.createIndex("status", "status", { unique: false }); //status
objectStore.createIndex("amount", "amount", { unique: false }); //amount
//Metadata table
let objectStoreMeta = db.createObjectStore("expense_mt", {
keyPath: "id",
autoIncrement: true,
});
objectStoreMeta.createIndex("type", "type", { unique: true }); //expense type
console.log("2 Databases setup complete");
};
form.onsubmit = addData;
function addData(e) {
//prevent default, dont want form to be submit normally
e.preventDefault();
let dateInputInMs = new Date(dateInput.value).getTime();
let fields = [merchantInput.value, dateInput.value, amountInput.value];
/*
Data Validation
1. Merchant Descrition: a. Special Char, b. Trim blank, c. null/ undefined
2. Expense Date: a. null/undefined, b. has to before or equal today
3. Expense Amount: just check emptyness
*/
if (merchantInput.value && dateInput.value && amountInput.value) {
//all 3 fields are non empty, reset all warning and exclamation
fields.forEach((eachInput, index) => {
warnings[index].style.zIndex = -1;
exclamations[index].style.zIndex = -1;
});
} else {
//Empty field warning message
for (let i = 0; i < fields.length; i++) {
if (!fields[i]) {
//if input fields are empty
warnings[i].style.zIndex = 1;
exclamations[i].style.zIndex = 1;
}
}
if (dateInputInMs > Date.now()) {
warnings[1].style.zIndex = 1;
warnings[1].innerText = "Please don't enter future expense";
exclamations[1].style.zIndex = 1;
}
return;
}
//get info entered
let newInfo = {
merchant: whiteCharHandler(merchantInput.value),
date: dateInput.value,
status: "Uncategorized",
amount: amountInput.value,
};
//open a read/write db transaction, ready for adding the data;
let transaction = db.transaction(["expense_os"], "readwrite");
console.log(transaction, newInfo);
//call an object store in database Returns an IDBObjectStore in the transaction's scope.
let objectStore = transaction.objectStore("expense_os");
// //Make request to add new info to object store
let request = objectStore.add(newInfo);
request.onsuccess = () => {
//clear the form
merchantInput.value = "";
dateInput.value = "";
amountInput.value = "";
};
// //report result of transaction to add data
transaction.oncomplete = () => {
console.log("Transaction complete: added data");
displayData(); //update DOM with new data
};
transaction.onerror = () => {
console.log("transaction adding data failed");
};
}
function displayData() {
// // -- Metadata database
// /*
// need to open cursor to expense_mt (metadata database)
// because that is what the expense type dropdown list attached to each chevron will be based on
// */
let transactionMeta = db.transaction("expense_mt");
let objectStoreMeta = transactionMeta.objectStore("expense_mt");
let expenseListArr = [];
objectStoreMeta.openCursor().onsuccess = e => {
let cursor = e.target.result;
if (cursor) {
// console.log(cursor.value.type)
// let expenseItem = document.createElement('li');
// expenseItem.innerHTML = cursor.value.type
expenseListArr.push(cursor.value.type);
cursor.continue();
}
};
transactionMeta.oncomplete = () => {
// -- Metadata database end
//Returns an IDBObjectStore in the transaction's scope
let transaction = db.transaction("expense_os");
let objectStore = transaction.objectStore("expense_os");
let countRequest = objectStore.count();
countRequest.onsuccess = () => {
console.log("Count request successful");
elementEntrySubmit.innerText = countRequest.result; //update number in content-3 blue box value
};
//new experiment, try to query db to get all entries with 'uncategorized' for key: status
let uncategorizedExpCount = 0;
//merchant list dropdown??
let merchants = new Set();
//has to delete the first entry? no idea why
while (contentParent.firstChild) {
contentParent.removeChild(contentParent.firstChild);
}
objectStore.openCursor().onsuccess = e => {
//get a reference to the cursor
let cursor = e.target.result;
//once iterate all items, cursor will become null hence false and stop running this block
if (cursor) {
const li = document.createElement("li");
// const p1 = document.createElement('p'); //id
const p2 = document.createElement("p"); //merchant
const p3 = document.createElement("p"); //date
//-- drop down text
// const p4 = document.createElement('p'); //status
const p4 = document.createElement("ul"); //status
//-- drop down text
const p5 = document.createElement("p"); //amount
const p6 = document.createElement("i"); //delete
//<i class="far fa-trash-alt"></i>
// li.append(...[p1, p2, p3, p4, p5, p6]) //append all to li (backup)
li.append(...[p2, p3, p4, p5, p6]); //append all to li
contentParent.appendChild(li);
// p1.textContent = cursor.value.id;
p2.textContent = cursor.value.merchant;
p3.textContent = cursor.value.date;
//-- drop down text
// p4.textContent = cursor.value.status;
//p4.innerHTML = `<li><i class="fas fa-chevron-down"></i>${cursor.value.status}</li><ul class='expenseList'><li>Uncategorized</li><li>${cursor.value.status}</li></ul>`
p4.innerHTML = `<li class="needborder"><i class="fas fa-chevron-down"></i>${cursor.value.status}</li><ul class='expenseList'></ul>`;
//-- drop down text0
p5.textContent = cursor.value.amount;
p6.classList.add("far");
p6.classList.add("fa-trash-alt");
li.setAttribute("data-id", cursor.value.id); // the id of list item, to facilitate deletion
li.classList.add("content-5-header"); //add css class for formatting
//Delete the entry
p6.onclick = deleteData;
//increment uncategorized exp
// console.log(cursor.value.status);
if (cursor.value.status == "Uncategorized") {
uncategorizedExpCount++;
}
//Merchant dropdown options, a Set
merchants.add(cursor.value.merchant);
//continue for next entry
cursor.continue();
} else {
//iterator all cursor item completes
//update uncategorized expense count in orange box
elementEntryTodo.innerText = uncategorizedExpCount;
elementEntryDone.innerText =
elementEntrySubmit.innerText - uncategorizedExpCount;
//rebuilding dropdown list, clear previous data always
merchantsDropdown.innerText = "";
//iterate cursor completes: update merchantsDropDownList
merchants = new Array(...merchants);
merchants.forEach(merchant => {
let merchantDiv = document.createElement("div");
merchantDiv.classList.add("merchant");
merchantDiv.innerHTML = `<h4>${merchant}</h4>`;
merchantsDropdown.append(merchantDiv);
//add click listener to fill in input when clicked
merchantDiv.addEventListener("click", event => {
merchantInput.value = event.target.innerText;
});
// merchantDivClone.addEventListener('click', (event) => {
// expenseTypeDropdown.value = event.target.innerText
// })
});
}
};
//after openCursor transaction on transaction db
transaction.oncomplete = () => {
let expenseLists = document.querySelectorAll(".expenseList");
let chevrons = document.querySelectorAll(
"ul li i[class='fas fa-chevron-down']"
);
//expense type (Expense Category) drop down List
expenseTypeDropdown.innerHTML = "";
expenseListArr.forEach(expenseType => {
let expenseListItem = document.createElement("div");
expenseListItem.innerText = expenseType;
expenseTypeDropdown.append(expenseListItem);
});
//Expense Category Contains Uncategorized
let uncategorizedDiv = document.createElement("div");
uncategorizedDiv.innerText = "Uncategorized";
expenseTypeDropdown.append(uncategorizedDiv);
//add expense drop down list to each category
expenseLists.forEach(expenseList => {
expenseListArr.forEach(expenseType => {
let expenseListItem = document.createElement("li");
expenseListItem.innerText = expenseType;
expenseList.append(expenseListItem);
});
});
// console.log(expenseLists)
chevrons.forEach((chevron, index) => {
chevron.addEventListener("click", () => {
expenseLists.forEach(expenseList => {
// expenseList.style.display = 'none';
expenseList.style.zIndex = -1;
expenseList.style.opacity = "0";
expenseList.style.pointerEvents = "none";
});
// expenseLists[index].style.display = 'block'
expenseLists[index].style.zIndex = 1;
expenseLists[index].style.opacity = "1";
expenseLists[index].style.pointerEvents = "all";
chevrons[index].parentElement.style.borderColor = "#4aae9b";
expenseLists[index].addEventListener(
"mouseleave",
function handleMouseleave(event) {
expenseLists[index].removeEventListener(
"mouseleave",
handleMouseleave
);
// expenseLists[index].style.display = 'none';
expenseLists[index].style.zIndex = -1;
expenseLists[index].style.opacity = "0";
expenseLists[index].style.pointerEvents = "none";
chevrons[index].parentElement.style.borderColor = "#DCDCDC";
}
);
//process another transaction to handle click on expense type
let expenseItems = expenseLists[index].querySelectorAll("li");
expenseItems.forEach(expenseItem => {
expenseItem.addEventListener("click", () => {
//update Status column
expenseItem.parentElement.previousElementSibling.childNodes[1].nodeValue =
expenseItem.innerText;
let transactionUpdate = db.transaction(
["expense_os"],
"readwrite"
);
let objectStore = transactionUpdate.objectStore("expense_os");
//dataId is the key in expense_os db that we want to update, has to convert to number
let dataId =
+expenseItem.parentElement.parentElement.parentElement.getAttribute(
"data-id"
);
let objectStoreRetrieveRequest = objectStore.get(dataId);
objectStoreRetrieveRequest.onsuccess = () => {
let expenseTransactionResult =
objectStoreRetrieveRequest.result;
expenseTransactionResult.status = expenseItem.innerText;
let expenseUpdateRequest = objectStore.put(
expenseTransactionResult
);
expenseUpdateRequest.onsuccess = () => {};
};
});
});
});
});
//pagination test
//persumebly all expense entries are all rendered at this point
//with all expense dropdowns appended
// let expenseListItems = document.querySelectorAll('.indexedDb li[data-id]')
// console.log(expenseListItems.length)
// expenseListItems.forEach((listItem, index) => {
// if (index > 20) {
// listItem.style.display = 'none';
// }
// })
};
};
}
function deleteData(e) {
//get primary key (id) of the node getting deleted
let id = e.target.parentNode.getAttribute("data-id");
//start a transaction
let transaction = db.transaction(["expense_os"], "readwrite");
let objectStore = transaction.objectStore("expense_os");
let request = objectStore.delete(+id); //delete() takes a NUMBER!!! need to convert it
//remove node from DOM
contentParent.removeChild(e.target.parentNode);
transaction.oncomplete = () => {
console.log(`Transaction with ${id} had been deleted`);
};
// displayData();
}
};
//add functionality to load sample data
let linkToLoadSampleData = document.querySelector("#sampleData");
linkToLoadSampleData.addEventListener("click", () => {
//add transaction fake data
openDB()
.then(db => {
return new Promise((resolve, reject) => {
let transaction = db.transaction(["expense_mt"], "readwrite");
let objectStore = transaction.objectStore("expense_mt");
let fakeTypes = [
{ type: "expenseType1" },
{ type: "expenseType2" },
{ type: "expenseType3" },
{ type: "expenseType4" },
];
fakeTypes.forEach(fakeType => {
let request = objectStore.add(fakeType);
request.onsuccess = () => {};
});
transaction.oncomplete = () => {};
resolve(db);
}).then(db => {
let transaction = db.transaction(["expense_os"], "readwrite");
let objectStore = transaction.objectStore("expense_os");
let fakeInfo1 = {
merchant: "sampleMerc1",
date: "2020-11-01",
status: "Uncategorized",
amount: 10.01,
};
let fakeInfo2 = {
merchant: "sampleMerc2",
date: "2020-11-02",
status: "Uncategorized",
amount: 21.02,
};
let fakeInfo3 = {
merchant: "sampleMerc3",
date: "2020-11-03",
status: "Uncategorized",
amount: 32.03,
};
let fakeInfo4 = {
merchant: "sampleMerc4",
date: "2020-11-04",
status: "Uncategorized",
amount: 43.04,
};
let fakeInfo5 = {
merchant: "sampleMerc5",
date: "2020-11-05",
status: "Uncategorized",
amount: 54.05,
};
let fakeInfo6 = {
merchant: "sampleMerc6",
date: "2020-11-06",
status: "Uncategorized",
amount: 65.06,
};
let fakeInfo7 = {
merchant: "sampleMerc7",
date: "2020-11-07",
status: "Uncategorized",
amount: 102.07,
};
let fakeDataSets = [
fakeInfo1,
fakeInfo2,
fakeInfo3,
fakeInfo4,
fakeInfo5,
fakeInfo6,
fakeInfo7,
];
fakeDataSets.forEach(fakeData => {
let request = objectStore.add(fakeData);
request.onsuccess = () => {};
});
transaction.oncomplete = () => {};
resolve("fake added");
});
})
.then(console.log);
});
// async function addSampleData() {
// let db = await openDB();
// let transaction = await db.transaction(['expense_mt'], 'readwrite');
// let objectStore = await transaction.objectStore('expense_mt');
// let fakeTypes = [
// {type: 'expenseType1'},
// {type: 'expenseType2'},
// {type: 'expenseType3'},
// {type: 'expenseType4'}
// ];
// fakeTypes.forEach(fakeType => {
// let request = objectStore.add(fakeType);
// request.oncomplete = () => console.log('add')
// });
// transaction.oncomplete = () => console.log('complete')
// return db;
// }
// linkToLoadSampleData.addEventListener('click', () => {
// addSampleData();
// })