From f9abb00bce290863e23f833f93936b178c092d9f Mon Sep 17 00:00:00 2001 From: "Glitch (a2-lfleming-314)" Date: Fri, 5 Feb 2021 07:35:29 +0000 Subject: [PATCH] Imported from Glitch --- README.md | 105 ++++--------------- package.json | 2 +- public/css/style.css | 107 ++++++++++++++++++- public/index.html | 93 ++++++++++------- public/js/scripts.js | 237 ++++++++++++++++++++++++++++++++++++++++++- server.improved.js | 118 +++++++++++++++------ 6 files changed, 506 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 3945c152..3b3222c3 100755 --- a/README.md +++ b/README.md @@ -1,93 +1,24 @@ -Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js -=== +## Readme -Due: September 16th, by 11:59 PM. +## Title: **Shopping List & Calculator** -This assignment aims to introduce you to the concepts and practice involved in creating a prototype (i.e. not deployment ready) two-tiered web application. The baseline aims of this assignment involve creating an application that demonstrates the use of several specific pieces of HTML, CSS, JavaScript, and Node.js functionality. +https://a2-lfleming-314.glitch.me/ -Baseline Requirements ---- +## Description +This project allows the user to make a shopping list and see the total price of their items. +## Instructions +- To add an item, type in the item's name, unit price (price per item, per pound, etc.), and quantity into the input fields. Then click the `Add` button. The item will then appear in the table. +- To edit an item in the list table, click the `Edit` button next to it. The item's name, unit price, and quantity will be copied to the input fields. Change those as desired and then click the `Save` button to update the table. +- To delete an item from the list table, click the `Delete` button next to it. +## Derived Fields +- The server logic creates two derived fields, `Subtotal` and `Tax`. `Subtotal` is calculated by multiplying the unit price by the quantity. `Tax` is calculated by multiplying the subtotal by the Massachusetts tax rate of 0.065. +- The server logic also adds a third field, `ID`, which is a randomly generated identifier used by the program to keep track of items. This is technically not a derived field as it does not depend on any of the input fields. +## CSS +- Flexbox is used to position the items. +- There are no default fonts. The 3 fonts used (Arvo, Rock Salt, and Caveat) are imported from Google Fonts. +- The styling uses an analogous color scheme of pink (#9F17EB), purple (#5430F5), dark blue (#003ADB), bright blue (#3BBFF5), and green (#0CE8C4). -Note that there is a very large range of application areas and possibilities that meet these baseline requirements. Make your application do something useful! A todo list, storing / retrieving high scores for a very simple game, have a little fun with it. +## **Technical Achievements** -Your application is required to implement the following functionalities: +- **Tech Achievement 1**: When the client submits data (either by adding, editing, or deleting items), the server sends back the updated data and the client-side display updates. Thus the client-side display always reflects the current state of the server-side data. -- 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, modify, 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 `
+ + +
+ +
+ + + + + + + + + + + + + +
Subtotal
Tax
Total
+
+ + diff --git a/public/js/scripts.js b/public/js/scripts.js index de052eae..15647ebc 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,3 +1,236 @@ -// Add some Javascript code here, to run on the front end. +//populates the table as soon as the page loads +window.addEventListener('load', function() { + //fill in table with items from server + populateTable() -console.log("Welcome to assignment 2!") \ No newline at end of file + //event listener for the "add item" button + document.getElementById("addButton").addEventListener('click', function() { + addItem() + }) +}) + +//adds inputted item to the server database and fills in the table +const addItem = function() { + //define variables for the input fields + var itemnameField = document.getElementById("itemname"); + var unitpriceField = document.getElementById("unitprice"); + var quantityField = document.getElementById("quantity"); + + //get data from input fields + var itemname = itemnameField.value; + var unitprice = floatify(unitpriceField.value); + var quantity = floatify(quantityField.value); + + //send the input data to the server + var itemJSON = {itemname: itemname, unitprice: unitprice, quantity: quantity}; + var body = JSON.stringify(itemJSON); + fetch("/addItem", { + method: "POST", + body, + headers: { + 'Content-Type': 'application/json' + } + }) + + //clear fields + itemnameField.value = ""; + unitpriceField.value = ""; + quantityField.value = ""; + + //fill in the list table again now that there is a new item + populateTable(); +} + +//parses string into a float (or returns 0 if it is not a number) +const floatify = function(inputString) { + var numVal = parseFloat(inputString); + if (inputString.length == 0 || numVal == NaN) { + numVal = 0; + } + return numVal; +} + +//function to make the table with list items +const populateTable = function() { + //get all the list items from the server + fetch("/fetchItems", { + method: "GET", + headers: { + 'Content-Type': 'application/json' + } + }).then(response => response.json()).then(json => { + //put data into a 2d array suitable for printing into the table + var listItems = json; + var tableData = []; + var totalSubtotal = 0; + var totalTax = 0; + + for (var i = 0; i < listItems.length; i++) { + var currentItem = listItems[i]; + tableData.push([currentItem.itemname, currentItem.unitprice, currentItem.quantity, + currentItem.subtotal, currentItem.tax, currentItem.id]); + totalSubtotal += currentItem.subtotal; + totalTax += currentItem.tax; + } + + //print the data onto the webpage + printTable(tableData); + printTotals(totalSubtotal, totalTax); + }) +} + +//helper function to print the table onto the webpage +const printTable = function(tableData) { + const listTable = document.querySelector("#listtable"); + + //clear table in case it has already been built + listTable.innerHTML = ""; + + //build header row + var headerRow = listTable.insertRow(0); + addHCell(headerRow, "Item Name"); + addHCell(headerRow, "Unit Price"); + addHCell(headerRow, "Quantity"); + addHCell(headerRow, "Subtotal"); + addHCell(headerRow, "Tax"); + addHCell(headerRow, ""); + addHCell(headerRow, ""); + + //fill in item rows + for (var i = 0; i < tableData.length; i++) { + var currentItem = tableData[i]; + var currentRow = listTable.insertRow(); + currentRow.id = currentItem[5]; + var currentRowIndex = currentRow.rowIndex; + addCell(currentRow, currentItem[0]); + addCell(currentRow, dollarFormat(currentItem[1])); + addCell(currentRow, currentItem[2]); + addCell(currentRow, dollarFormat(currentItem[3])); + addCell(currentRow, dollarFormat(currentItem[4])); + addCell(currentRow, editButton(currentItem[0], currentItem[1], currentItem[2], currentItem[5])); + addCell(currentRow, deleteButton(currentItem[0], currentItem[5])); //[0] is name, [5] is id + } +} + +//adds a table cell to a row with specified contents +const addCell = function(row, contents) { + row.insertCell().innerHTML = contents; +} + +//adds a table cell with the tHeader class to a row with specified contents +const addHCell = function(row, contents) { + var hcell = row.insertCell(); + hcell.classList.add("tHeader"); + hcell.innerHTML = contents; +} + +//formats dollar values with $ sign and 2 decimal places +const dollarFormat = function(cost) { + return "$" + cost.toFixed(2); +} + +//creates the "edit" button for a table row +const editButton = function(name, unitprice, quantity, id) { + var argString = name + "\', \'" + unitprice + "\', \'" + quantity + "\', \'" + id + "\'"; + var clickString = "onclick=\"edit(\'" + argString + ")\""; + return ""; +} + +//runs when edit button is clicked +const edit = function(name, unitprice, quantity, id) { + //hide "add item" button + document.getElementById("addButton").classList.add("hidden"); + + //reveal "save" button and attach onclick function + var saveButton = document.getElementById("saveButton"); + saveButton.classList.remove("hidden"); + saveButton.onclick = function() { save(name, id)}; + + //fill in form fields with data from item being edited + document.getElementById("itemname").value = name; + document.getElementById("unitprice").value = floatify(unitprice).toFixed(2); + document.getElementById("quantity").value = quantity; +} + +// +const save = function(previousName, id) { + //define variables for the input fields + var itemnameField = document.getElementById("itemname"); + var unitpriceField = document.getElementById("unitprice"); + var quantityField = document.getElementById("quantity"); + + //get edited data from input fields + var newName = itemnameField.value; + var newUnitprice = floatify(unitpriceField.value); + var newQuantity = floatify(quantityField.value); + + //send POST request to server with item to update + var itemJSON = {previousName: previousName, id: id, name: newName, unitprice: newUnitprice, quantity: newQuantity}; + var body = JSON.stringify(itemJSON); + fetch("/updateItem", { + method: "POST", + body, + headers: { + 'Content-Type': 'application/json' + } + }) + + //clear fields + itemnameField.value = ""; + unitpriceField.value = ""; + quantityField.value = ""; + + //reveal "add item" button + document.getElementById("addButton").classList.remove("hidden"); + + //hide "save" button + document.getElementById("saveButton").classList.add("hidden"); + + //regenerate the list table + populateTable(); +} + +//creates the "delete" button for a table row +const deleteButton = function(itemName, itemId) { + var htmlPt1 = ""; + return htmlPt1 + htmlPt2; +} + +//runs when a deleteButton is clicked +const deleteItem = function(itemName, itemId) { + //delete item in the server + deleteFromServer(itemName, itemId); + + //re-make table + populateTable(); +} + +//delete specified item from server +const deleteFromServer = function(itemName, itemId) { + //send post request to server with name and id of item to delete + var itemJSON = {itemname: itemName, id: itemId}; + var body = JSON.stringify(itemJSON); + fetch("/deleteItem", { + method: "POST", + body, + headers: { + 'Content-Type': 'application/json' + } + }) +} + +//print the subtotal, tax, and total price for the whole list +const printTotals = function(subtotal, tax) { + //set variables for html elements + var subCell = document.getElementById("totalSubtotal"); + var taxCell = document.getElementById("totalTax"); + var totCell = document.getElementById("totalTotal"); + + var totalPrice = subtotal + tax; + + //print totals to table on webpage + subCell.innerHTML = dollarFormat(subtotal); + taxCell.innerHTML = dollarFormat(tax); + totCell.innerHTML = dollarFormat(totalPrice); +} \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index 26673fc0..cdff22e6 100644 --- a/server.improved.js +++ b/server.improved.js @@ -1,3 +1,4 @@ +//require dependencies const http = require( 'http' ), fs = require( 'fs' ), // IMPORTANT: you must run `npm install` in the directory for this assignment @@ -6,12 +7,10 @@ const http = require( 'http' ), dir = 'public/', port = 3000 -const appdata = [ - { 'model': 'toyota', 'year': 1999, 'mpg': 23 }, - { 'model': 'honda', 'year': 2004, 'mpg': 30 }, - { 'model': 'ford', 'year': 1987, 'mpg': 14} -] +//initialize list to store json items +var appdata = [] +//create the server, handle incoming requests const server = http.createServer( function( request,response ) { if( request.method === 'GET' ) { handleGet( request, response ) @@ -20,53 +19,110 @@ const server = http.createServer( function( request,response ) { } }) +//handle GET requests const handleGet = function( request, response ) { const filename = dir + request.url.slice( 1 ) - - if( request.url === '/' ) { + if( request.url === '/' ) { //open homepage sendFile( response, 'public/index.html' ) + } else if (request.url === '/fetchItems') { //get the list items from server + response.setHeader('Content-Type', 'application/json') + response.write(JSON.stringify(appdata)) + response.end() }else{ sendFile( response, filename ) } } -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() - }) -} - +//send an html file 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 ) +//handle POST requests +const handlePost = function( request, response ) { + //recieve the data + let dataString = '' + request.on('data', function(data) { + dataString += data; + }) + //parse data into JSON + request.on('end', function() { + var postItem = JSON.parse(dataString); + + //handle different types of POST requests + if (request.url === '/addItem') { //add an item to the server + //server logic for derived fields + var newSubtotal = calculateSubtotal(postItem.unitprice, postItem.quantity); + postItem.subtotal = newSubtotal; + postItem.tax = calculateTax(newSubtotal); + postItem.id = createRandomId(); + appdata.push(postItem); + } else if (request.url === '/updateItem') { + updateItem(postItem.previousName, postItem.id, postItem.name, postItem.unitprice, postItem.quantity); + } else if (request.url === '/deleteItem') { + deleteItem(postItem.itemname, postItem.id); + } + }) + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.end() +} + +//subtotal = unitprice * quantity +function calculateSubtotal(unitprice, quantity) { + return unitprice * quantity; +} + +//tax = subtotal * 0.0625 (using MA tax rate) +function calculateTax(subtotal) { + return subtotal * 0.0625; +} + +//id is a randomized string to distinguish potential duplicate elements +function createRandomId() { + var idLength = 10; + var id = ""; + var chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var charactersLength = chars.length; + for (var i = 0; i < idLength; i++) { + id += chars.charAt(Math.floor(Math.random() * charactersLength)); + } + return id; +} + +//update specific item in appdata with edited fields +const updateItem = function(prevName, id, newName, unitprice, quantity) { + for (var i=0; i