Skip to content

Commented Analysis of Express TO-DO-LIST #876

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions public/js/main.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,96 @@
// 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'},
body: JSON.stringify({
'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'},
body: JSON.stringify({
'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'},
body: JSON.stringify({
'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)
}
}
69 changes: 52 additions & 17 deletions server.js
Original file line number Diff line number Diff line change
@@ -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}`)
})
21 changes: 11 additions & 10 deletions views/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,45 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<title>Todo List App</title>
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<!-- Custom CSS styles -->
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Todo List: </h1>

<!-- Display all todo items from database -->
<ul class="todoItems">
<!-- Loop through each todo item passed from server -->
<% for(let i=0; i < items.length; i++) {%>
<li class="item">
<%# Show completed items with strikethrough style %>
<% if(items[i].completed === true) {%>
<span class='completed'><%= items[i].thing %></span>
<% }else{ %>
<span><%= items[i].thing %></span>
<% } %>
<!-- Trash icon for deleting items -->
<span class='fa fa-trash'></span>
</li>
<% } %>
</ul>

<!-- Display count of incomplete items -->
<h2>Left to do: <%= left %></h2>

<h2>Add A Todo:</h2>

<!-- Form to add new todo item -->
<form action="/addTodo" method="POST">
<input type="text" placeholder="Thing To Do" name="todoItem">
<input type="submit">
</form>


<!-- JavaScript for client-side interactions -->
<script src='js/main.js'></script>
</body>
</html>