Skip to content
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
141 changes: 77 additions & 64 deletions public/js/main.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,85 @@
const deleteBtn = document.querySelectorAll('.fa-trash')
const item = document.querySelectorAll('.item span')
const itemCompleted = document.querySelectorAll('.item span.completed')
// The frontend client-side code that wires up the todo list UI to te backend routes

Array.from(deleteBtn).forEach((element)=>{
element.addEventListener('click', deleteItem)
})
// Select all delete icons, all todo text spans and all completed todos, to let these attach
// behaviour to each matching element in the NodeList
const deleteBtn = document.querySelectorAll(".fa-trash");
const item = document.querySelectorAll(".item span");
const itemCompleted = document.querySelectorAll(".item span.completed");

Array.from(item).forEach((element)=>{
element.addEventListener('click', markComplete)
})
// For every delete icon, add a click listener that triggers the deleteItem function/handler.
Array.from(deleteBtn).forEach((element) => {
element.addEventListener("click", deleteItem);
});

Array.from(itemCompleted).forEach((element)=>{
element.addEventListener('click', markUnComplete)
})
// For every incomplete todo text span, add a click listener that triggers the markComplete function/handler.
Array.from(item).forEach((element) => {
element.addEventListener("click", markComplete);
});

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()
// For every completed todo text span, add a click listener that triggers the markUnComplete function/handler.
Array.from(itemCompleted).forEach((element) => {
element.addEventListener("click", markUnComplete);
});

}catch(err){
console.log(err)
}
// A handler for deleting a todo items, it runs in the context of the clicked delete icon (`this`).
// Reads the text of the todo from the DOM, sends it to the server in a DELETE request.
async function deleteItem() {
// Go from the clicked this icon to its parent element, then to the second child node which is the span containing the text, then grab the text of the todo item.
const itemText = this.parentNode.childNodes[1].innerText;
try {
const response = await fetch("deleteItem", {
method: "delete",
headers: { "Content-Type": "application/json" },
// Send the todo text in the request body so the server knows which item to delete.
body: JSON.stringify({
itemFromJS: itemText,
}),
});
// Get the JSON response from the server and log it, then reload the page to reflect the change
const data = await response.json();
console.log(data);
location.reload();
} catch (err) {
// If anything goes wrong with the request, log the error.
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)
}
// Handler for marking a todo as complete, similar to deleteItem, but sends a PUT/update request
// to the markComplete route, expecting the server to update the completed state of the item
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);
}
}

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)
}
}
// Handler for marking a todo as not complete, Sends a PUT/update request to the markUnComplete
// route so the server can toggle the state to completed: false
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);
}
}
188 changes: 112 additions & 76 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,129 @@
const express = require('express')
const app = express()
const MongoClient = require('mongodb').MongoClient
const PORT = 2121
require('dotenv').config()
// Backend server-side code for the todo list app

// Import the Express framework
const express = require("express");
// create an instance of the express application
const app = express();

// Import the MongoDB client in order to connect to the database
const MongoClient = require("mongodb").MongoClient;

// Set a default port to 2121 used when not running the app locally
const PORT = 2121;

// Load environment variables from a .env file
require("dotenv").config();

// set variable names for the database connection string and the database name
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())
// selecting, configuring and connecting to the specific database using dbConnectionStr
MongoClient.connect(dbConnectionStr, { useUnifiedTopology: true }).then((client) => {
console.log(`Connected to ${dbName} Database`);
db = client.db(dbName);
});

// Configuring Express:
// - Set the view/template engine to ejs
// - server the static assets from the public folder
// - parse url encoded form data into the request body
// - parse json request bodies into the request body
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))
})
// Handle get requests to the root url/front page of the app. Fetch all todo items and the count of
// incomplete items from MongoDB, then render the index.ejs template with that data.
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));
});

app.post('/addTodo', (request, response) => {
db.collection('todos').insertOne({thing: request.body.todoItem, completed: false})
.then(result => {
console.log('Todo Added')
response.redirect('/')
// Handle post requests to the /addTodo url, when a new todo form is submitted. Insert the new item
// into the todo collection, then redirect to the homepage to reload the page including the change.
app.post("/addTodo", (request, response) => {
db.collection("todos")
.insertOne({ thing: request.body.todoItem, completed: false })
.then((result) => {
console.log("Todo Added");
response.redirect("/");
})
.catch(error => console.error(error))
})
.catch((error) => console.error(error));
});

app.put('/markComplete', (request, response) => {
db.collection('todos').updateOne({thing: request.body.itemFromJS},{
// Handle put requests to /markComplete. Find the document where the thing field match the text
// sent from the client and set its completed flag to true. If multiple match, update the most
// recent one. Do not create a new document if none match (upsert: false).
app.put("/markComplete", (request, response) => {
db.collection("todos")
.updateOne(
{ thing: request.body.itemFromJS },
{
$set: {
completed: true
}
},{
sort: {_id: -1},
upsert: false
completed: true,
},
},
{
sort: { _id: -1 },
upsert: false,
}
)
.then((result) => {
console.log("Marked Complete");
response.json("Marked Complete");
})
.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},{
// Same as markComplete, but set completed to false
app.put("/markUnComplete", (request, response) => {
db.collection("todos")
.updateOne(
{ thing: request.body.itemFromJS },
{
$set: {
completed: false
}
},{
sort: {_id: -1},
upsert: false
})
.then(result => {
console.log('Marked Complete')
response.json('Marked Complete')
completed: false,
},
},
{
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.delete('/deleteItem', (request, response) => {
db.collection('todos').deleteOne({thing: request.body.itemFromJS})
.then(result => {
console.log('Todo Deleted')
response.json('Todo Deleted')
// Handle delete requests to /deleteItem. Remove a single todo from the collection whose thing value matches the text sent from the client
app.delete("/deleteItem", (request, response) => {
db.collection("todos")
.deleteOne({ thing: request.body.itemFromJS })
.then((result) => {
console.log("Todo Deleted");
response.json("Todo Deleted");
})
.catch(error => console.error(error))

})
.catch((error) => console.error(error));
});

app.listen(process.env.PORT || PORT, ()=>{
console.log(`Server running on port ${PORT}`)
})
// Start the server, using the hosting provider's port if available, otherwise use port 2121
app.listen(process.env.PORT || PORT, () => {
console.log(`Server running on port ${PORT}`);
});