From 82903d7665dc8246fcf48fe9334dc6382133d994 Mon Sep 17 00:00:00 2001 From: ezrabarb Date: Mon, 14 Oct 2024 17:57:26 -0400 Subject: [PATCH 1/2] Upload to github --- .gitignore | 3 + .glitch-assets | 3 + README.md | 111 ++++++------------------------ package.json | 28 +++++--- public/css/main.css | 4 -- public/css/style.css | 103 ++++++++++++++++++++++++++++ public/index.html | 105 +++++++++++++++++++++++++--- public/js/main.js | 27 -------- public/js/scripts.js | 160 +++++++++++++++++++++++++++++++++++++++++++ server.js | 127 ++++++++++++++++------------------ 10 files changed, 460 insertions(+), 211 deletions(-) create mode 100644 .gitignore create mode 100644 .glitch-assets delete mode 100644 public/css/main.css create mode 100644 public/css/style.css delete mode 100644 public/js/main.js create mode 100644 public/js/scripts.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f0098668 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.DS_Store +node_modules/ +package-lock.json \ No newline at end of file diff --git a/.glitch-assets b/.glitch-assets new file mode 100644 index 00000000..0f95134b --- /dev/null +++ b/.glitch-assets @@ -0,0 +1,3 @@ +{"name":"showpic.png","date":"2019-09-08T19:25:11.108Z","url":"https://www.514blog.ca/wp-content/uploads/2020/03/1_dBJMknulIZSAC36tTmanVA.jpeg","type":"image/png","size":29741,"imageWidth":320,"imageHeight":320,"thumbnail":"https://cdn.glitch.com/4f0fe7ab-196a-4c13-9b89-b60fd2312715%2Fbookpic.png","thumbnailWidth":320,"thumbnailHeight":320,"uuid":"Kjjp8ctJmV6WIBj2"} +{"uuid":"Kjjp8ctJmV6WIBj2","deleted":true} +{"name":"showpic2.jpg","date":"2019-09-08T19:27:31.906Z","url":"https://www.514blog.ca/wp-content/uploads/2020/03/1_dBJMknulIZSAC36tTmanVA.jpeg","thumbnailWidth":330,"thumbnailHeight":207,"uuid":"LsI6dViHEIvjQBNw"} \ No newline at end of file diff --git a/README.md b/README.md index 4471f667..35331b2e 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,31 @@ -Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js +Assignment 2 - Short Stack === + +Ezra Barboza -Due: September 9th, by 11:59 AM. +## Show Tracker -This assignment will introduce you to creating a prototype two-tiered web application. -Your application will include the use of HTML, CSS, JavaScript, and Node.js functionality, with active communication between the client and the server. +I love watching TV shows but often find myself forgetting the show recommendations that people tell me, or not being able to provide suggestions on the spot. +This project is an organizational tool to keep track of all the shows you have watched and want to watch, showing not only the name of the shows and directors, but also a comments section and ratings. The tracker further sorts the shows into sections based on whether you liked them or not. -Baseline Requirements ---- +While this is a personal organizational tool at this time, in the future I could envision it as a forum for TV show enthusiasts to share their best/worst show picks. -There are a range of application areas and possibilities that meet these baseline requirements. -Try to make your application do something useful! A todo list, storing / retrieving high scores for a very simple game... have a little fun with it. +Note: Almost all the time, clicking the buttons will automatically update the tables (I have my `loadData` function for the tables called in all of the `onclick` functions), but sometimes if the website has been sitting open for a while, you need to refresh the page if a button was pressed but the tables aren't automatically updating. -Your application is required to implement the following functionalities: - -- a `Server` which not only serves files, but also maintains a tabular dataset with 3 or more fields related to your application -- a `Results` functionality which shows the entire dataset residing in the server's memory -- a `Form/Entry` functionality which allows a user to add or delete data items residing in the server's memory -- a `Server Logic` which, upon receiving new or modified "incoming" data, includes and uses a function that adds at least one additional derived field to this incoming data before integrating it with the existing dataset -- the `Derived field` for a new row of data must be computed based on fields already existing in the row. -For example, a `todo` dataset with `task`, `priority`, and `creation_date` may generate a new field `deadline` by looking at `creation_date` and `priority` - -Your application is required to demonstrate the use of the following concepts: - -HTML: -- One or more [HTML Forms](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms), with any combination of form tags appropriate for the user input portion of the application -- A results page displaying all data currently available on the server. You will most likely use a `` tag for this, but `
+ + + + + +
NameDirectorCommentsRating
+ + +

