diff --git a/.vscode/settings.json b/.vscode/settings.json index c3c81b8b..aeadfddb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "editor.fontSize": 42, - "terminal.integrated.fontSize": 62 -} \ No newline at end of file + "editor.fontSize": 42, // Controls code editior text size + "terminal.integrated.fontSize": 62 // Controls terminal window text size +} diff --git a/public/css/style.css b/public/css/style.css index 0475253a..8958b3ae 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,7 +1,12 @@ -h1{ - color: red; +/* CSS styles for the todo app */ + +/* makes the title red */ +h1 { + color: red; +} + +/* shows completed todos with a strikethrough in grayshows completed todos with a strikethrough in gray */ +.completed { + color: gray; + text-decoration: line-through; } -.completed{ - color: gray; - text-decoration: line-through; -} \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index ff0eac39..356d8c4a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,72 +1,90 @@ -const deleteBtn = document.querySelectorAll('.fa-trash') -const item = document.querySelectorAll('.item span') -const itemCompleted = document.querySelectorAll('.item span.completed') +// Select all trash icons (delete buttons) +const deleteBtn = document.querySelectorAll('.fa-trash'); +// Select all todo item spans (uncompleted items) +const item = document.querySelectorAll('.item span'); +// Select all completed todo item spans +const itemCompleted = document.querySelectorAll('.item span.completed'); -Array.from(deleteBtn).forEach((element)=>{ - element.addEventListener('click', deleteItem) -}) +// Convert delete buttons to array and add click event listeners to each +Array.from(deleteBtn).forEach((element) => { + element.addEventListener('click', deleteItem); +}); -Array.from(item).forEach((element)=>{ - element.addEventListener('click', markComplete) -}) +// Convert todo items to array and add click event listeners for marking complete +Array.from(item).forEach((element) => { + element.addEventListener('click', markComplete); +}); -Array.from(itemCompleted).forEach((element)=>{ - element.addEventListener('click', markUnComplete) -}) +// Convert completed items to array and add click listeners for marking incomplete +Array.from(itemCompleted).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() - - }catch(err){ - console.log(err) - } +// Function to delete a todo item +async function deleteItem() { + // Get the text of the todo item + const itemText = this.parentNode.childNodes[1].innerText; + try { + // Send delete request to server with the todo text + const response = await fetch('deleteItem', { + method: 'delete', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + itemFromJS: itemText, + }), + }); + // Get response from server + const data = await response.json(); + console.log(data); + // Refresh the page to show updated list + location.reload(); + } 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() - - }catch(err){ - console.log(err) - } +// Function to mark a todo as complete +async function markComplete() { + // Get the text of the todo item + const itemText = this.parentNode.childNodes[1].innerText; + try { + // Send PUT request to server to mark item as complete + const response = await fetch('markComplete', { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + itemFromJS: itemText, + }), + }); + // Get response from server + const data = await response.json(); + console.log(data); + // Refresh the page to show updated list + location.reload(); + } 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() - - }catch(err){ - console.log(err) - } -} \ No newline at end of file +// Function to mark a completed todo as incomplete +async function markUnComplete() { + // Get the text of the todo item + const itemText = this.parentNode.childNodes[1].innerText; + try { + // Send PUT request to server to mark item as incomplete + const response = await fetch('markUnComplete', { + method: 'put', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + itemFromJS: itemText, + }), + }); + // Get response from server + const data = await response.json(); + console.log(data); + // Refresh the page to show updated list + location.reload(); + } catch (err) { + console.log(err); + } +} diff --git a/server.js b/server.js index 58b53e2f..736a3723 100644 --- a/server.js +++ b/server.js @@ -1,93 +1,155 @@ -const express = require('express') -const app = express() -const MongoClient = require('mongodb').MongoClient -const PORT = 2121 -require('dotenv').config() +// Imports the Express Framework - All tools needs to build a web server +const express = require('express'); +// Creates a new Express application - Create the web server you will use +const app = express(); +// Imports MongoDB Driver - Allows you to communicate with Database +const MongoClient = require('mongodb').MongoClient; + +// Sets which port number your server will run on - Like choosing what door your website will use +const PORT = 2121; + +// Loads enivroment variables from the .env file - This keeps sensitive info like database passwords secure +require('dotenv').config(); + +// Creates a variable 'db' that will hold your database connection later - currently empty let db, - dbConnectionStr = process.env.DB_STRING, - dbName = 'todo' + // Gets your database connection string from your environment variables (.env file) + // This is usually something like 'mongodb+srv://username:password@cluster.mongodb.net' + dbConnectionStr = process.env.DB_STRING, + // Names your database 'todo' - this is the specific database you'll be using in MongoDB + dbName = 'todo'; +// Starts connection to MongoDB using our connection string, with modern connection settings 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()) - - -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)) -}) + // After successfully connecting, run this function with the database client + .then((client) => { + // Log a message confirming we've connected to our database + console.log(`Connected to ${dbName} Database`); + + // Store the database connection in our db variable for later use + db = client.db(dbName); + }); + +// Tell Express to use EJS as the template engine for rendering our pages +app.set('view engine', 'ejs'); + +// Set up the 'public' folder to serve static files like CSS, images, and JavaScript +app.use(express.static('public')); + +// Allow Express to handle form data submitted by users (like todo items) +app.use(express.urlencoded({ extended: true })); + +// Allow Express to understand JSON data that's sent to the server +app.use(express.json()); + +// When someone visits the homepage ('/') +app.get('/', async (request, response) => { + // Get all todo items from the database and convert them to an array + const todoItems = await db.collection('todos').find().toArray(); + + // Count how many uncompleted items are left (where completed = false) + const itemsLeft = await db + .collection('todos') + .countDocuments({ completed: false }); + + // Show the index.ejs page, passing in our todo items and count of items left + response.render('index.ejs', { items: todoItems, left: itemsLeft }); +}); + +// Handle POST requests to /addTodo when someone submits a new todo app.post('/addTodo', (request, response) => { - db.collection('todos').insertOne({thing: request.body.todoItem, completed: false}) - .then(result => { - console.log('Todo Added') - response.redirect('/') + // Add a new todo item to our database 'todos' collection + // 'thing' is the todo text, and set completed to false by default + db.collection('todos') + .insertOne({ thing: request.body.todoItem, completed: false }) + // After successfully adding the todo + .then((result) => { + // Log that we added a todo + console.log('Todo Added'); + // Send the user back to the homepage + response.redirect('/'); }) - .catch(error => console.error(error)) -}) + // If there's an error, log it to the console + .catch((error) => console.error(error)); +}); app.put('/markComplete', (request, response) => { - db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + db.collection('todos') + .updateOne( + { thing: request.body.itemFromJS }, + { $set: { - completed: true - } - },{ - sort: {_id: -1}, - upsert: false - }) - .then(result => { - console.log('Marked Complete') - response.json('Marked Complete') + completed: true, + }, + }, + { + sort: { _id: -1 }, + upsert: false, + } + ) + .then((result) => { + console.log('Marked Complete'); + response.json('Marked Complete'); }) - .catch(error => console.error(error)) + .catch((error) => console.error(error)); +}); -}) - -app.put('/markUnComplete', (request, response) => { - db.collection('todos').updateOne({thing: request.body.itemFromJS},{ +// Handle PUT requests to /markComplete when someone marks a todo as done +app.put('/markComplete', (request, response) => { + // Find and update the todo in our database + db.collection('todos') + .updateOne( + // Find the todo item that matches the text sent from our JavaScript + { thing: request.body.itemFromJS }, + // Set its 'completed' status to true + { $set: { - completed: false - } - },{ - sort: {_id: -1}, - upsert: false + completed: true, + }, + }, + // Additional options for the update: + // sort by newest first (-1), and don't create if not found (upsert: false) + { + sort: { _id: -1 }, + upsert: false, + } + ) + // After successfully marking as complete + .then((result) => { + // Log success message + console.log('Marked Complete'); + // Send success response back to the browser + response.json('Marked Complete'); }) - .then(result => { - console.log('Marked Complete') - response.json('Marked Complete') - }) - .catch(error => console.error(error)) - -}) + // If there's an error, log it to the console + .catch((error) => console.error(error)); +}); +// Handle DELETE requests to /deleteItem when someone wants to remove a todo app.delete('/deleteItem', (request, response) => { - db.collection('todos').deleteOne({thing: request.body.itemFromJS}) - .then(result => { - console.log('Todo Deleted') - response.json('Todo Deleted') + // Delete the todo from our database 'todos' collection + db.collection('todos') + // Find and delete the todo that matches the text sent from our JavaScript + .deleteOne({ thing: request.body.itemFromJS }) + // After successfully deleting the todo + .then((result) => { + // Log that we deleted the todo + console.log('Todo Deleted'); + // Send success message back to the browser + response.json('Todo Deleted'); }) - .catch(error => console.error(error)) + // If there's an error, log it to the console + .catch((error) => console.error(error)); +}); -}) +// Start the server and make it listen for requests +app.listen(process.env.PORT || PORT, () => { + // Use either the PORT from our environment variables (like Heroku provides) + // OR use port 2121 (which we set earlier) if there's no environment variable -app.listen(process.env.PORT || PORT, ()=>{ - console.log(`Server running on port ${PORT}`) -}) \ No newline at end of file + // Log a message when the server successfully starts + console.log(`Server running on port ${PORT}`); +}); diff --git a/views/index.ejs b/views/index.ejs index a26617ae..69cce732 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -1,47 +1,52 @@ + - - - - + + + + Document - - - - + + + + - - - - - - Document - - -

Todo List:

- -

Left to do: <%= left %>

+

Left to do: <%= left %>

-

Add A Todo:

+

Add A Todo:

-
- - -
- +
+ + +
- - + + + +