diff --git a/public/js/main.js b/public/js/main.js index ff0eac39..60b9ebba 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,22 +1,34 @@ +// select all trash icon elements for delete functionality const deleteBtn = document.querySelectorAll('.fa-trash') +// select all todo item text spans const item = document.querySelectorAll('.item span') +// select all completed todo item spans const itemCompleted = document.querySelectorAll('.item span.completed') +// add click event listeners to all delete buttons Array.from(deleteBtn).forEach((element)=>{ + // trigger deleteItem function when trash icon is clicked element.addEventListener('click', deleteItem) }) +// add click event listeners to all todo item spans Array.from(item).forEach((element)=>{ + // trigger markComplete function when item text is clicked element.addEventListener('click', markComplete) }) +// add click event listeners to all completed item spans Array.from(itemCompleted).forEach((element)=>{ + // trigger markUnComplete function when completed item is clicked element.addEventListener('click', markUnComplete) }) +// async function to delete todo item from database async function deleteItem(){ + // get the todo item text from the clicked element's parent const itemText = this.parentNode.childNodes[1].innerText try{ + // send DELETE request to server with item text const response = await fetch('deleteItem', { method: 'delete', headers: {'Content-Type': 'application/json'}, @@ -24,18 +36,23 @@ async function deleteItem(){ 'itemFromJS': itemText }) }) + // parse server response const data = await response.json() console.log(data) + // reload page to show updated todo list location.reload() - }catch(err){ + // log any errors that occur console.log(err) } } +// async function to mark todo item as completed async function markComplete(){ + // get the todo item text from the clicked element's parent 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'}, @@ -43,18 +60,23 @@ async function markComplete(){ 'itemFromJS': itemText }) }) + // parse server response const data = await response.json() console.log(data) + // reload page to show updated todo list location.reload() - }catch(err){ + // log any errors that occur console.log(err) } } +// async function to mark todo item as incomplete async function markUnComplete(){ + // get the todo item text from the clicked element's parent 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'}, @@ -62,11 +84,13 @@ async function markUnComplete(){ 'itemFromJS': itemText }) }) + // parse server response const data = await response.json() console.log(data) + // reload page to show updated todo list location.reload() - }catch(err){ + // log any errors that occur console.log(err) } } \ No newline at end of file diff --git a/server.js b/server.js index 58b53e2f..92d99935 100644 --- a/server.js +++ b/server.js @@ -1,93 +1,128 @@ +// import express framework const express = require('express') +// create express application instance const app = express() +// import MongoDB client for database operations const MongoClient = require('mongodb').MongoClient +// set local port to 2121 const PORT = 2121 +// import environment variables from .env file require('dotenv').config() - +// database connection variables let db, + // connection string stored securely in environment variables dbConnectionStr = process.env.DB_STRING, + // set database name to 'todo' dbName = 'todo' +// connect to MongoDB database using connection string MongoClient.connect(dbConnectionStr, { useUnifiedTopology: true }) .then(client => { + // log successful database connection console.log(`Connected to ${dbName} Database`) + // assign database instance for use throughout app db = client.db(dbName) }) - + +// configure EJS as the templating engine app.set('view engine', 'ejs') +// serve static files (CSS, JS, images) from public folder app.use(express.static('public')) +// middleware to parse URL-encoded form data app.use(express.urlencoded({ extended: true })) +// middleware to parse JSON request bodies app.use(express.json()) - +// GET request for home page route app.get('/',async (request, response)=>{ + // fetch all todo items from database as array const todoItems = await db.collection('todos').find().toArray() + // count incomplete todo items (completed: false) const itemsLeft = await db.collection('todos').countDocuments({completed: false}) + // render index page with todo data 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)) + // catch any errors (Note: this should be in a try-catch block) + .catch(error => console.error(error)) }) +// POST request to add new todo item (connected to addTodo form) app.post('/addTodo', (request, response) => { + // insert new todo with text from form and completed status false db.collection('todos').insertOne({thing: request.body.todoItem, completed: false}) .then(result => { + // log successful addition console.log('Todo Added') + // redirect to home page to show updated list response.redirect('/') }) + // catch and log any errors .catch(error => console.error(error)) }) +// PUT request to mark todo item as completed (linked to markComplete function in main.js) app.put('/markComplete', (request, response) => { + // find todo item by name and update completed status to true db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + // set completed property to true $set: { completed: true } },{ - sort: {_id: -1}, - upsert: false + sort: {_id: -1}, // sort by newest first (descending order by _id) + upsert: false // don't create new document if no match found }) .then(result => { + // log successful completion console.log('Marked Complete') + // send JSON response to client response.json('Marked Complete') }) + // catch and log any errors .catch(error => console.error(error)) }) +// PUT request to mark item as incomplete (linked to markUnComplete function in main.js) app.put('/markUnComplete', (request, response) => { + // find todo item by name and update completed status to false db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + // set completed property to false $set: { completed: false } },{ - sort: {_id: -1}, - upsert: false + sort: {_id: -1}, // sort by newest first (descending order by _id) + upsert: false // don't create new document if no match found }) .then(result => { - console.log('Marked Complete') - response.json('Marked Complete') + // log successful update + console.log('Marked Incomplete') + // send JSON response to client + response.json('Marked Incomplete') }) + // catch and log any errors .catch(error => console.error(error)) }) +// DELETE request to remove todo item (linked to deleteItem function in main.js) app.delete('/deleteItem', (request, response) => { + // find and delete todo item by name db.collection('todos').deleteOne({thing: request.body.itemFromJS}) .then(result => { + // log successful deletion console.log('Todo Deleted') + // send JSON response to client response.json('Todo Deleted') }) + // catch and log any errors .catch(error => console.error(error)) }) +// start server on environment port or default port 2121 app.listen(process.env.PORT || PORT, ()=>{ + // log server startup confirmation console.log(`Server running on port ${PORT}`) }) \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index a26617ae..3513ef47 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -4,44 +4,45 @@ - Document + Todo List App + + - - - - - - - - Document -

Todo List:

+ + +

Left to do: <%= left %>

Add A Todo:

+
+