Shows You Liked

+
+ + + + + + +
NameDirectorCommentsRating
+
+ +

Shows You Didn't Like

+
+ + + + + + +
NameDirectorCommentsRating
+
+ + + +
+
+ + Oops, Made A Mistake? + + Update Show:
+
+
+ Name of Show: + +
+
+ New Rating: + +
+
+
+
+ +
+ +
Show Name to Delete:
+
+
+ +
+
+
- + + \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js deleted file mode 100644 index a569258f..00000000 --- a/public/js/main.js +++ /dev/null @@ -1,27 +0,0 @@ -// FRONT-END (CLIENT) JAVASCRIPT HERE - -const submit = async function( event ) { - // stop form submission from trying to load - // a new .html page for displaying results... - // this was the original browser behavior and still - // remains to this day - event.preventDefault() - - const input = document.querySelector( '#yourname' ), - json = { yourname: input.value }, - body = JSON.stringify( json ) - - const response = await fetch( '/submit', { - method:'POST', - body - }) - - const text = await response.text() - - console.log( 'text:', text ) -} - -window.onload = function() { - const button = document.querySelector("button"); - button.onclick = submit; -} \ No newline at end of file diff --git a/public/js/scripts.js b/public/js/scripts.js new file mode 100644 index 00000000..9a720ae1 --- /dev/null +++ b/public/js/scripts.js @@ -0,0 +1,160 @@ +// Display functions for each table +const displayShows = function(shdata) { + const template = '{name}{director}{comments}{rating}'; // Fixed closing tag + const row = template.replace("{name}", shdata.showName) + .replace("{director}", shdata.directorName) + .replace("{comments}", shdata.comments) + .replace("{rating}", shdata.rating); + const tbody = document.querySelector("#all-shows"); + tbody.innerHTML += row; +}; + +const displayGoodShows = function(shdata) { + const template = '{name}{director}{comments}{rating}'; // Fixed closing tag + const row = template.replace("{name}", shdata.showName) + .replace("{director}", shdata.directorName) + .replace("{comments}", shdata.comments) + .replace("{rating}", shdata.rating); + const tbody = document.querySelector("#good-shows"); + tbody.innerHTML += row; +}; + +const displayBadShows = function(shdata) { + const template = '{name}{director}{comments}{rating}'; // Fixed closing tag + const row = template.replace("{name}", shdata.showName) + .replace("{director}", shdata.directorName) + .replace("{comments}", shdata.comments) + .replace("{rating}", shdata.rating); + const tbody = document.querySelector("#bad-shows"); + tbody.innerHTML += row; +}; + +// Display data for the three different tables +const displayData = function(data) { + document.querySelector("#all-shows").innerHTML = ""; + document.querySelector("#good-shows").innerHTML = ""; + document.querySelector("#bad-shows").innerHTML = ""; + + for (let i = 0; i < data.length; i++) { + const shdata = data[i]; + displayShows(shdata); + if (shdata.rating === "1" || shdata.rating === "2") { + displayBadShows(shdata); + } + if (shdata.rating === "3" || shdata.rating === "4" || shdata.rating === "5") { + displayGoodShows(shdata); + } + } +}; + +// Fetch the appdata and then call the display functions +const loadData = function() { + fetch('/shows') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(displayData) + .catch(error => console.error('Fetch error:', error)); // Error handling +}; + +// Add Show function +const addShow = function(e) { + e.preventDefault(); // Prevent default form submission + + const newShow = { + showName: document.getElementById('showName').value, + directorName: document.getElementById('directorName').value, + comments: document.getElementById('comments').value, + rating: document.getElementById('rating').value, + status: 'none' + }; + + newShow.status = (newShow.rating === "1" || newShow.rating === "2") ? 'bad' : 'good'; + + fetch('/addShow', { + method: 'POST', + body: JSON.stringify(newShow), + headers: { + 'Content-Type': 'application/json' // Specify the content type + } + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + resetOrderForm(); + loadData(); + }) + .catch(error => console.error('Fetch error:', error)); // Error handling +}; + +// Delete show based on name +const delShow = function(e) { + e.preventDefault(); // Prevent default form submission + + const body = JSON.stringify({ showName: document.getElementById('delShowName').value }); // Send showName in JSON + fetch('/delShow', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json' // Specify the content type + } + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + resetOrderForm(); + loadData(); + }) + .catch(error => console.error('Fetch error:', error)); // Error handling +}; + +// Edit show based on name and new rating +const editShow = function(e) { + e.preventDefault(); // Prevent default form submission + + const newShow2 = { + showName: document.getElementById('editShowName').value, + rating: document.getElementById('editShowRating').value, + }; + + fetch('/editShow', { + method: 'POST', + body: JSON.stringify(newShow2), + headers: { + 'Content-Type': 'application/json' // Specify the content type + } + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + resetOrderForm(); + loadData(); + }) + .catch(error => console.error('Fetch error:', error)); // Error handling +}; + +// Reset order form after adding show +const resetOrderForm = () => { + document.getElementById('showName').value = ''; + document.getElementById('directorName').value = ''; + document.getElementById('comments').value = ''; + document.getElementById('rating').value = ''; + document.getElementById('delShowName').value = ''; + document.getElementById('editShowName').value = ''; + document.getElementById('editShowRating').value = ''; +}; + +// Window onload function +window.onload = function() { + document.getElementById('submit-btn').addEventListener('click', addShow); // Use addEventListener + document.getElementById('del-btn').addEventListener('click', delShow); // Use addEventListener + document.getElementById('edit-btn').addEventListener('click', editShow); // Use addEventListener + + loadData(); +}; diff --git a/server.js b/server.js index 9ac27fb8..e13a1cd7 100644 --- a/server.js +++ b/server.js @@ -1,74 +1,63 @@ -const http = require( 'http' ), - fs = require( 'fs' ), - // IMPORTANT: you must run `npm install` in the directory for this assignment - // to install the mime library if you're testing this on your local machine. - // However, Glitch will install it automatically by looking in your package.json - // file. - mime = require( 'mime' ), - dir = 'public/', - port = 3000 +const express = require('express'); +const path = require('path'); +const app = express(); +const port = 3000; const appdata = [ - { 'model': 'toyota', 'year': 1999, 'mpg': 23 }, - { 'model': 'honda', 'year': 2004, 'mpg': 30 }, - { 'model': 'ford', 'year': 1987, 'mpg': 14} -] - -const server = http.createServer( function( request,response ) { - if( request.method === 'GET' ) { - handleGet( request, response ) - }else if( request.method === 'POST' ){ - handlePost( request, response ) + { 'showName': 'Breaking Bad', 'directorName': 'Vince Gilligan', 'comments': 'An incredible journey of transformation and moral dilemmas.', 'rating': '5', 'status': 'excellent' }, + { 'showName': 'Law and Order: SVU', 'directorName': 'Dick Wolf', 'comments': 'A compelling procedural that tackles tough issues.', 'rating': '4', 'status': 'good' }, + { 'showName': 'Avatar: The Last Airbender', 'directorName': 'Michael Dante DiMartino', 'comments': 'A excellent story but its ending was anticlimactic', 'rating': '2', 'status': 'bad' } +]; + +// Serve static files from the React app +app.use(express.static(path.join(__dirname, 'public'))); + +// Middleware to parse JSON request bodies +app.use(express.json()); + +// GET route to serve the React app +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'index.html')); +}); + +// API route to get shows data +app.get('/shows', (req, res) => { + res.json(appdata); +}); + +// POST route to add a new show +app.post('/addShow', (req, res) => { + const newShow = req.body; + appdata.push(newShow); + res.status(200).json({ message: 'Show added successfully', newShow }); +}); + +// POST route to delete a show +app.post('/delShow', (req, res) => { + const showName = req.body.showName; + const index = appdata.findIndex(show => show.showName === showName); + if (index !== -1) { + appdata.splice(index, 1); + res.status(200).json({ message: 'Show deleted successfully' }); + } else { + res.status(404).json({ message: 'Show not found' }); } -}) - -const handleGet = function( request, response ) { - const filename = dir + request.url.slice( 1 ) - - if( request.url === '/' ) { - sendFile( response, 'public/index.html' ) - }else{ - sendFile( response, filename ) +}); + +// POST route to edit a show's rating and status +app.post('/editShow', (req, res) => { + const { showName, rating } = req.body; + const show = appdata.find(b => b.showName === showName); + if (show) { + show.rating = rating; + show.status = (rating === '1' || rating === '2') ? 'bad' : 'good'; + res.status(200).json({ message: 'Show updated successfully', show }); + } else { + res.status(404).json({ message: 'Show not found' }); } -} - -const handlePost = function( request, response ) { - let dataString = '' - - request.on( 'data', function( data ) { - dataString += data - }) - - request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) - - // ... do something with the data here!!! - - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) - response.end('test') - }) -} - -const sendFile = function( response, filename ) { - const type = mime.getType( filename ) - - fs.readFile( filename, function( err, content ) { - - // if the error = null, then we've loaded the file successfully - if( err === null ) { - - // status code: https://httpstatuses.com - response.writeHeader( 200, { 'Content-Type': type }) - response.end( content ) - - }else{ - - // file not found, error code 404 - response.writeHeader( 404 ) - response.end( '404 Error: File Not Found' ) - - } - }) -} +}); -server.listen( process.env.PORT || port ) +// Start the server +app.listen(port, () => { + console.log(`Server is running on http://localhost:${port}`); +}); From ea502390e6dc21ac91b3d89a29122995d35ce0cb Mon Sep 17 00:00:00 2001 From: ezrabarb Date: Mon, 4 Nov 2024 02:53:43 -0500 Subject: [PATCH 2/2] slight fix --- public/css/style.css | 6 +++--- server.js | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 91a4f266..47e3d307 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,7 +1,7 @@ html, body { height: 100%; margin: 0; - font-family: 'Roboto', sans-serif; /* Use a cleaner, modern font */ + font-family: 'Roboto', sans-serif; } body { @@ -10,7 +10,7 @@ body { #big-title { font-size: 40px; - font-family: 'Orbitron', monospace; /* A futuristic monospace font */ + font-family: 'Orbitron', monospace; font-weight: bold; color: #FFFAE3; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.6); @@ -21,7 +21,7 @@ body { .content { max-width: 1000px; margin: 40px auto; - background: rgba(130, 212, 206, 0.9); /* Add transparency for a modern effect */ + background: rgba(130, 212, 206, 0.9); /* Add transparency */ border-radius: 12px; padding: 20px; text-align: center; diff --git a/server.js b/server.js index e13a1cd7..4a6e7a92 100644 --- a/server.js +++ b/server.js @@ -9,13 +9,11 @@ const appdata = [ { 'showName': 'Avatar: The Last Airbender', 'directorName': 'Michael Dante DiMartino', 'comments': 'A excellent story but its ending was anticlimactic', 'rating': '2', 'status': 'bad' } ]; -// Serve static files from the React app +// Serve static files app.use(express.static(path.join(__dirname, 'public'))); -// Middleware to parse JSON request bodies app.use(express.json()); -// GET route to serve the React app app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); });