diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index c3c81b8b..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "editor.fontSize": 42,
- "terminal.integrated.fontSize": 62
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 5ab0326c..6897d92b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,48 @@
-npm install
-add DB_STRING to .env file
\ No newline at end of file
+# ToDo List - EJS, MongoDB, Node + Express
+
+## Getting Started
+
+1. Go to the [todo-list-express](https://github.com/100devs/todo-list-express) repository and click `Fork` on the top right. This is basically "Save As"ing the project to your own GitHub.
+1. In your forked repository, click `Code` -> `HTTPS` -> copy the link
+1. Choose a location on your local PC where you want to create a copy of these files to work on.
+1. Use the terminal to access that location, and type: `git clone [paste link copied during step 2]`
+
+Now you can work on the code. The assignment is to comment every line. Start with server.js, then move to index.ejs, main.js, and style.css if you want.
+
+## Running the App
+
+> Note: This technically isn't part of the assignment.
+
+### Orginal README said: run `npm install`
+
+- Run `npm install` from the terminal while in your repository's folder.
+ - This command goes through the `package.json` file and installs all dependencies listed there so the app can work.
+
+### Original README said: add DB_STRING to .env file
+
+- Create a file called `.env`, and type `DB_STRING=your-MongoDB-connection-string`.
+- **Note**: If you need help, follow along with this [article](https://zellwk.com/blog/crud-express-mongodb/#setting-up-mongodb-atlas) to get your connection string.
+- Add your connection string to your `.env` file. It should look something like:
+ - `DB_STRING=mongodb+srv://...`
+ - Continue with your _full connection string_. This is sensitive information, which is why it's being stored in your `.env`, and your `.gitignore` file lists `.env` so that it is not shared to your public github repository.
+
+1. Start the node server by running this command in a terminal within your repository folder: `node server.js`
+
+- **Note**: If running properly, the terminal will state:
+ - `Server running on port 2121`
+ - `Connected to todo Database`
+
+1. Open the application by going to `http://127.0.0.1:2121/` or `http://localhost:2121/`
+
+- The server is running on port 2121 on your local machine, which has the address of 127.0.0.1 (or localhost) when referring to itself.
+
+## Updates
+
+1. added styling
+1. change text at top dynamically if all tasks get completed
+1. added dark/light mode toggle
+
+### Todos
+
+- [] update app to use list item ids instead of DOM text to make changes
+- [] improve list responsiveness on tiny screens
diff --git a/public/css/normalize.css b/public/css/normalize.css
new file mode 100644
index 00000000..c4699894
--- /dev/null
+++ b/public/css/normalize.css
@@ -0,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+ html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+ border-style: none;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Misc
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+ display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
diff --git a/public/css/style.css b/public/css/style.css
index 0475253a..5c4bf7b3 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -1,7 +1,318 @@
-h1{
- color: red;
+:root {
+ /* colors */
+ --paper-background-color: #f7f7e6;
+ --completed-color: #bdbdbd;
+ --primary-dark-color: #0d1117;
+ --secondary-dark-color: #282a36;
+ --primary-light-color: #f8f8f8;
+ --secondary-light-color: #8f8f8f;
+ --accent-color: #73cfee;
+ --action-color: #dd6387;
+
+ /* shadows */
+ --accent-box-glow: 0 0 0.5em var(--accent-color);
+ --accent-text-outline: -2px -2px 0 var(--accent-color),
+ 2px -2px 0 var(--accent-color), -2px 2px 0 var(--accent-color),
+ 2px 2px 0 var(--accent-color);
+ --action-text-glow: 0.1em 0.1em 0.2em rgba(221, 99, 135, 0.5);
+ --light-text-glow: 0 0 0.2em var(--primary-light-color);
+ --accent-box-shadow: 0 -0.2em 0.2em rgba(115, 207, 238, 0.5);
+ --light-box-shadow: 0 -0.2em 0.2em rgba(143, 143, 143, 0.5);
+
+ /* transitions */
+ --transition: all 0.3s ease-in-out;
+}
+
+/* overall document settings */
+body {
+ background-color: var(--primary-light-color);
+ color: var(--secondary-light-color);
+ font-family: system-ui, -apple-system, BlinkMacSystemFont,
+ 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
+ 'Helvetica Neue', sans-serif;
+ line-height: 1.5;
+
+ /* main grid */
+ display: grid;
+ grid: auto 1fr auto / 10vw 1fr 10vw;
+ grid-template-areas:
+ 'header header header'
+ 'main main main'
+ 'footer footer footer';
+ min-height: 100vh;
+}
+
+a,
+a *,
+button,
+button * {
+ cursor: pointer;
+}
+
+/* header */
+header {
+ grid-area: header;
+ text-align: center;
+ font-size: clamp(1.75rem, 4vw + 1rem, 3rem);
+}
+
+.title {
+ margin: 0;
+}
+
+/* main content */
+main {
+ grid-area: main;
+}
+
+.paperContainer {
+ position: relative;
+ width: 90%;
+ max-width: 800px;
+ min-width: 400px;
+ height: 480px;
+ margin: 0 auto;
+ background: var(--paper-background-color);
+ border: 1px solid var(--secondary-light-color);
+ border-radius: 10px;
+ overflow: auto;
+}
+
+.paperContainer:before {
+ content: '';
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 60px;
+ background: radial-gradient(
+ var(--secondary-light-color) 8px,
+ transparent 2px
+ )
+ repeat-y;
+ background-size: 30px 30px;
+ border-right: 3px solid var(--action-color);
+ box-sizing: border-box;
+}
+
+.paperContent {
+ position: absolute;
+ top: 30px;
+ right: 0;
+ bottom: 30px;
+ left: 60px;
+ background: linear-gradient(
+ transparent,
+ transparent 28px,
+ var(--accent-color) 28px
+ );
+ background-size: 100% 30px;
+ overflow-y: auto;
+}
+
+.todosLeft {
+ margin: 0 0 0 0.25em;
+}
+
+.todoItems {
+ margin-top: -3px;
+}
+
+.paperContent .item {
+ display: flex;
+ align-items: center;
+ height: 30px;
+ line-height: 30px;
+ font-size: 1.3em;
+ font-weight: bold;
+ font-family: 'Handlee', cursive;
+ z-index: 1;
+}
+
+.item .fa-trash {
+ padding-left: 10px;
+}
+
+.item span:hover,
+.fa-trash:hover {
+ color: var(--action-color);
+ transition: var(--transition);
+}
+
+.item:hover {
+ cursor: pointer;
+}
+
+.completed {
+ color: var(--completed-color);
+ text-decoration: line-through;
+}
+
+.addTodoContainer {
+ max-width: max-content;
+ margin: 0 auto;
+}
+
+.addTodo {
+ margin-bottom: 0;
+}
+
+input[type='text'] {
+ background: var(--paper-background-color);
+ color: var(--primary-dark-color);
+ border: 1px solid var(--accent-color);
+ padding: 12px 16px;
+ font-size: 1rem;
+ border-radius: 8px;
+ outline: none;
+ transition: var(--transition);
+ width: 250px;
+ box-shadow: 0 4px 8px rgba(115, 207, 238, 0.3);
+}
+
+input[type='text']::placeholder {
+ color: var(--secondary-light-color);
+ font-style: italic;
+}
+
+input[type='text']:focus {
+ border-color: var(--action-color);
+ box-shadow: var(--action-text-glow);
+}
+
+#addTaskBtn {
+ background: var(--action-color);
+ color: var(--primary-light-color);
+ border: 1px solid var(--action-color);
+ padding: 12px 24px;
+ font-size: 1rem;
+ font-weight: bold;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: var(--transition);
+}
+
+#addTaskBtn:hover {
+ box-shadow: var(--action-text-glow);
+ background: var(--primary-light-color);
+ color: var(--action-color);
+}
+
+#addTaskBtn:active {
+ transform: scale(0.95);
+ box-shadow: inset 0 4px 8px rgba(221, 99, 135, 0.5);
+}
+
+/* footer layout */
+footer {
+ grid-area: footer;
+ background-color: var(--secondary-light-color);
+ color: var(--primary-light-color);
+ box-shadow: var(--light-box-shadow);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 15px;
+ padding: 10px;
+ margin-top: 15px;
+ font-size: 18px;
+ font-weight: 500;
+}
+
+footer .fa-github,
+footer .fa-linkedin {
+ color: var(--primary-light-color);
+ font-size: 24px;
+ transition: var(--transition);
+}
+
+footer .fa-github:hover,
+footer .fa-linkedin:hover {
+ transform: rotateY(360deg) scale(1.4);
+ color: var(--action-color);
+}
+
+footer .fa-heart {
+ color: var(--action-color);
+ font-size: 24px;
+ transition: var(--transition);
+}
+
+footer .fa-heart:hover {
+ transform: scale(1.3);
+}
+
+/* Light mode */
+.light {
+ background-color: var(--primary-light-color);
+ color: var(--secondary-light-color);
+}
+
+/* Dark theme overrides */
+body.dark {
+ background-color: var(--primary-dark-color);
+ color: var(--primary-light-color);
+}
+
+.dark .paperContainer {
+ background-color: var(--secondary-dark-color);
+}
+
+.dark .fa-trash {
+ color: var(--primary-light-color);
+}
+
+.dark input[type='text'] {
+ background: var(--secondary-dark-color);
+ color: var(--primary-light-color);
+ border: 1px solid var(--secondary-light-color);
+ box-shadow: 0 4px 8px rgba(115, 207, 238, 0.3);
+}
+
+.dark input[type='text']::placeholder {
+ color: var(--primary-light-color);
+}
+
+.dark footer {
+ background-color: var(--secondary-dark-color);
+ box-shadow: var(--accent-box-shadow);
+}
+
+/* Update button text based on theme */
+button.dark-mode-toggle {
+ position: absolute;
+ top: 1em;
+ right: 1em;
+ font-size: 1.5em;
+ background: none;
+ border: none;
+ border-radius: 50%;
+ padding: 0.3em;
+ cursor: pointer;
+ transition: var(--transition);
+}
+
+button.dark-mode-toggle:hover {
+ box-shadow: var(--action-text-glow);
+}
+
+button.dark-mode-toggle:active {
+ transform: scale(0.95);
+}
+
+/* media queries */
+@media (max-width: 500px) {
+ #footer-span {
+ display: none;
+ }
+}
+
+@media (max-width: 300px) {
+ header {
+ font-size: 1em;
+ }
+
+ footer p {
+ display: none;
+ }
}
-.completed{
- color: gray;
- text-decoration: line-through;
-}
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 00000000..53134e8e
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/js/main.js b/public/js/main.js
index ff0eac39..12afd567 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -1,72 +1,143 @@
-const deleteBtn = document.querySelectorAll('.fa-trash')
-const item = document.querySelectorAll('.item span')
-const itemCompleted = document.querySelectorAll('.item span.completed')
+// select items from the DOM
+const deleteBtns = document.querySelectorAll('.fa-trash');
+const items = document.querySelectorAll('.item span');
+const completedItems = document.querySelectorAll(
+ '.item span.completed'
+);
-Array.from(deleteBtn).forEach((element)=>{
- element.addEventListener('click', deleteItem)
-})
+// querySelectorAll returns a NodeList since it is pulling things from the DOM
+// a NodeList is an 'array-like' and can be turned into an array for manipulation
+// next 3 lines creates arrays from the NodeLists selected at the top of the file and adds click event listeners to them, each running a different callback function
+Array.from(deleteBtns).forEach((element) => {
+ element.addEventListener('click', deleteItem);
+});
-Array.from(item).forEach((element)=>{
- element.addEventListener('click', markComplete)
-})
+Array.from(items).forEach((element) => {
+ element.addEventListener('click', markComplete);
+});
-Array.from(itemCompleted).forEach((element)=>{
- element.addEventListener('click', markUnComplete)
-})
+Array.from(completedItems).forEach((element) => {
+ element.addEventListener('click', markUnComplete);
+});
-async function deleteItem(){
- const itemText = this.parentNode.childNodes[1].innerText
- try{
- const response = await fetch('deleteItem', {
- method: 'delete',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- 'itemFromJS': itemText
- })
- })
- const data = await response.json()
- console.log(data)
- location.reload()
+// deletes an item from the todo list
+// must be async because fetching data from the server
+async function deleteItem() {
+ // select the text of the current item
+ const itemText = this.parentNode.childNodes[1].innerText;
+ // try to ask the server to delete an item with the `itemText` as the identifier
+ try {
+ // since async, must await the fetch from the server
+ // there must be a route on the server that matches 'deleteItem'
+ const response = await fetch('deleteItem', {
+ // 'delete' is the action to remove the item from the database
+ method: 'delete',
+ // lets the API know to expect JSON data
+ headers: { 'Content-Type': 'application/json' },
+ // converts the javascript to JSON
+ body: JSON.stringify({
+ itemFromJS: itemText,
+ }),
+ });
+ // wait for API response and save into `data` variable
+ const data = await response.json();
+ // print the response to the console
+ console.log(data);
+ // refresh the page to cause client to sent new GET req to show that the item has been deleted
+ location.reload();
- }catch(err){
- console.log(err)
- }
+ // if something goes wrong, print the error to the console
+ } catch (err) {
+ console.log(err);
+ }
}
-async function markComplete(){
- const itemText = this.parentNode.childNodes[1].innerText
- try{
- const response = await fetch('markComplete', {
- method: 'put',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- 'itemFromJS': itemText
- })
- })
- const data = await response.json()
- console.log(data)
- location.reload()
+// marks a todo item as complete
+// must be async because fetching data from the server
+async function markComplete() {
+ // select the text of the current item
+ const itemText = this.parentNode.childNodes[1].innerText;
+ // try to ask the server to update an item with the `itemText` as the identifier
+ try {
+ // since async, must await the fetch from the server
+ // there must be a route on the server that matches 'markComplete'
+ const response = await fetch('markComplete', {
+ // 'put' is the action to update the item in the database
+ method: 'put',
+ // lets the API know to expect JSON data
+ headers: { 'Content-Type': 'application/json' },
+ // converts the javascript to JSON
+ body: JSON.stringify({
+ itemFromJS: itemText,
+ }),
+ });
+ // wait for API response and save into `data` variable
+ const data = await response.json();
+ // print the response to the console
+ console.log(data);
+ // refresh the page to cause client to sent new GET req to show that the item has been marked as completed
+ location.reload();
- }catch(err){
- console.log(err)
- }
+ // if something goes wrong, print the error to the console
+ } catch (err) {
+ console.log(err);
+ }
}
-async function markUnComplete(){
- const itemText = this.parentNode.childNodes[1].innerText
- try{
- const response = await fetch('markUnComplete', {
- method: 'put',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({
- 'itemFromJS': itemText
- })
- })
- const data = await response.json()
- console.log(data)
- location.reload()
+// marks a todo item as uncompleted
+// must be async because fetching data from the server
+async function markUnComplete() {
+ // select the text of the current item
+ const itemText = this.parentNode.childNodes[1].innerText;
+ // since async, must await the fetch from the server
+ // there must be a route on the server that matches 'markUnComplete'
+ try {
+ const response = await fetch('markUnComplete', {
+ // 'put' is the action to update the item in the database
+ method: 'put',
+ // lets the API know to expect JSON data
+ headers: { 'Content-Type': 'application/json' },
+ // converts the javascript to JSON
+ body: JSON.stringify({
+ itemFromJS: itemText,
+ }),
+ });
+ // wait for API response and save into `data` variable
+ const data = await response.json();
+ // print the response to the console
+ console.log(data);
+ // refresh the page to cause client to sent new GET req to show that the item has been marked as uncompleted
+ location.reload();
- }catch(err){
- console.log(err)
- }
-}
\ No newline at end of file
+ // if something goes wrong, print the error to the console
+ } catch (err) {
+ console.log(err);
+ }
+}
+
+// Check for saved theme preference in localStorage
+const savedTheme = localStorage.getItem('theme');
+if (savedTheme) {
+ document.body.classList.add(savedTheme);
+}
+
+// Toggle dark mode
+const darkModeToggle = document.getElementById('darkModeToggle');
+darkModeToggle.addEventListener('click', () => {
+ const currentTheme = document.body.classList.contains('dark')
+ ? 'dark'
+ : 'light';
+
+ // Toggle theme
+ if (currentTheme === 'dark') {
+ document.body.classList.remove('dark');
+ document.body.classList.add('light');
+ darkModeToggle.textContent = '🌙'; // Change button text for light mode
+ localStorage.setItem('theme', 'light');
+ } else {
+ document.body.classList.remove('light');
+ document.body.classList.add('dark');
+ darkModeToggle.textContent = '🌞'; // Change button text for dark mode
+ localStorage.setItem('theme', 'dark');
+ }
+});
diff --git a/server.js b/server.js
index 58b53e2f..62607841 100644
--- a/server.js
+++ b/server.js
@@ -1,93 +1,142 @@
-const express = require('express')
-const app = express()
-const MongoClient = require('mongodb').MongoClient
-const PORT = 2121
-require('dotenv').config()
-
-
+// import express
+const express = require('express');
+// import mongodb
+const MongoClient = require('mongodb').MongoClient;
+// set up dotenv
+require('dotenv').config();
+// use express
+const app = express();
+// set default port
+const PORT = 2121;
+// initialize db variables
let db,
- dbConnectionStr = process.env.DB_STRING,
- dbName = 'todo'
+ dbConnectionStr = process.env.DB_STRING,
+ dbName = 'todo';
-MongoClient.connect(dbConnectionStr, { useUnifiedTopology: true })
- .then(client => {
- console.log(`Connected to ${dbName} Database`)
- db = client.db(dbName)
- })
-
-app.set('view engine', 'ejs')
-app.use(express.static('public'))
-app.use(express.urlencoded({ extended: true }))
-app.use(express.json())
+// connect to MongoDB database
+MongoClient.connect(dbConnectionStr, {
+ useUnifiedTopology: true,
+}).then((client) => {
+ console.log(`Connected to ${dbName} Database`);
+ db = client.db(dbName);
+});
+// let express know to expect ejs as view engine
+app.set('view engine', 'ejs');
+// tell express to use static files inside the public folder
+app.use(express.static('public'));
+// replace bodyparser middleware, this is how express can parse JSON
+app.use(express.urlencoded({ extended: true }));
+app.use(express.json());
-app.get('/',async (request, response)=>{
- const todoItems = await db.collection('todos').find().toArray()
- const itemsLeft = await db.collection('todos').countDocuments({completed: false})
- response.render('index.ejs', { items: todoItems, left: itemsLeft })
- // db.collection('todos').find().toArray()
- // .then(data => {
- // db.collection('todos').countDocuments({completed: false})
- // .then(itemsLeft => {
- // response.render('index.ejs', { items: data, left: itemsLeft })
- // })
- // })
- // .catch(error => console.error(error))
-})
+// set up route for home page -> GET/Read
+// is async because need to await req to mongodb for todo items
+app.get('/', async (request, response) => {
+ // search db for all todo items, put into an array
+ const todoItems = await db.collection('todos').find().toArray();
+ // count how many todos are not done
+ const itemsLeft = await db
+ .collection('todos')
+ .countDocuments({ completed: false });
+ // render the DOM using the index.ejs template, supplying the todoItems as items and itemsLeft as left
+ response.render('index.ejs', { items: todoItems, left: itemsLeft });
+});
+// route to add a new todo item -> POST/Create
app.post('/addTodo', (request, response) => {
- db.collection('todos').insertOne({thing: request.body.todoItem, completed: false})
- .then(result => {
- console.log('Todo Added')
- response.redirect('/')
+ // access the todos db collection
+ db.collection('todos')
+ // insert one item (as thing) using the form to set the body, hard coding whether it is completed as false
+ .insertOne({ thing: request.body.todoItem, completed: false })
+ // after the item is inserted (async)
+ .then((result) => {
+ // print to the console that the todo was added
+ console.log('Todo Added');
+ // tell the client to redirect back to the home page (essentially a refresh) to cause a new GET request
+ response.redirect('/');
})
- .catch(error => console.error(error))
-})
+ // if something goes wrong, print the error to the console
+ .catch((error) => console.error(error));
+});
+// route to mark a todo as complete -> PUT/Update
app.put('/markComplete', (request, response) => {
- db.collection('todos').updateOne({thing: request.body.itemFromJS},{
+ // access the todos db collection
+ db.collection('todos')
+ // update the first todo item found based on the todo text grabbed from the DOM on the client-side
+ .updateOne(
+ { thing: request.body.itemFromJS },
+ {
+ // set its completed attribute to true
$set: {
- completed: true
- }
- },{
- sort: {_id: -1},
- upsert: false
- })
- .then(result => {
- console.log('Marked Complete')
- response.json('Marked Complete')
+ completed: true,
+ },
+ },
+ {
+ // sort the items in descending order
+ sort: { _id: -1 },
+ // if no item found, do not add a new one
+ upsert: false,
+ }
+ )
+ .then((result) => {
+ // print to the console that the item is marked complete
+ console.log('Marked Complete');
+ // respond to client that item marked complete
+ response.json('Marked Complete');
})
- .catch(error => console.error(error))
-
-})
+ // if something goes wrong, print error to console
+ .catch((error) => console.error(error));
+});
+// route to mark a todo as uncompleted -> PUT/Update
app.put('/markUnComplete', (request, response) => {
- db.collection('todos').updateOne({thing: request.body.itemFromJS},{
+ // access the todos db collection
+ db.collection('todos')
+ // update the first todo item found based on the todo text grabbed from the DOM on the client-side
+ .updateOne(
+ { thing: request.body.itemFromJS },
+ {
+ // set its completed attribute to false
$set: {
- completed: false
- }
- },{
- sort: {_id: -1},
- upsert: false
- })
- .then(result => {
- console.log('Marked Complete')
- response.json('Marked Complete')
+ completed: false,
+ },
+ },
+ {
+ // sort the items in descending order
+ sort: { _id: -1 },
+ // if no item found, do not add a new one
+ upsert: false,
+ }
+ )
+ .then((result) => {
+ // print to the console that the item is marked uncompleted
+ console.log('Marked Uncompleted');
+ // respond to client that item marked uncompleted
+ response.json('Marked Uncompleted');
})
- .catch(error => console.error(error))
-
-})
+ // if something goes wrong, print error to console
+ .catch((error) => console.error(error));
+});
+// route to remove a todo from the list -> DELETE/Dee-ley-tey
app.delete('/deleteItem', (request, response) => {
- db.collection('todos').deleteOne({thing: request.body.itemFromJS})
- .then(result => {
- console.log('Todo Deleted')
- response.json('Todo Deleted')
+ // access the todos db collection
+ db.collection('todos')
+ // remove the first todo item found based on the todo text grabbed from the DOM on the client-side
+ .deleteOne({ thing: request.body.itemFromJS })
+ .then((result) => {
+ // print to the console that the item was deleted
+ console.log('Todo Deleted');
+ // respond to client that item was deleted
+ response.json('Todo Deleted');
})
- .catch(error => console.error(error))
-
-})
+ // if something goes wrong, print error to console
+ .catch((error) => console.error(error));
+});
-app.listen(process.env.PORT || PORT, ()=>{
- console.log(`Server running on port ${PORT}`)
-})
\ No newline at end of file
+// tell the server where to listen
+app.listen(process.env.PORT || PORT, () => {
+ // print listening statement to console
+ console.log(`Server running on port ${PORT}`);
+});
diff --git a/views/index.ejs b/views/index.ejs
index a26617ae..b11df0bc 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -1,47 +1,107 @@
+
+
-
-
-
-
- Document
-
-
-
-
-
-
-
-
-
-
- Document
-
-
- Todo List:
-
- <% for(let i=0; i < items.length; i++) {%>
- -
- <% if(items[i].completed === true) {%>
- <%= items[i].thing %>
- <% }else{ %>
- <%= items[i].thing %>
- <% } %>
-
-
- <% } %>
-
+
+
+
+
+
+
+
+
+
+ ToDo List
- Left to do: <%= left %>
+
+
+
+
- Add A Todo:
+
+
+
+
+
+
+
+
+
+
+
+ <% if ( left < 1 ) { %>
+ All Done!
+ <% } else { %>
+ <%= left %> Todos Left
+ <% } %>
+
-
-
+
+
+ <% for(let i=0; i < items.length; i++) {%>
+ -
+ <% if(items[i].completed === true) {%>
+ <%= items[i].thing %>
+ <% }else{ %>
+ <%= items[i].thing %>
+ <% } %>
+
+
+ <% } %>
+
+
+
-
-
+
+
Add Todo:
+
+
+
+
+
+