diff --git a/.gitignore b/.gitignore index 1dcef2d9..995d0606 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +# ensure node_modules is not sent to github (a lot of data and it already exists in browsers environment) node_modules +# ensure any of your environment variables are excluded from being pushed to github (could contain private info) .env \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c3c81b8b..f8d13956 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ +// tryna mess with us + { - "editor.fontSize": 42, - "terminal.integrated.fontSize": 62 + "editor.fontSize": 14, + "terminal.integrated.fontSize": 14 } \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css index 0475253a..c9fa4fc1 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,6 +1,9 @@ +/* make the heading red */ h1{ color: red; } + +/* text with a completed task will be colored gray and have a line thru it */ .completed{ color: gray; text-decoration: line-through; diff --git a/public/js/main.js b/public/js/main.js index ff0eac39..705be056 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,71 +1,109 @@ +// find all delete icons const deleteBtn = document.querySelectorAll('.fa-trash') +// find all instances of a span within any class="item" element const item = document.querySelectorAll('.item span') +// find all instances of a completed span within any class="item" element const itemCompleted = document.querySelectorAll('.item span.completed') +// for all instances of the delete icon, give it a click listener to call deleteItem Array.from(deleteBtn).forEach((element)=>{ element.addEventListener('click', deleteItem) }) +// for all instances of spans within class="item" elements, give it a click listener to call markComplete Array.from(item).forEach((element)=>{ element.addEventListener('click', markComplete) }) +// for all instances of completed items within class="item" elements, give it a click listener to markUnComplete Array.from(itemCompleted).forEach((element)=>{ element.addEventListener('click', markUnComplete) }) +// create an async function to delete items async function deleteItem(){ + // access the parent node of a clicked icon, find its second child's inner text const itemText = this.parentNode.childNodes[1].innerText try{ + // attempt to send request to server to delete the item, 'deleteItem' is the endpoint used in this case const response = await fetch('deleteItem', { + // notify server which method is supposed to be used method: 'delete', + // tell the server to type of content to expect headers: {'Content-Type': 'application/json'}, + // create an object whose property and value can be accessed by the server, and format it into json body: JSON.stringify({ 'itemFromJS': itemText }) }) + // take the data the server responded with and format is as an object const data = await response.json() + // log the data so we can see what exactly is sent back in the console console.log(data) + // reload the current location so the most up to date content is shown location.reload() + // if the server is not accepting or properly returning the requested info, tell us why }catch(err){ console.log(err) } } +// create an async function to mark tasks as completed async function markComplete(){ + // find the parent node of the content that was clicked, get the text content of its second child const itemText = this.parentNode.childNodes[1].innerText + // attempt to send request to server to update the data try{ + // create a variable to ensure the request is properly received, 'markComplete' tells the server which endpoint to use const response = await fetch('markComplete', { + // notify the server which method is supposed to be used method: 'put', + // let the server know what type of content to expect headers: {'Content-Type': 'application/json'}, + // create an object and format as json it so the server can use it body: JSON.stringify({ 'itemFromJS': itemText }) }) + // convert the server's response into json format const data = await response.json() + // log the data received console.log(data) + // reload the location with the most up to date data location.reload() + // if there was any error during this process, log the error }catch(err){ console.log(err) } } +// make an async function to mark completed tasks as incomplete async function markUnComplete(){ + // find the parent node of the clicked content, get the inner text of its second child const itemText = this.parentNode.childNodes[1].innerText + // attempt to send request to server to update data try{ + // set variable to make request, 'markUnComplete' tells the server which endpoint to use const response = await fetch('markUnComplete', { + // tell the server which method to use method: 'put', + // tell the server which type of content to expect headers: {'Content-Type': 'application/json'}, + // create an object with the text and format it with json so the server can use it body: JSON.stringify({ 'itemFromJS': itemText }) }) + // convert the server's response into json format const data = await response.json() + // log the data received from the server console.log(data) + // reload the location location.reload() + // if there were any issues during the update process, log the issue }catch(err){ console.log(err) } diff --git a/server.js b/server.js index 58b53e2f..269122e2 100644 --- a/server.js +++ b/server.js @@ -1,30 +1,51 @@ +// import express and mongo (after npm install express, mongodb) and create an instance of MongoClient const express = require('express') const app = express() const MongoClient = require('mongodb').MongoClient +// set up port const PORT = 2121 +// install dotenv to secure information (place in gitignore) require('dotenv').config() - +// declare the db let db, + // receive the connection string (obtained from mongo db) that is stored in the (gitignored) env dbConnectionStr = process.env.DB_STRING, + // name your app after the app name in Mongo Atlas dbName = 'todo' +// connect using the connection string and an object and add Unified Topology for UX (tho now standard) MongoClient.connect(dbConnectionStr, { useUnifiedTopology: true }) + // take the return from the connect method's Promise .then(client => { + // let us know we're connected to the database console.log(`Connected to ${dbName} Database`) + // set the pre-declared, global db to the clients proper database db = client.db(dbName) }) - + +// enable use of ejs templating language (npm install ejs --save) app.set('view engine', 'ejs') +// tell our instance of express to be consistently viewing the public folder app.use(express.static('public')) +// make sure the instance of express can handle html form submissions app.use(express.urlencoded({ extended: true })) +// make sure our instance of express can handle the json format app.use(express.json()) - +// upon loading the main page '/', call an async function to retrieve information app.get('/',async (request, response)=>{ + // go into our database's collection thats named 'todos', everything within it, and put it in an array const todoItems = await db.collection('todos').find().toArray() + // access the same collection, get a total number of documents that have a property of completed that = false const itemsLeft = await db.collection('todos').countDocuments({completed: false}) + // send the response to the browser as an object - with properties item = the array from above, and left = number of incomplete response.render('index.ejs', { items: todoItems, left: itemsLeft }) + + + + // this performs the same as above for the itemsLeft, but does it in the form of Promise chaining. items: data -> items: todoItems + // db.collection('todos').find().toArray() // .then(data => { // db.collection('todos').countDocuments({completed: false}) @@ -35,59 +56,95 @@ app.get('/',async (request, response)=>{ // .catch(error => console.error(error)) }) +// when our instance of express hears a POST request thats made with the /addTodo action property app.post('/addTodo', (request, response) => { + // find our collection called todos and insert an object with properties thing = the request body's todoItem and completed = false db.collection('todos').insertOne({thing: request.body.todoItem, completed: false}) + // if a message is sent from the database after the insertion, obtain the 'result' of that message .then(result => { + // ignore the resulting message and log in real language what occurred console.log('Todo Added') + // redirect to the main page response.redirect('/') }) + // if an error occurred at any point, log it .catch(error => console.error(error)) }) +// set up our instance of express to receive a PUT request thats made with the /markComplete action app.put('/markComplete', (request, response) => { + // go to our db's todo collection and update one object that has the thing property that matches the request body's itemFromJS db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + // let mongo db know were planning to set (change) something on that object $set: { + // change its completed property to true completed: true } },{ + // sort the objects in descending order (newest to oldest) sort: {_id: -1}, + // don't add another task if the one searched for isnt found upsert: false }) + // if mongo sends a response to that process, get it .then(result => { + // ignore result + // log real language completion of task console.log('Marked Complete') + // send the plain english response as a json response.json('Marked Complete') }) + // if theres an error, tell us whats happening .catch(error => console.error(error)) }) +// set up the instance of express to receive a PUT requests made to the /markUnComplete endpoint app.put('/markUnComplete', (request, response) => { + // see above, access collection and update and object with same prop values db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + // were setting / changing something $set: { + // its fake news now completed: false } },{ + // sort newest to oldest sort: {_id: -1}, + // dont repeat upsert: false }) + // get return from mongo .then(result => { + // ignore that return + // log to server console.log('Marked Complete') + // send to browser response.json('Marked Complete') }) + // what went wrong? .catch(error => console.error(error)) }) +// set up the server to handle DELETE requests made with the /deleteItem endpoint app.delete('/deleteItem', (request, response) => { + // access the db collection named todo and delete an item in it that has the thing property equal to the request body's itemFromJS db.collection('todos').deleteOne({thing: request.body.itemFromJS}) + // get mongos return as a result .then(result => { + // ignore it + // log to the server that it was deleted console.log('Todo Deleted') + // tell the browser it was deleted response.json('Todo Deleted') }) + // tells us what went wrong if something did .catch(error => console.error(error)) }) +// tell the server to listen to the environment's port, or if its unavailable (only hosted locally) to listen at our pre-established local port app.listen(process.env.PORT || PORT, ()=>{ 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..deaf87d3 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -18,30 +18,46 @@