From 4c1432e33cd74a61390e044bb59ce541197a0da1 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Tue, 8 Sep 2020 21:07:09 -0400 Subject: [PATCH 01/21] basic ui, and dummy http post request are made to server --- package.json | 19 +++++++-- public/css/style.css | 26 +++++++++++- public/index.html | 97 ++++++++++++++++++++++++++++---------------- public/js/scripts.js | 80 +++++++++++++++++++++++++++++++++++- server.improved.js | 53 +++++++++++++++++++----- 5 files changed, 224 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 988f135f..952daef4 100755 --- a/package.json +++ b/package.json @@ -1,12 +1,23 @@ { - "name": "", - "version": "", - "description": "", + "name": "a2-shortstack", + "description": "Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js \r ===", "author": "", "scripts": { "start": "node server.improved.js" }, "dependencies": { "mime": "^2.4.4" - } + }, + "version": "1.0.0", + "main": "server.improved.js", + "repository": { + "type": "git", + "url": "git+https://github.com/JoeSwetz/a2-shortstack.git" + }, + "keywords": [], + "license": "ISC", + "bugs": { + "url": "https://github.com/JoeSwetz/a2-shortstack/issues" + }, + "homepage": "https://github.com/JoeSwetz/a2-shortstack#readme" } diff --git a/public/css/style.css b/public/css/style.css index d5f842ab..210ab4b9 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -1 +1,25 @@ -/*Style your own assignment! This is fun! */ \ No newline at end of file +/*Style your own assignment! This is fun! */ + +h1 { + text-align: center; +} + +.appgrid{ + position: relative; + border: 1px solid red; + width: 800px; + left: 50%; + transform: translate(-50%, 0%); + display: grid; + grid-row-start: 1; + grid-column-start: 1; + grid-template: 1fr 1fr 1fr / 1fr 3fr; +} + +.app-item{ + border: 1px solid black; +} + +form { + width: 230px; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index c56d620e..834c004a 100755 --- a/public/index.html +++ b/public/index.html @@ -1,41 +1,70 @@ - - CS4241 Assignment 2 - - - -
- - -
- - + + +

FPS Stat Calculator

+
+ +
+

Add new stats

+ + +
+ + +
+ + + + +
- const submit = function( e ) { - // prevent default form action from being carried out - e.preventDefault() + + + + + + + + + + +
ID #KillsAssistsDeathsK/D RatioA/D Ratio
- const input = document.querySelector( '#yourname' ), - json = { yourname: input.value }, - body = JSON.stringify( json ) + +
+

Modify row of stats

+ + +
+ + +
+ + +
+ + + +
- fetch( '/submit', { - method:'POST', - body - }) - .then( function( response ) { - // do something with the reponse - console.log( response ) - }) +
- return false - } - - window.onload = function() { - const button = document.querySelector( 'button' ) - button.onclick = submit - } - - + +
+

Delete row of stats

+ + + +
+
+ diff --git a/public/js/scripts.js b/public/js/scripts.js index de052eae..d0c1d140 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,3 +1,81 @@ // Add some Javascript code here, to run on the front end. -console.log("Welcome to assignment 2!") \ No newline at end of file +console.log("Welcome to assignment 2!") + +function handle_add(){ + console.log("add!"); + // prevent default form action from being carried out + //e.preventDefault(); + //The following source showed me how to extract values from a + //form: https://www.w3schools.com/jsref/coll_form_elements.asp + const input = document.getElementById("add"), + json = { + kills: input.elements[0].value, + assists: input.elements[1].value, + deaths: input.elements[2].value, + }, + body = JSON.stringify(json); + + fetch( '/add', { + method:'POST', + body + }).then( function( response ) { + // do something with the response + console.log( response ); + return true; + }) + + return false; +} + +function handle_modify(){ + console.log("modify!"); + console.log("add!"); + // prevent default form action from being carried out + //e.preventDefault(); + //The following source showed me how to extract values from a + //form: https://www.w3schools.com/jsref/coll_form_elements.asp + const input = document.getElementById("modify"), + json = { + id_num: input.elements[0].value, + kills: input.elements[1].value, + assists: input.elements[2].value, + deaths: input.elements[3].value, + }, + body = JSON.stringify(json); + + fetch( '/modify', { + method:'POST', + body + }).then( function( response ) { + // do something with the response + console.log( response ); + return true; + }) + + return false; +} + +function handle_delete(){ + console.log("delete!"); + // prevent default form action from being carried out + //e.preventDefault(); + //The following source showed me how to extract values from a + //form: https://www.w3schools.com/jsref/coll_form_elements.asp + const input = document.getElementById("delete"), + json = { + id_num: input.elements[0].value + }, + body = JSON.stringify(json); + + fetch( '/delete', { + method:'POST', + body + }).then( function( response ) { + // do something with the response + console.log( response ); + return true; + }) + + return false; +} \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index 26673fc0..5828b0a9 100644 --- a/server.improved.js +++ b/server.improved.js @@ -6,11 +6,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} -] +//Format: { "id": 0, "kills": 0, "assists": 0, "deaths": 0, "kd_ratio": 0, "ad_ratio": 0 }, +const appdata = []; + +let id = 1; const server = http.createServer( function( request,response ) { if( request.method === 'GET' ) { @@ -21,9 +20,9 @@ const server = http.createServer( function( request,response ) { }) const handleGet = function( request, response ) { - const filename = dir + request.url.slice( 1 ) + const filename = dir + request.url.slice( 1 ) - if( request.url === '/' ) { + if(request.url === '/') { sendFile( response, 'public/index.html' ) }else{ sendFile( response, filename ) @@ -38,15 +37,47 @@ const handlePost = function( request, response ) { }) request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) - - // ... do something with the data here!!! + let data = JSON.parse(dataString); + console.log(data); + + if(request.url === "/add") { + addItem(data); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + }else if(request.url === "/modify"){ + modifyItem(data); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + }else if(request.url === "/delete"){ + deleteItem(data); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + }else{ + response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); + } - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) response.end() }) } +const addItem = function(data){ + console.log("adding"); + appdata.push({ + "id": id, + "kills": data.kills, + "assists": data.assists, + "deaths": data.kills, + "kd_ratio": data.kills / data.deaths, + "ad_ratio": data.assists / data.deaths + }) + id++; +} + +const modifyItem = function(data){ + console.log("modifying"); +} + +const deleteItem = function(data){ + console.log("deleting"); +} + const sendFile = function( response, filename ) { const type = mime.getType( filename ) From f610a7c89e4fe110a629dc98450902558feeb646 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 9 Sep 2020 00:58:07 -0400 Subject: [PATCH 02/21] Working add and delete, and table updates whenever contents change --- public/index.html | 21 +++--- public/js/scripts.js | 95 +++++++++++++++---------- server.improved.js | 160 ++++++++++++++++++++++++++----------------- 3 files changed, 170 insertions(+), 106 deletions(-) diff --git a/public/index.html b/public/index.html index 834c004a..6d3369e1 100755 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ - +

FPS Stat Calculator

@@ -29,14 +29,17 @@

Add new stats

- - - - - - - - + + + + + + + + + + +
ID #KillsAssistsDeathsK/D RatioA/D Ratio
ID #KillsAssistsDeathsK/D RatioA/D Ratio
diff --git a/public/js/scripts.js b/public/js/scripts.js index d0c1d140..abe0396a 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,11 +1,4 @@ -// Add some Javascript code here, to run on the front end. - -console.log("Welcome to assignment 2!") - function handle_add(){ - console.log("add!"); - // prevent default form action from being carried out - //e.preventDefault(); //The following source showed me how to extract values from a //form: https://www.w3schools.com/jsref/coll_form_elements.asp const input = document.getElementById("add"), @@ -20,8 +13,9 @@ function handle_add(){ method:'POST', body }).then( function( response ) { - // do something with the response - console.log( response ); + if(response.status === 200){ + getLatestTable(); + } return true; }) @@ -29,27 +23,22 @@ function handle_add(){ } function handle_modify(){ - console.log("modify!"); - console.log("add!"); - // prevent default form action from being carried out - //e.preventDefault(); - //The following source showed me how to extract values from a - //form: https://www.w3schools.com/jsref/coll_form_elements.asp const input = document.getElementById("modify"), - json = { - id_num: input.elements[0].value, - kills: input.elements[1].value, - assists: input.elements[2].value, - deaths: input.elements[3].value, - }, - body = JSON.stringify(json); + json = { + id: input.elements[0].value, + kills: input.elements[1].value, + assists: input.elements[2].value, + deaths: input.elements[3].value, + }, + body = JSON.stringify(json); fetch( '/modify', { method:'POST', body }).then( function( response ) { - // do something with the response - console.log( response ); + if(response.status === 200){ + getLatestTable(); + } return true; }) @@ -57,25 +46,61 @@ function handle_modify(){ } function handle_delete(){ - console.log("delete!"); - // prevent default form action from being carried out - //e.preventDefault(); - //The following source showed me how to extract values from a - //form: https://www.w3schools.com/jsref/coll_form_elements.asp const input = document.getElementById("delete"), - json = { - id_num: input.elements[0].value - }, - body = JSON.stringify(json); + json = { + id: input.elements[0].value + }, + body = JSON.stringify(json); fetch( '/delete', { method:'POST', body }).then( function( response ) { - // do something with the response - console.log( response ); + if(response.status === 200){ + getLatestTable(); + } return true; }) return false; +} + +function getLatestTable(){ + fetch( '/table', { + method:'GET' + }).then( function( response ) { + if(response.status === 200){ + updateTable(response); + } + return true; + }) + +} + +function updateTable(response){ + //Delete existing table and add a new, empty one. The following + //source have me the idea of swapping the tbody element of the + //table, and showed me how to do it: + //https://stackoverflow.com/questions/7271490/delete-all-rows-in-an-html-table + let table = document.getElementById("results"); + let newBody = document.createElement('tbody'); + table.replaceChild(newBody, table.lastChild); + + //The following source showed me how to extract json from the HTTP + //response: https://developer.mozilla.org/en-US/docs/Web/API/Body/json + response.json().then(data => { + //The following source was used to learn how to insert a row into + //a table in JS: https://www.w3schools.com/jsref/met_table_insertrow.asp + let numRows = data.numRows; + let rows = data.rows; + for(let i = 0; i < numRows; i++){ + let newRow = newBody.insertRow(i); + newRow.insertCell(0).innerHTML = `${rows[i].id}`; + newRow.insertCell(1).innerHTML = `${rows[i].kills}`; + newRow.insertCell(2).innerHTML = `${rows[i].assists}`; + newRow.insertCell(3).innerHTML = `${rows[i].deaths}`; + newRow.insertCell(4).innerHTML = `${rows[i].kd_ratio}`; + newRow.insertCell(5).innerHTML = `${rows[i].ad_ratio}`; + } + }); } \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index 5828b0a9..d387e2c9 100644 --- a/server.improved.js +++ b/server.improved.js @@ -9,94 +9,130 @@ const http = require( 'http' ), //Format: { "id": 0, "kills": 0, "assists": 0, "deaths": 0, "kd_ratio": 0, "ad_ratio": 0 }, const appdata = []; -let id = 1; +let id = 1;//Unique IDs to indicate rows to modify or delete +let numEntries = 0;//Length of appdata const server = http.createServer( function( request,response ) { - if( request.method === 'GET' ) { - handleGet( request, response ) - }else if( request.method === 'POST' ){ - handlePost( request, response ) - } + if(request.method === "GET") { + handleGet(request, response); + }else if(request.method === "POST"){ + handlePost(request, response); + } }) const handleGet = function( request, response ) { - const filename = dir + request.url.slice( 1 ) - - if(request.url === '/') { - sendFile( response, 'public/index.html' ) - }else{ - sendFile( response, filename ) - } -} + const filename = dir + request.url.slice( 1 ) -const handlePost = function( request, response ) { - let dataString = '' - - request.on( 'data', function( data ) { - dataString += data - }) - - request.on( 'end', function() { - let data = JSON.parse(dataString); - console.log(data); - - if(request.url === "/add") { - addItem(data); - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); - }else if(request.url === "/modify"){ - modifyItem(data); - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); - }else if(request.url === "/delete"){ - deleteItem(data); - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + if(request.url === "/") { + sendFile(response, "public/index.html"); + }else if(request.url === "/table"){ + sendTable(response); }else{ - response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); + sendFile(response, filename); } +} - response.end() - }) +const handlePost = function( request, response ) { + let dataString = ''; + + request.on( 'data', function( data ) { + dataString += data; + }) + + request.on( 'end', function() { + let data = JSON.parse(dataString); + console.log(data); + + if(request.url === "/add") { + addItem(data); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + }else if(request.url === "/modify"){ + modifyItem(data); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + }else if(request.url === "/delete"){ + deleteItem(data); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + }else{ + response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); + } + + response.end(); + }) } const addItem = function(data){ - console.log("adding"); - appdata.push({ - "id": id, - "kills": data.kills, - "assists": data.assists, - "deaths": data.kills, - "kd_ratio": data.kills / data.deaths, - "ad_ratio": data.assists / data.deaths - }) - id++; + appdata.push({ + "id": id, + "kills": data.kills, + "assists": data.assists, + "deaths": data.kills, + "kd_ratio": data.kills / data.deaths, + "ad_ratio": data.assists / data.deaths + }) + id++; + numEntries++; } const modifyItem = function(data){ - console.log("modifying"); + let targetID = data.id; + for(let i = 0; i < numEntries; i++){ + if(appdata[i]["id_num"] === targetID){ + appdata[i]["kills"] = data.kills; + appdata[i]["assists"] = data.assists; + appdata[i]["deaths"] = data.deaths; + return true; + } + } + //Entry if given ID not found. + return false; } const deleteItem = function(data){ - console.log("deleting"); + let targetID = Number(data.id); + console.log("targetID is: " +targetID); + for(let i = 0; i < numEntries; i++){ + console.log(appdata[i]); + if(appdata[i]["id"] === targetID){ + appdata.splice(i, 1); + numEntries--; + return true; + } + } + //Entry if given ID not found. + return false; } -const sendFile = function( response, filename ) { - const type = mime.getType( filename ) +const sendTable = function(response){ + let json = { + "numRows": numEntries, + "rows": [] + } + for(let i = 0; i < numEntries; i++){ + json["rows"].push(appdata[i]); + } + let body = JSON.stringify(json); + response.writeHead(200, "OK", {"Content-Type": "text/plain"}); + response.end(body); +} - fs.readFile( filename, function( err, content ) { +const sendFile = function( response, filename ) { + const type = mime.getType( filename ) - // if the error = null, then we've loaded the file successfully - if( err === null ) { + fs.readFile( filename, function( err, content ) { - // status code: https://httpstatuses.com - response.writeHeader( 200, { 'Content-Type': type }) - response.end( content ) + // if the error = null, then we've loaded the file successfully + if( err === null ) { - }else{ + // status code: https://httpstatuses.com + response.writeHead( 200, "OK", { "Content-Type": type }); + response.end( content ) - // file not found, error code 404 - response.writeHeader( 404 ) - response.end( '404 Error: File Not Found' ) + }else{ - } + // file not found, error code 404 + response.writeHead( 404, "File Not Found"); + response.end("404 Error: File Not Found"); + } }) } From aebd1884961e7186766745f2a61acc0209be4d78 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 9 Sep 2020 23:51:49 -0400 Subject: [PATCH 03/21] Modify now works properly --- server.improved.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server.improved.js b/server.improved.js index d387e2c9..d7b06054 100644 --- a/server.improved.js +++ b/server.improved.js @@ -74,12 +74,14 @@ const addItem = function(data){ } const modifyItem = function(data){ - let targetID = data.id; + let targetID = Number(data.id); for(let i = 0; i < numEntries; i++){ - if(appdata[i]["id_num"] === targetID){ + if(appdata[i]["id"] === targetID){ appdata[i]["kills"] = data.kills; appdata[i]["assists"] = data.assists; appdata[i]["deaths"] = data.deaths; + appdata[i]["kd_ratio"] = data.kills / data.deaths; + appdata[i]["ad_ratio"] = data.assists / data.deaths; return true; } } From 2e2525663f9a007e11ed7ee96f659a705e612580 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Thu, 10 Sep 2020 00:52:33 -0400 Subject: [PATCH 04/21] Added styling for forms and table --- public/css/style.css | 59 ++++++++++++++++++++++++++++++++++++++------ public/index.html | 30 +++++++++++----------- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 210ab4b9..94c207cb 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -6,20 +6,65 @@ h1 { .appgrid{ position: relative; - border: 1px solid red; width: 800px; + height: 500px; left: 50%; transform: translate(-50%, 0%); - display: grid; - grid-row-start: 1; - grid-column-start: 1; - grid-template: 1fr 1fr 1fr / 1fr 3fr; + display: flex; + flex-direction: column; + flex-wrap: wrap; } .app-item{ - border: 1px solid black; + padding-left: 10px; +} + +#add { + border-top: 1px solid black; + height: 165px; +} + +#modify { + height: 190px; +} + +#delete { + height: 125px; + border-bottom: 1px solid black; +} + +#add, #modify { + border-bottom: 1px solid black; } form { - width: 230px; + width: 250px; + border-right: 1px solid black; + border-left: 1px solid black; +} + +button { + margin-top: 5px; +} + +table { + /* + * This source taught me about border collapsing so I could draw a + * grid around the table rows: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr + */ + border-collapse: collapse; + border: 1px solid black; + margin-left: 5%; + width: 550px; +} + +td, th { + text-align: center; + padding: 5px; + border-bottom: 1px solid black; + border-right: 1px solid black; +} + +th { + background-color: lightskyblue; } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 6d3369e1..0d003173 100755 --- a/public/index.html +++ b/public/index.html @@ -27,21 +27,6 @@

Add new stats

- - - - - - - - - - - - - -
ID #KillsAssistsDeathsK/D RatioA/D Ratio
-

Modify row of stats

@@ -68,6 +53,21 @@

Delete row of stats

+ + + + + + + + + + + + + + +
ID #KillsAssistsDeathsK/D RatioA/D Ratio
From 1dcd22d94a91350d1df86bca0328bc88aa7b0dd1 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Thu, 10 Sep 2020 15:12:02 -0400 Subject: [PATCH 05/21] Can now download a .csv file with table data --- README.md | 64 ++------------------------------------------ public/index.html | 3 +++ public/js/scripts.js | 32 ++++++++++++++++++++++ server.improved.js | 34 +++++++++++++++++++++++ 4 files changed, 71 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 38593962..065332cb 100755 --- a/README.md +++ b/README.md @@ -1,69 +1,11 @@ Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js === -Due: September 16th, by 11:59 AM. - -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. - -Baseline Requirements ---- - -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. - -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, 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 `
    ` could also work and might be simpler to work with. - -CSS: -- CSS styling of the primary visual elements in the application -- Various CSS Selector functionality must be demonstrated: - - Element selectors - - ID selectors - - Class selectors -- CSS positioning and styling of the primary visual elements in the application: - - Use of either a CSS grid or flexbox for layout - - Rules defining fonts for all text used; no default fonts! Be sure to use a web safe font or a font from a web service like [Google Fonts](http://fonts.google.com/) - -- CSS defined in a maintainable, readable form, in external stylesheets - -JavaScript: -- At minimum, a small amount of front-end JavaScript to get / fetch data from the server; a sample is provided in this repository. - -Node.js: -- An HTTP Server that delivers all necessary files and data for the application. A starting point is provided in this repository. - -Deliverables ---- - -Do the following to complete this assignment: - -1. Fork the starting project code. This repo contains some starter code that may be used or discarded as needed. -2. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page, it displays correctly. -4. Deploy your project to Glitch, and fill in the appropriate fields in your package.json file. -5. Ensure that your project has the proper naming scheme `a2-yourname` so we can find it. -6. Modify the Readme to the specifications below. -7. Create and submit a Pull Request to the original repo. Label the pull request as follows: a2-gitusername-firstname-lastname - -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- - -## Your Web Application Title +## FPS Stat Calculator Include a very brief summary of your project here. Images are encouraged, along with concise, high-level text. + Here is a sample formula for summarizing your activities, talk about: - the domain area the project pertains to - the main challenges or problems the application addresses @@ -71,8 +13,6 @@ Here is a sample formula for summarizing your activities, talk about: - the main results of the implementation, does it really address the problem? - any additional implications of the resulting application, or possibly areas for future work that have been discovered as part of the design and implementation activities -(Note that when I use the above formula, I aim to have only one sentence per thought in order to remain concise.) - http://a2-charlieroberts.glitch.me ## Technical Achievements diff --git a/public/index.html b/public/index.html index 0d003173..28dd43d0 100755 --- a/public/index.html +++ b/public/index.html @@ -68,6 +68,9 @@

    Delete row of stats

+ + + diff --git a/public/js/scripts.js b/public/js/scripts.js index abe0396a..7523749f 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -74,7 +74,39 @@ function getLatestTable(){ } return true; }) +} +function handle_csv(){ + /* + * This source explained to me that you can't just use the "Content-Disposition" + * header to download files from GET requests: + * https://stackoverflow.com/questions/26737883/content-dispositionattachment-not-triggering-download-dialog + * + * The top answer from the following source taught me how to download + * a file using fetch. It essentially says to download the response, + * get the blob with the file data, create a URL to it, and then create + * an element that, when clicked, downloads the object at the URL, + * which is our file. The code between lines 95-99 comes from this source. + * The original post used arrow shorthand notation but I changed cause I + * didn't like it :) + * + * https://stackoverflow.com/questions/32545632/how-can-i-download-a-file-using-window-fetch + */ + fetch( '/csv', { + method:'GET' + }).then(function(response){ + response.blob() + .then(function(blob) { + let a = document.createElement("a"); + a.href = window.URL.createObjectURL(blob); + a.download = "stats.csv"; + document.body.appendChild(a);//Author put this because it's needed for firefox + a.click(); + a.remove(); + }); + }); + + return false; } function updateTable(response){ diff --git a/server.improved.js b/server.improved.js index d7b06054..d4113a51 100644 --- a/server.improved.js +++ b/server.improved.js @@ -27,6 +27,8 @@ const handleGet = function( request, response ) { sendFile(response, "public/index.html"); }else if(request.url === "/table"){ sendTable(response); + }else if(request.url === "/csv"){ + sendCSV(response); }else{ sendFile(response, filename); } @@ -117,6 +119,38 @@ const sendTable = function(response){ response.end(body); } +const sendCSV = function(response){ + let file = fs.createWriteStream("stats.csv"); + + file.write("id,kills,assists,deaths,K/D Ratio, A/D Ratio\n"); + for(let i = 0; i < numEntries; i++){ + file.write(`${appdata[i]["id"]}, ${appdata[i]["kills"]}, ${appdata[i]["assists"]}, ${appdata[i]["deaths"]}, ${appdata[i]["kd_ratio"]}, ${appdata[i]["ad_ratio"]}\n`); + } + file.end(); + + //sendFile(response, "stats.csv"); + const type = mime.getType( "stats.csv" ); + + fs.readFile( "stats.csv", function( err, content ) { + + // if the error = null, then we've loaded the file successfully + if( err === null ) { + + // status code: https://httpstatuses.com + response.writeHead( 200, "OK", { "Content-Type": type, "Content-Disposition": "attachment; filename=stats.csv" }); + response.end( content ); + + }else{ + + // file not found, error code 404 + response.writeHead( 404, "File Not Found"); + response.end("404 Error: File Not Found"); + } + }) + //response.writeHead(200, "OK", {"Content-disposition": "attachment; filename=newstats.csv"}); + //console.log(response); +} + const sendFile = function( response, filename ) { const type = mime.getType( filename ) From 79fc92cdd77e7818464905fdc82f0de704e18bf7 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Thu, 10 Sep 2020 17:31:35 -0400 Subject: [PATCH 06/21] server not calculate and returns totals --- public/css/style.css | 19 +++++++++++++++++++ public/index.html | 11 ++++++++++- public/js/scripts.js | 2 +- server.improved.js | 27 ++++++++++++++++++++++++++- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 94c207cb..eadd6d1e 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -45,6 +45,11 @@ form { button { margin-top: 5px; + width: fit-content; +} + +#download_button { + margin-left: 30%; } table { @@ -67,4 +72,18 @@ td, th { th { background-color: lightskyblue; +} + +.total_results_panel { + margin-left: 20%; + margin-top: 3%; + margin-bottom: 3%; + border: 1px solid black; + width: fit-content; + justify-content: center; + padding-right: 10px; /*Left padding comes from .app_item rule*/ +} + +.total_results_item, .total_results_label { + display: inline; } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 28dd43d0..68687af1 100755 --- a/public/index.html +++ b/public/index.html @@ -69,7 +69,16 @@

Delete row of stats

- +
+
Total Kills:
+
0
+
| Total Assists:
+
0
+
| Total Deaths:
+
0
+
+ + diff --git a/public/js/scripts.js b/public/js/scripts.js index 7523749f..584d50d7 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -120,7 +120,7 @@ function updateTable(response){ //The following source showed me how to extract json from the HTTP //response: https://developer.mozilla.org/en-US/docs/Web/API/Body/json - response.json().then(data => { + response.json().then(function(data) { //The following source was used to learn how to insert a row into //a table in JS: https://www.w3schools.com/jsref/met_table_insertrow.asp let numRows = data.numRows; diff --git a/server.improved.js b/server.improved.js index d4113a51..250d19ba 100644 --- a/server.improved.js +++ b/server.improved.js @@ -11,6 +11,9 @@ const appdata = []; let id = 1;//Unique IDs to indicate rows to modify or delete let numEntries = 0;//Length of appdata +let totalKills = 0; +let totalAssists = 0; +let totalDeaths = 0; const server = http.createServer( function( request,response ) { if(request.method === "GET") { @@ -73,17 +76,29 @@ const addItem = function(data){ }) id++; numEntries++; + totalKills += data.kills; + totalAssists += data.assists; + totalDeaths += data.deaths; } const modifyItem = function(data){ let targetID = Number(data.id); for(let i = 0; i < numEntries; i++){ if(appdata[i]["id"] === targetID){ + //Remove old values from running total + totalKills -= appdata[i]["kills"]; + totalAssists -= appdata[i]["deaths"]; + totalDeaths -= appdata[i]["assists"]; + appdata[i]["kills"] = data.kills; appdata[i]["assists"] = data.assists; appdata[i]["deaths"] = data.deaths; appdata[i]["kd_ratio"] = data.kills / data.deaths; appdata[i]["ad_ratio"] = data.assists / data.deaths; + + totalKills += data.kills; + totalAssists += data.assists; + totalDeaths += data.deaths; return true; } } @@ -97,6 +112,10 @@ const deleteItem = function(data){ for(let i = 0; i < numEntries; i++){ console.log(appdata[i]); if(appdata[i]["id"] === targetID){ + totalKills -= appdata[i]["kills"]; + totalAssists -= appdata[i]["deaths"]; + totalDeaths -= appdata[i]["assists"]; + appdata.splice(i, 1); numEntries--; return true; @@ -109,11 +128,17 @@ const deleteItem = function(data){ const sendTable = function(response){ let json = { "numRows": numEntries, - "rows": [] + "rows": [], + "totals": {}, } for(let i = 0; i < numEntries; i++){ json["rows"].push(appdata[i]); } + json.totals = { + "total_kills": totalKills, + "total_assists": totalAssists, + "total_deaths": totalDeaths, + } let body = JSON.stringify(json); response.writeHead(200, "OK", {"Content-Type": "text/plain"}); response.end(body); From d2dbfbf39780b447fcaad173e28856159db98c55 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Thu, 10 Sep 2020 22:36:00 -0400 Subject: [PATCH 07/21] New table for totals and averages, .csv file now downloads properly --- .gitignore | 1 + public/css/style.css | 15 +++++--- public/index.html | 40 +++++++++++++++++---- public/js/scripts.js | 42 +++++++++++++--------- server.improved.js | 83 +++++++++++++++++++++++--------------------- 5 files changed, 115 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 57195033..3340b430 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.DS_Store node_modules/ package-lock.json +stats.csv \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css index eadd6d1e..7bee2399 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -7,7 +7,7 @@ h1 { .appgrid{ position: relative; width: 800px; - height: 500px; + height: 630px; left: 50%; transform: translate(-50%, 0%); display: flex; @@ -38,18 +38,22 @@ h1 { } form { - width: 250px; + width: 235px; border-right: 1px solid black; border-left: 1px solid black; } +#total_avg_results { + width: 248px; +} + button { margin-top: 5px; width: fit-content; } #download_button { - margin-left: 30%; + margin-left: 7%; } table { @@ -59,8 +63,11 @@ table { */ border-collapse: collapse; border: 1px solid black; - margin-left: 5%; +} + +#results_list { width: 550px; + margin-left: 5%; } td, th { diff --git a/public/index.html b/public/index.html index 68687af1..fe04cc19 100755 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ - +

FPS Stat Calculator

@@ -46,7 +46,7 @@

Modify row of stats

- +

Delete row of stats

@@ -54,8 +54,37 @@

Delete row of stats

+ + + + + + + + + + + + + + + + + + + + + + + + + +
TotalAverage
Kills00
Assists00
Deaths00
+ + + - +
@@ -68,7 +97,7 @@

Delete row of stats

ID #
- +
diff --git a/public/js/scripts.js b/public/js/scripts.js index 584d50d7..8552718b 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -12,12 +12,12 @@ function handle_add(){ fetch( '/add', { method:'POST', body - }).then( function( response ) { + }).then(function( response ) { if(response.status === 200){ - getLatestTable(); + getLatestResults(); } return true; - }) + }); return false; } @@ -35,12 +35,12 @@ function handle_modify(){ fetch( '/modify', { method:'POST', body - }).then( function( response ) { + }).then(function( response ) { if(response.status === 200){ - getLatestTable(); + getLatestResults(); } return true; - }) + }); return false; } @@ -55,25 +55,25 @@ function handle_delete(){ fetch( '/delete', { method:'POST', body - }).then( function( response ) { + }).then(function( response ) { if(response.status === 200){ - getLatestTable(); + getLatestResults(); } return true; - }) + }); return false; } -function getLatestTable(){ - fetch( '/table', { +function getLatestResults(){ + fetch( '/results', { method:'GET' - }).then( function( response ) { + }).then(function( response ) { if(response.status === 200){ - updateTable(response); + updateResults(response); } return true; - }) + }); } function handle_csv(){ @@ -109,12 +109,12 @@ function handle_csv(){ return false; } -function updateTable(response){ +function updateResults(response){ //Delete existing table and add a new, empty one. The following - //source have me the idea of swapping the tbody element of the + //source gave me the idea of swapping the tbody element of the //table, and showed me how to do it: //https://stackoverflow.com/questions/7271490/delete-all-rows-in-an-html-table - let table = document.getElementById("results"); + let table = document.getElementById("results_list"); let newBody = document.createElement('tbody'); table.replaceChild(newBody, table.lastChild); @@ -134,5 +134,13 @@ function updateTable(response){ newRow.insertCell(4).innerHTML = `${rows[i].kd_ratio}`; newRow.insertCell(5).innerHTML = `${rows[i].ad_ratio}`; } + + //Now updates the boxes holding the totals and averages + document.getElementById("total_kills").innerHTML = `${data.totals_avgs["total_kills"]}` + document.getElementById("avg_kills").innerHTML = `${data.totals_avgs["avg_kills"]}` + document.getElementById("total_assists").innerHTML = `${data.totals_avgs["total_assists"]}` + document.getElementById("avg_assists").innerHTML = `${data.totals_avgs["avg_assists"]}` + document.getElementById("total_deaths").innerHTML = `${data.totals_avgs["total_deaths"]}` + document.getElementById("avg_deaths").innerHTML = `${data.totals_avgs["avg_deaths"]}` }); } \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index 250d19ba..f3d8ab8d 100644 --- a/server.improved.js +++ b/server.improved.js @@ -14,6 +14,9 @@ let numEntries = 0;//Length of appdata let totalKills = 0; let totalAssists = 0; let totalDeaths = 0; +let avgKills = 0; +let avgAssists = 0; +let avgDeaths = 0; const server = http.createServer( function( request,response ) { if(request.method === "GET") { @@ -28,7 +31,7 @@ const handleGet = function( request, response ) { if(request.url === "/") { sendFile(response, "public/index.html"); - }else if(request.url === "/table"){ + }else if(request.url === "/results"){ sendTable(response); }else if(request.url === "/csv"){ sendCSV(response); @@ -76,9 +79,7 @@ const addItem = function(data){ }) id++; numEntries++; - totalKills += data.kills; - totalAssists += data.assists; - totalDeaths += data.deaths; + calculateTotalsAvgs(data); } const modifyItem = function(data){ @@ -86,9 +87,9 @@ const modifyItem = function(data){ for(let i = 0; i < numEntries; i++){ if(appdata[i]["id"] === targetID){ //Remove old values from running total - totalKills -= appdata[i]["kills"]; - totalAssists -= appdata[i]["deaths"]; - totalDeaths -= appdata[i]["assists"]; + totalKills -= Number(appdata[i]["kills"]); + totalAssists -= Number(appdata[i]["deaths"]); + totalDeaths -= Number(appdata[i]["assists"]); appdata[i]["kills"] = data.kills; appdata[i]["assists"] = data.assists; @@ -96,9 +97,7 @@ const modifyItem = function(data){ appdata[i]["kd_ratio"] = data.kills / data.deaths; appdata[i]["ad_ratio"] = data.assists / data.deaths; - totalKills += data.kills; - totalAssists += data.assists; - totalDeaths += data.deaths; + calculateTotalsAvgs(data); return true; } } @@ -112,12 +111,16 @@ const deleteItem = function(data){ for(let i = 0; i < numEntries; i++){ console.log(appdata[i]); if(appdata[i]["id"] === targetID){ - totalKills -= appdata[i]["kills"]; - totalAssists -= appdata[i]["deaths"]; - totalDeaths -= appdata[i]["assists"]; + numEntries--; + + totalKills -= Number(appdata[i]["kills"]); + avgKills = totalKills / numEntries; + totalAssists -= Number(appdata[i]["deaths"]); + avgAssists = totalAssists / numEntries; + totalDeaths -= Number(appdata[i]["assists"]); + avgDeaths = totalDeaths / numEntries; appdata.splice(i, 1); - numEntries--; return true; } } @@ -125,19 +128,31 @@ const deleteItem = function(data){ return false; } +const calculateTotalsAvgs = function(data){ + totalKills += Number(data.kills); + avgKills = totalKills / numEntries; + totalAssists += Number(data.assists); + avgAssists = totalAssists / numEntries; + totalDeaths += Number(data.deaths); + avgDeaths = totalDeaths / numEntries; +} + const sendTable = function(response){ let json = { "numRows": numEntries, "rows": [], - "totals": {}, + "totals_avgs": {}, } for(let i = 0; i < numEntries; i++){ json["rows"].push(appdata[i]); } - json.totals = { + json["totals_avgs"] = { "total_kills": totalKills, + "avg_kills": avgKills, "total_assists": totalAssists, + "avg_assists": avgAssists, "total_deaths": totalDeaths, + "avg_deaths": avgDeaths } let body = JSON.stringify(json); response.writeHead(200, "OK", {"Content-Type": "text/plain"}); @@ -145,35 +160,25 @@ const sendTable = function(response){ } const sendCSV = function(response){ + /* + * The following link from node.js documentation taught how to + * close and flush write streams: https://nodejs.org/api/stream.html + */ let file = fs.createWriteStream("stats.csv"); + file.write(",Total,Average\n"); + file.write(`Kills,${totalKills},${avgKills}\n`); + file.write(`Assists,${totalAssists},${avgAssists}\n`); + file.write(`Deaths,${totalDeaths},${avgDeaths}\n\n`); - file.write("id,kills,assists,deaths,K/D Ratio, A/D Ratio\n"); + file.write("ID #,Kills,Assists,Deaths,K/D Ratio,A/D Ratio\n"); for(let i = 0; i < numEntries; i++){ file.write(`${appdata[i]["id"]}, ${appdata[i]["kills"]}, ${appdata[i]["assists"]}, ${appdata[i]["deaths"]}, ${appdata[i]["kd_ratio"]}, ${appdata[i]["ad_ratio"]}\n`); } + file.on("finish", function(){ + //Whole file has now been written, so send. + sendFile(response, "stats.csv"); + }); file.end(); - - //sendFile(response, "stats.csv"); - const type = mime.getType( "stats.csv" ); - - fs.readFile( "stats.csv", function( err, content ) { - - // if the error = null, then we've loaded the file successfully - if( err === null ) { - - // status code: https://httpstatuses.com - response.writeHead( 200, "OK", { "Content-Type": type, "Content-Disposition": "attachment; filename=stats.csv" }); - response.end( content ); - - }else{ - - // file not found, error code 404 - response.writeHead( 404, "File Not Found"); - response.end("404 Error: File Not Found"); - } - }) - //response.writeHead(200, "OK", {"Content-disposition": "attachment; filename=newstats.csv"}); - //console.log(response); } const sendFile = function( response, filename ) { From 3a1edd0e5d553669ee849c200625d995da6fd1bd Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Sun, 13 Sep 2020 20:34:47 -0400 Subject: [PATCH 08/21] Updated readme with all info except achievements --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 065332cb..9e167d2c 100755 --- a/README.md +++ b/README.md @@ -2,19 +2,63 @@ Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and === ## FPS Stat Calculator -Include a very brief summary of your project here. -Images are encouraged, along with concise, high-level text. +This application allows the users to enter in their kills, assists, and +deaths from multiple rounds of an FPS game, and it then calculates and +displays all these stats, as well as derived stats, to the user. The data +can then be exported as a CSV file, so users can open it in Excel and +create graphs to help evaluate their performance. +The main challenge I sought to solve with this application was to easily +keep track of and analyze one's performance in a game without have to write +stats down by hand or do a bunch of math. This makes it easy for the user +to see stats and create graphs for themselves to further improve their +skills. -Here is a sample formula for summarizing your activities, talk about: -- the domain area the project pertains to -- the main challenges or problems the application addresses -- the key innovations that make it possible to address the problem -- the main results of the implementation, does it really address the problem? -- any additional implications of the resulting application, or possibly areas for future work that have been discovered as part of the design and implementation activities - -http://a2-charlieroberts.glitch.me +I satisfied the requirements for the assignments in the following ways: +- **Functionality**: + - The server in server.improved.js serves the necessary files as well as + maintains a table of statistics in the "appdata" variable. The API supports + calls to add, modify and delete items from the table, as well as request for + all the data in the table. + - The results functionality has been implemented on the same page as the rest + of the application, as described in the assignment description for the + Technical Achievement. + - The form/entry functionality is implemented with the three forms on the left + side of the UI: one form each for add, modify and delete. + - The server logic requirement is satisfied in the addItem() and modifyItem() + functions which, in addition to adding the provided data to table, calculates + kill/death ratio and assist/death ratio by dividing the corresponding fields. + The totals and averages for kills, assists and deaths are computed in the + function calculateTotalsAvgs(). + - The derived fields are the kill/death ratio and assist/death ratio fields, + which use the kills/deaths fields and assists/deaths fields respectively. The + total and averages for kills, assists and deaths also use all the data in + the table. +- **HTML**: + - I used 3 HTML forms: one each for add, modify, delete. + - To display results, I used two HTML tables: One for each set of stats + the user enters in, and then one table for the running total and average + of kills, assists, deaths. + - /public/index.html has been validated with the link given in the + assignment description. +- **CSS**: + - All the primary visual elements of the application have been styled with + CSS. Each elements has style rules in /public/css/style.css. + - In /public/css/style.css, Element, ID, and Class selctors have all been + used (Ex: h1, .app-item, #add). + -A flex box was used to place the three forms and two tables. The div + item with class "appgrid" has "display: flex" to contain these element. + -The ___ font was used for all text. It's imported in on line ___ of + index.html. + -The CSS is all maintained in the external stylesheet /public/css/style.css +- **JS**: + - Front-end JS is located in /public/js/scripts.js to fetch data from the + server. + - Back-end JS for the Node.js HTTP server is located in server.improved.js + to return files and table data. +http://a2-joeswetz.glitch.me + ## Technical Achievements - **Tech Achievement 1**: Using a combination of... - **Tech Achievement 2**: ... From 6953e20be486ccbf21a3329527bb5d0d5e9e90be Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Sun, 13 Sep 2020 20:54:29 -0400 Subject: [PATCH 09/21] server now returns table contents in response to add, modify or delete requests. google font added and readme updated --- README.md | 14 ++++++++------ public/css/style.css | 9 ++++++++- public/index.html | 22 ++++++++-------------- public/js/scripts.js | 9 ++++++--- server.improved.js | 12 +++++++----- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 9e167d2c..e60da9a0 100755 --- a/README.md +++ b/README.md @@ -46,10 +46,11 @@ I satisfied the requirements for the assignments in the following ways: CSS. Each elements has style rules in /public/css/style.css. - In /public/css/style.css, Element, ID, and Class selctors have all been used (Ex: h1, .app-item, #add). - -A flex box was used to place the three forms and two tables. The div - item with class "appgrid" has "display: flex" to contain these element. - -The ___ font was used for all text. It's imported in on line ___ of - index.html. + -The three forms and two tables are inside a flex box for formatting. The + div item with class "appgrid" has "display: flex" to contain these elements. + -All text uses the font Inconsolata from Google Fonts. It's linked into + index.html at line 7, and set as the font family for all text on line 7 .of + public/css/style.css -The CSS is all maintained in the external stylesheet /public/css/style.css - **JS**: - Front-end JS is located in /public/js/scripts.js to fetch data from the @@ -60,8 +61,9 @@ I satisfied the requirements for the assignments in the following ways: http://a2-joeswetz.glitch.me ## Technical Achievements -- **Tech Achievement 1**: Using a combination of... -- **Tech Achievement 2**: ... +- **Real-Time Update**: The tables in index.html update automatically as the +contents change based on the user input. Whenever the user makes an add, modify, or +delete request, the server returns a list containing the current contents of the tables. ### Design/Evaluation Achievements - **Design Achievement 1**: Shown in `style.css`, the code... diff --git a/public/css/style.css b/public/css/style.css index 7bee2399..67903f44 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,4 +1,11 @@ -/*Style your own assignment! This is fun! */ +/* + * Joseph Swetz + * Stylesheet for index.html for CS4241 A2 + */ + +* { + font-family: Inconsolata, serif; +} h1 { text-align: center; diff --git a/public/index.html b/public/index.html index fe04cc19..bb06f39c 100755 --- a/public/index.html +++ b/public/index.html @@ -4,11 +4,13 @@ CS4241 Assignment 2 +

FPS Stat Calculator

+

Add new stats

@@ -44,8 +46,6 @@

Modify row of stats

-
-

Delete row of stats

@@ -54,6 +54,7 @@

Delete row of stats

+ @@ -81,9 +82,10 @@

Delete row of stats

+ - + @@ -95,19 +97,11 @@

Delete row of stats

+
A/D Ratio
- -
+ diff --git a/public/js/scripts.js b/public/js/scripts.js index 8552718b..98acd43e 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -14,7 +14,8 @@ function handle_add(){ body }).then(function( response ) { if(response.status === 200){ - getLatestResults(); + updateResults(response); + //getLatestResults(); } return true; }); @@ -37,7 +38,8 @@ function handle_modify(){ body }).then(function( response ) { if(response.status === 200){ - getLatestResults(); + updateResults(response); + //getLatestResults(); } return true; }); @@ -57,7 +59,8 @@ function handle_delete(){ body }).then(function( response ) { if(response.status === 200){ - getLatestResults(); + updateResults(response); + //getLatestResults(); } return true; }); diff --git a/server.improved.js b/server.improved.js index f3d8ab8d..4535bc6c 100644 --- a/server.improved.js +++ b/server.improved.js @@ -53,18 +53,20 @@ const handlePost = function( request, response ) { if(request.url === "/add") { addItem(data); - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + sendTable(response); + //response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); }else if(request.url === "/modify"){ modifyItem(data); - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + sendTable(response); + //response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); }else if(request.url === "/delete"){ deleteItem(data); - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); + sendTable(response); + //response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); }else{ response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); + response.end(); } - - response.end(); }) } From 8f2562acd655af61595bc6da9b9025588607b3fa Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Sun, 13 Sep 2020 20:56:49 -0400 Subject: [PATCH 10/21] Updated package.json in accordance with the one given in the assigment --- package.json | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 952daef4..7565d9ab 100755 --- a/package.json +++ b/package.json @@ -1,23 +1,12 @@ { - "name": "a2-shortstack", - "description": "Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js \r ===", - "author": "", + "name": "a2-joeswetz", + "version": "1.0", + "description": "FPS stat calculator", + "author": "Joe Swetz", "scripts": { "start": "node server.improved.js" }, "dependencies": { "mime": "^2.4.4" - }, - "version": "1.0.0", - "main": "server.improved.js", - "repository": { - "type": "git", - "url": "git+https://github.com/JoeSwetz/a2-shortstack.git" - }, - "keywords": [], - "license": "ISC", - "bugs": { - "url": "https://github.com/JoeSwetz/a2-shortstack/issues" - }, - "homepage": "https://github.com/JoeSwetz/a2-shortstack#readme" -} + } +} \ No newline at end of file From 8dadb3deb2b6522c4d5fb31d6337711674072608 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Sun, 13 Sep 2020 21:43:49 -0400 Subject: [PATCH 11/21] All data is now handled with numbers rather than strings, all numbers rounded to two decimal places --- server.improved.js | 50 +++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/server.improved.js b/server.improved.js index 4535bc6c..3c171eff 100644 --- a/server.improved.js +++ b/server.improved.js @@ -8,6 +8,7 @@ const http = require( 'http' ), //Format: { "id": 0, "kills": 0, "assists": 0, "deaths": 0, "kd_ratio": 0, "ad_ratio": 0 }, const appdata = []; +const DECIMAL_PRECISION = 2; let id = 1;//Unique IDs to indicate rows to modify or delete let numEntries = 0;//Length of appdata @@ -50,19 +51,18 @@ const handlePost = function( request, response ) { request.on( 'end', function() { let data = JSON.parse(dataString); console.log(data); + convertDataToNum(data); + calculateKDandAD(data); if(request.url === "/add") { addItem(data); sendTable(response); - //response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); }else if(request.url === "/modify"){ modifyItem(data); sendTable(response); - //response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); }else if(request.url === "/delete"){ deleteItem(data); sendTable(response); - //response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }); }else{ response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); response.end(); @@ -70,14 +70,25 @@ const handlePost = function( request, response ) { }) } +const convertDataToNum = function(data){ + data.kills = parseInt(data.kills, 10); + data.assists = parseInt(data.assists, 10); + data.deaths = parseInt(data.deaths, 10); +} + +const calculateKDandAD = function(data){ + data.kd_ratio = parseFloat((data.kills / data.deaths).toFixed(DECIMAL_PRECISION)); + data.ad_ratio = parseFloat((data.assists / data.deaths).toFixed(DECIMAL_PRECISION)); +} + const addItem = function(data){ appdata.push({ "id": id, "kills": data.kills, "assists": data.assists, - "deaths": data.kills, - "kd_ratio": data.kills / data.deaths, - "ad_ratio": data.assists / data.deaths + "deaths": data.deaths, + "kd_ratio": data.kd_ratio, + "ad_ratio": data.ad_ratio }) id++; numEntries++; @@ -89,15 +100,15 @@ const modifyItem = function(data){ for(let i = 0; i < numEntries; i++){ if(appdata[i]["id"] === targetID){ //Remove old values from running total - totalKills -= Number(appdata[i]["kills"]); - totalAssists -= Number(appdata[i]["deaths"]); - totalDeaths -= Number(appdata[i]["assists"]); + totalKills -= appdata[i]["kills"]; + totalAssists -= appdata[i]["deaths"]; + totalDeaths -= appdata[i]["assists"]; appdata[i]["kills"] = data.kills; appdata[i]["assists"] = data.assists; appdata[i]["deaths"] = data.deaths; - appdata[i]["kd_ratio"] = data.kills / data.deaths; - appdata[i]["ad_ratio"] = data.assists / data.deaths; + appdata[i]["kd_ratio"] = data.kd_ratio; + appdata[i]["ad_ratio"] = data.ad_ratio; calculateTotalsAvgs(data); return true; @@ -108,18 +119,16 @@ const modifyItem = function(data){ } const deleteItem = function(data){ - let targetID = Number(data.id); - console.log("targetID is: " +targetID); + let targetID = data.id; for(let i = 0; i < numEntries; i++){ - console.log(appdata[i]); if(appdata[i]["id"] === targetID){ numEntries--; - totalKills -= Number(appdata[i]["kills"]); + totalKills -= appdata[i]["kills"]; avgKills = totalKills / numEntries; - totalAssists -= Number(appdata[i]["deaths"]); + totalAssists -= appdata[i]["deaths"]; avgAssists = totalAssists / numEntries; - totalDeaths -= Number(appdata[i]["assists"]); + totalDeaths -= appdata[i]["assists"]; avgDeaths = totalDeaths / numEntries; appdata.splice(i, 1); @@ -130,12 +139,13 @@ const deleteItem = function(data){ return false; } + const calculateTotalsAvgs = function(data){ - totalKills += Number(data.kills); + totalKills += data.kills; avgKills = totalKills / numEntries; - totalAssists += Number(data.assists); + totalAssists += data.assists; avgAssists = totalAssists / numEntries; - totalDeaths += Number(data.deaths); + totalDeaths += data.deaths; avgDeaths = totalDeaths / numEntries; } From 3b894e98a81862970ac5f2204ddfa21e86801c8d Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Sun, 13 Sep 2020 21:53:23 -0400 Subject: [PATCH 12/21] Updated mime dependency version to match the one in glitch --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7565d9ab..e9b90016 100755 --- a/package.json +++ b/package.json @@ -7,6 +7,6 @@ "start": "node server.improved.js" }, "dependencies": { - "mime": "^2.4.4" + "mime": "^2.4.6" } -} \ No newline at end of file +} From 007066a34d5db9d8d490cac303816e34aacbf1ab Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Mon, 14 Sep 2020 21:14:09 -0400 Subject: [PATCH 13/21] updated readme with all submission information except design achievement --- README.md | 38 +++++++++++++++++++++++++++----------- public/js/scripts.js | 11 ++++++----- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e60da9a0..f12464e9 100755 --- a/README.md +++ b/README.md @@ -14,6 +14,19 @@ stats down by hand or do a bunch of math. This makes it easy for the user to see stats and create graphs for themselves to further improve their skills. +**Important Note**: I added an extra feature where the user can click +"Download as CSV" and the contents of both tables will be downloaded to the +user's computer as a file called "stats.csv." I know this was not a +requirement, but I just wanted to do it for fun! However, to learn how to do +this, I had to do some research, and I ended up using some code from stack +overflow. Since this was not an actual assignment requirement, Professor Roberts +said it was OK as long as I cite it and mention this in the readme. This code is +in handle_csv() in ./public/js/scripts.js. In that method, I put a comment +crediting the source, as well as my own explanation to prove my understanding of +it. The comments that start with "OA" are comments from the original post by the +Original Author, and I took out the => notation and made that part fit my coding +style (cause I didn't like the => notation). + I satisfied the requirements for the assignments in the following ways: - **Functionality**: - The server in server.improved.js serves the necessary files as well as @@ -27,9 +40,9 @@ I satisfied the requirements for the assignments in the following ways: side of the UI: one form each for add, modify and delete. - The server logic requirement is satisfied in the addItem() and modifyItem() functions which, in addition to adding the provided data to table, calculates - kill/death ratio and assist/death ratio by dividing the corresponding fields. - The totals and averages for kills, assists and deaths are computed in the - function calculateTotalsAvgs(). + the derived fields "kill/death ratio" and "assist/death ratio" in the function + calculateKDandAD(). The totals and averages for kills, assists and deaths are + computed in the function calculateTotalsAvgs(). - The derived fields are the kill/death ratio and assist/death ratio fields, which use the kills/deaths fields and assists/deaths fields respectively. The total and averages for kills, assists and deaths also use all the data in @@ -37,33 +50,36 @@ I satisfied the requirements for the assignments in the following ways: - **HTML**: - I used 3 HTML forms: one each for add, modify, delete. - To display results, I used two HTML tables: One for each set of stats - the user enters in, and then one table for the running total and average - of kills, assists, deaths. - - /public/index.html has been validated with the link given in the + the user enters in (for each game), and then one table for the running + total and average of kills, assists, deaths. + - ./public/index.html has been validated with the link given in the assignment description. - **CSS**: - All the primary visual elements of the application have been styled with - CSS. Each elements has style rules in /public/css/style.css. - - In /public/css/style.css, Element, ID, and Class selctors have all been + CSS. Each elements has style rules in ./public/css/style.css. + - In ./public/css/style.css, Element, ID, and Class selectors have all been used (Ex: h1, .app-item, #add). -The three forms and two tables are inside a flex box for formatting. The div item with class "appgrid" has "display: flex" to contain these elements. -All text uses the font Inconsolata from Google Fonts. It's linked into index.html at line 7, and set as the font family for all text on line 7 .of public/css/style.css - -The CSS is all maintained in the external stylesheet /public/css/style.css + -The CSS is all maintained in the external stylesheet ./public/css/style.css - **JS**: - - Front-end JS is located in /public/js/scripts.js to fetch data from the + - Front-end JS is located in ./public/js/scripts.js to fetch data from the server. - Back-end JS for the Node.js HTTP server is located in server.improved.js to return files and table data. +Project can be found on glitch at the following link: http://a2-joeswetz.glitch.me - + ## Technical Achievements - **Real-Time Update**: The tables in index.html update automatically as the contents change based on the user input. Whenever the user makes an add, modify, or delete request, the server returns a list containing the current contents of the tables. +There is also a request API call that gets the contents of the table when the HTML body +has loaded. ### Design/Evaluation Achievements - **Design Achievement 1**: Shown in `style.css`, the code... diff --git a/public/js/scripts.js b/public/js/scripts.js index 98acd43e..dbf0cb11 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -89,9 +89,10 @@ function handle_csv(){ * a file using fetch. It essentially says to download the response, * get the blob with the file data, create a URL to it, and then create * an
element that, when clicked, downloads the object at the URL, - * which is our file. The code between lines 95-99 comes from this source. - * The original post used arrow shorthand notation but I changed cause I - * didn't like it :) + * which is our file. The code between lines 101-111 comes from this source, + * and the comments that start with "OA" are comments from the original post + * by that Original Author. The original post used arrow shorthand notation + * but I changed cause I didn't like it :) * * https://stackoverflow.com/questions/32545632/how-can-i-download-a-file-using-window-fetch */ @@ -103,9 +104,9 @@ function handle_csv(){ let a = document.createElement("a"); a.href = window.URL.createObjectURL(blob); a.download = "stats.csv"; - document.body.appendChild(a);//Author put this because it's needed for firefox + document.body.appendChild(a);// OA: we need to append the element to the dom -> otherwise it will not work in firefox a.click(); - a.remove(); + a.remove();// OA: afterwards we remove the element again }); }); From e2f3043bbfdc9c8c444562fc5f40b9b211c716d1 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Mon, 14 Sep 2020 22:18:30 -0400 Subject: [PATCH 14/21] Added javadoc comments for all JS functions in scripts.js and server.improved.js --- public/js/scripts.js | 84 +++++++++++++++++++------ server.improved.js | 142 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 200 insertions(+), 26 deletions(-) diff --git a/public/js/scripts.js b/public/js/scripts.js index dbf0cb11..5acc1bc5 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,3 +1,12 @@ +/** + * Send an /add API HTTP request to add a new game's stats to the + * table. The stats are taken from the "add" form in index.html. + * The updated stats are then displayed in total_avg_results and + * results_list tables in index.html. + * + * @returns {boolean} true if server returned a 2O0 status code, + * false otherwise. + */ function handle_add(){ //The following source showed me how to extract values from a //form: https://www.w3schools.com/jsref/coll_form_elements.asp @@ -15,14 +24,22 @@ function handle_add(){ }).then(function( response ) { if(response.status === 200){ updateResults(response); - //getLatestResults(); + return true; } - return true; }); return false; } +/** + * Send a /modify API HTTP request to modifies a game's stats by + * setting them to the values in the "modify" form in index.html. + * The updated stats are then displayed in total_avg_results and + * results_list tables in index.html. + * + * @returns {boolean} true if server returned a 2O0 status code, + * false otherwise. + */ function handle_modify(){ const input = document.getElementById("modify"), json = { @@ -39,14 +56,22 @@ function handle_modify(){ }).then(function( response ) { if(response.status === 200){ updateResults(response); - //getLatestResults(); + return true; } - return true; }); return false; } +/** + * Send a /delete API HTTP request to remove a game's stats from + * the table. The ID# of the game to remove are taken from the + * "delete" form in index.html The updated stats are then displayed + * in total_avg_results and results_list tables in index.html. + * + * @returns {boolean} true if server returned a 2O0 status code, + * false otherwise. + */ function handle_delete(){ const input = document.getElementById("delete"), json = { @@ -60,25 +85,39 @@ function handle_delete(){ }).then(function( response ) { if(response.status === 200){ updateResults(response); - //getLatestResults(); + return true; } - return true; }); return false; } +/** + * Send a /results API HTTP request to retrieve all the current + * stats stored in the server. The updated stats are then displayed + * in total_avg_results and results_list tables in index.html. + * + * @returns {boolean} true if server returned a 2O0 status code, + * false otherwise. + */ function getLatestResults(){ fetch( '/results', { method:'GET' }).then(function( response ) { if(response.status === 200){ updateResults(response); + return true; } - return true; }); } +/** + * Downloads all the data from both tables as a CSV file called + * "stats.csv". + * + * @returns {boolean} if stats.csv was successfully created + * and downloaded. + */ function handle_csv(){ /* * This source explained to me that you can't just use the "Content-Disposition" @@ -89,7 +128,7 @@ function handle_csv(){ * a file using fetch. It essentially says to download the response, * get the blob with the file data, create a URL to it, and then create * an element that, when clicked, downloads the object at the URL, - * which is our file. The code between lines 101-111 comes from this source, + * which is our file. The code between lines 140-151 comes from this source, * and the comments that start with "OA" are comments from the original post * by that Original Author. The original post used arrow shorthand notation * but I changed cause I didn't like it :) @@ -99,20 +138,29 @@ function handle_csv(){ fetch( '/csv', { method:'GET' }).then(function(response){ - response.blob() - .then(function(blob) { - let a = document.createElement("a"); - a.href = window.URL.createObjectURL(blob); - a.download = "stats.csv"; - document.body.appendChild(a);// OA: we need to append the element to the dom -> otherwise it will not work in firefox - a.click(); - a.remove();// OA: afterwards we remove the element again - }); - }); + return response.blob() + }) + .then(function(blob) { + let a = document.createElement("a"); + a.href = window.URL.createObjectURL(blob); + a.download = "stats.csv"; + document.body.appendChild(a);// OA: we need to append the element to the dom -> otherwise it will not work in firefox + a.click(); + a.remove();// OA: afterwards we remove the element again + return true; + }); + return false; } +/** + * Updates the contents of the total_avg_results and results_list + * tables in index.html with the data in response. + * + * @param response an HTTP response with the data to be displayed in + * the total_avg_results and results_list tables in index.html + */ function updateResults(response){ //Delete existing table and add a new, empty one. The following //source gave me the idea of swapping the tbody element of the diff --git a/server.improved.js b/server.improved.js index 3c171eff..cf5ea2b3 100644 --- a/server.improved.js +++ b/server.improved.js @@ -12,6 +12,8 @@ const DECIMAL_PRECISION = 2; let id = 1;//Unique IDs to indicate rows to modify or delete let numEntries = 0;//Length of appdata + +//Track running totals and averages of all three main stats let totalKills = 0; let totalAssists = 0; let totalDeaths = 0; @@ -19,6 +21,12 @@ let avgKills = 0; let avgAssists = 0; let avgDeaths = 0; +/** + * Create the HTTP server and set the request handler to send GET + * and POST requests to their respective handlers. + * + * @type {Server} the HTTP server that will respond to all requests. + */ const server = http.createServer( function( request,response ) { if(request.method === "GET") { handleGet(request, response); @@ -27,6 +35,13 @@ const server = http.createServer( function( request,response ) { } }) +/** + * Handle the HTTP GET request stored in request and stores the + * HTTP response in response. + * + * @param request the HTTP GET request to be processed + * @param response the HTTP response to store all response data in + */ const handleGet = function( request, response ) { const filename = dir + request.url.slice( 1 ) @@ -41,6 +56,13 @@ const handleGet = function( request, response ) { } } +/** + * Handle the HTTP POST request stored in request and stores the + * HTTP response in response. + * + * @param request the HTTP POST request to be processed + * @param response the HTTP response to store all response data in + */ const handlePost = function( request, response ) { let dataString = ''; @@ -51,9 +73,14 @@ const handlePost = function( request, response ) { request.on( 'end', function() { let data = JSON.parse(dataString); console.log(data); + //Convert everything to a Number now so all operations + //dont have to keep calling Number() convertDataToNum(data); - calculateKDandAD(data); + calculateKDandAD(data);//Calculate derived fields + //Call the proper function based on API call, then + //send the updated table information in response so + // index.html can display the updated table. if(request.url === "/add") { addItem(data); sendTable(response); @@ -64,23 +91,53 @@ const handlePost = function( request, response ) { deleteItem(data); sendTable(response); }else{ + //Not recognized response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); response.end(); } }) } +/** + * Converts the stats given in the HTTP request to Numbers, and stores + * them back into data. + * + * @param data an object containing "kills", "assists", and "deaths" fields + * from the HTTP request. + */ const convertDataToNum = function(data){ - data.kills = parseInt(data.kills, 10); - data.assists = parseInt(data.assists, 10); - data.deaths = parseInt(data.deaths, 10); + if(data.hasOwnProperty("kills")) + data.kills = parseInt(data.kills, 10); + + if(data.hasOwnProperty("assists")) + data.assists = parseInt(data.assists, 10); + + if(data.hasOwnProperty("deaths")) + data.deaths = parseInt(data.deaths, 10); } +/** + * Calculates the kill/death ratio and assist/death ratio based on the + * kills, assists and deaths fields in data. + * + * @param data an object with "kills", "assists", and "deaths" fields for + * calculating the kill/death and assist/death ratio. All three fields + * are expected to contain Numbers. + */ const calculateKDandAD = function(data){ data.kd_ratio = parseFloat((data.kills / data.deaths).toFixed(DECIMAL_PRECISION)); data.ad_ratio = parseFloat((data.assists / data.deaths).toFixed(DECIMAL_PRECISION)); } +/** + * Add the item stored in data into the appdata table. This set + * of stats is assigned an unique ID number as well. + * + * @param data an object with stats to add to the table. It is expected + * to have fields for "kills", "assists", "deaths", "kd_ratio", and + * "ad_ratio." + * @return {boolean} true on successful addition, false otherwise. + */ const addItem = function(data){ appdata.push({ "id": id, @@ -92,9 +149,19 @@ const addItem = function(data){ }) id++; numEntries++; - calculateTotalsAvgs(data); + updateTotalsAvgs(data); } +/** + * Modify the row in the appdata table with the given id to instead + * have the stats stored in data. This set of stats will keep + * the unique ID number that was assigned to it when it was added. + * + * @param data an object with stats to add to the table. It is expected + * to have fields for "id", "kills", "assists", "deaths", "kd_ratio", + * and "ad_ratio." + * @return {boolean} true on successful modification, false otherwise. + */ const modifyItem = function(data){ let targetID = Number(data.id); for(let i = 0; i < numEntries; i++){ @@ -110,7 +177,7 @@ const modifyItem = function(data){ appdata[i]["kd_ratio"] = data.kd_ratio; appdata[i]["ad_ratio"] = data.ad_ratio; - calculateTotalsAvgs(data); + updateTotalsAvgs(data); return true; } } @@ -118,6 +185,13 @@ const modifyItem = function(data){ return false; } +/** + * Delete the row in the appdata table with the given id. + * + * @param data an object with the id of the row in appdata to remove. + * It is expected to have a field for "id". + * @return {boolean} true on successful deletion, false otherwise. + */ const deleteItem = function(data){ let targetID = data.id; for(let i = 0; i < numEntries; i++){ @@ -139,8 +213,15 @@ const deleteItem = function(data){ return false; } - -const calculateTotalsAvgs = function(data){ +/** + * Update the total and average kills, assists and deaths by taking into + * account the new set of kills, assists and death in data. + * + * @param data an object with stats to add to the table. It is expected + * to have fields for "kills", "assists", "deaths", "kd_ratio", and + * "ad_ratio." + */ +const updateTotalsAvgs = function(data){ totalKills += data.kills; avgKills = totalKills / numEntries; totalAssists += data.assists; @@ -149,6 +230,34 @@ const calculateTotalsAvgs = function(data){ avgDeaths = totalDeaths / numEntries; } +/** + * Creates an HTTP response with a JSON object that contains all the data for the + * total_avg_results and result_list tables in index.html. This includes every + * row of appdata as well as total and average number of kills, assists and deaths. + * This JSON object is then stored in response and the headers are set. + * + * The format of the JSON object is as follows: + * { + * numRows: , + * rows: [ + * { "id": , "kills": , "assists": , "deaths": , "kd_ratio": , "ad_ratio": }, + * ... + * { "id": , "kills": , "assists": , "deaths": , "kd_ratio": , "ad_ratio": }, + * ], + * totals_avgs: { + * total_kills: + * avg_kills: + * total_assists: + * avg_assists: + * total_deaths: + * avg_deaths: + * } + * } + * + * @param response an HTTP response that will populated with a JSON object that + * contains every row of appdata as well as total and average number of kills, + * assists and deaths. + */ const sendTable = function(response){ let json = { "numRows": numEntries, @@ -171,6 +280,14 @@ const sendTable = function(response){ response.end(body); } +/** + * Creates an HTTP response that contains the contents of a stats.csv file, + * which is a csv file that contains every row of appdata as well as total + * and average number of kills, assists and deaths. This response is then + * stored in response and the headers are set. + * + * @param response an HTTP response that will be populated the data for stats.csv. + */ const sendCSV = function(response){ /* * The following link from node.js documentation taught how to @@ -193,6 +310,15 @@ const sendCSV = function(response){ file.end(); } +/** + * Creates an HTTP response that contains the contents of the file located, + * at filename. This response is then stored in response and + * the headers are set. + * + * @param response an HTTP response that will be populated with the data for + * filename. + * @param filename the path to the file to send in response. + */ const sendFile = function( response, filename ) { const type = mime.getType( filename ) From 65a02e794f51394a8e495f1f5ee9fbd37b490fb0 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Tue, 15 Sep 2020 15:16:37 -0400 Subject: [PATCH 15/21] Added functionality to clear the server data from the client side in a "clear stats" button --- public/css/style.css | 13 ++++++++++++- public/index.html | 7 +++++-- public/js/scripts.js | 15 ++++++++++++++- server.improved.js | 16 +++++++++++++++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 67903f44..538b9f58 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -54,13 +54,24 @@ form { width: 248px; } +.button_container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; +} + button { margin-top: 5px; width: fit-content; } #download_button { - margin-left: 7%; + margin-right: 5px; +} + +#clear_button { + margin-left: 5px; } table { diff --git a/public/index.html b/public/index.html index bb06f39c..0adf7094 100755 --- a/public/index.html +++ b/public/index.html @@ -82,8 +82,11 @@

Delete row of stats

- - +
+ + + +
diff --git a/public/js/scripts.js b/public/js/scripts.js index 5acc1bc5..21a1cb62 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -92,6 +92,19 @@ function handle_delete(){ return false; } +function handle_clear(){ + fetch( '/clear', { + method:'GET', + }).then(function( response ) { + if(response.status === 200){ + updateResults(response); + return true; + } + }); + + return false; +} + /** * Send a /results API HTTP request to retrieve all the current * stats stored in the server. The updated stats are then displayed @@ -177,7 +190,7 @@ function updateResults(response){ //a table in JS: https://www.w3schools.com/jsref/met_table_insertrow.asp let numRows = data.numRows; let rows = data.rows; - for(let i = 0; i < numRows; i++){ + for (let i = 0; i < numRows; i++) { let newRow = newBody.insertRow(i); newRow.insertCell(0).innerHTML = `${rows[i].id}`; newRow.insertCell(1).innerHTML = `${rows[i].kills}`; diff --git a/server.improved.js b/server.improved.js index cf5ea2b3..93a807a1 100644 --- a/server.improved.js +++ b/server.improved.js @@ -7,7 +7,7 @@ const http = require( 'http' ), port = 3000 //Format: { "id": 0, "kills": 0, "assists": 0, "deaths": 0, "kd_ratio": 0, "ad_ratio": 0 }, -const appdata = []; +let appdata = []; const DECIMAL_PRECISION = 2; let id = 1;//Unique IDs to indicate rows to modify or delete @@ -51,6 +51,8 @@ const handleGet = function( request, response ) { sendTable(response); }else if(request.url === "/csv"){ sendCSV(response); + }else if(request.url === "/clear"){ + clearStats(response); }else{ sendFile(response, filename); } @@ -213,6 +215,18 @@ const deleteItem = function(data){ return false; } +const clearStats = function(response){ + appdata = []; + numEntries = 0; + totalKills = 0; + totalAssists = 0; + totalDeaths = 0; + avgKills = 0; + avgAssists = 0; + avgDeaths = 0; + sendTable(response); +} + /** * Update the total and average kills, assists and deaths by taking into * account the new set of kills, assists and death in data. From 09643a923a3a6e8d83e58f81cbd9b48c52d3e5a1 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Tue, 15 Sep 2020 15:23:38 -0400 Subject: [PATCH 16/21] Added valid nodejs version in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e9b90016..d9a3e039 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "a2-joeswetz", - "version": "1.0", + "version": "10.15.3", "description": "FPS stat calculator", "author": "Joe Swetz", "scripts": { From dbcee9a151e04f4d233fc01e6b37cb71b1554b70 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 16 Sep 2020 00:06:12 -0400 Subject: [PATCH 17/21] Added test doc with results from first test. Can now modify a row and only update some fields. Improved error checking. Bug fixes for updating totals and averages, and for dividing by zero. --- A2_Test_Task.md | 61 ++++++++++++++++++ public/js/scripts.js | 2 +- server.improved.js | 149 ++++++++++++++++++++++++++++++------------- 3 files changed, 165 insertions(+), 47 deletions(-) create mode 100644 A2_Test_Task.md diff --git a/A2_Test_Task.md b/A2_Test_Task.md new file mode 100644 index 00000000..6c9321d0 --- /dev/null +++ b/A2_Test_Task.md @@ -0,0 +1,61 @@ +A2 Test Task +=== +You've played a few games of your favorite FPS game, and +you've decided that you want to play competetively against +other players. You open the FPS stat calculator and you +want to get some stats about your performance from the past +couple of games. + +1.) You've played five games and you want to enter the +following stats: + Game1: 5 kills, 6 assists, 2 deaths + Game2: 0 kills, 8 assists, 3 deaths + Game3: 3 kills, 1 assist, 5 deaths + Game4: 10 kills, 0 assists, 2 deaths + Game5: 3 kills, 4 assists, 6 deaths + +2.) You realized you made a typo on Game3 and need to +correct it. Modify the Game3 stats to: 10 kills, 5 assists, +3 deaths. + +3.) You're not happy with your performance in Game2 and want +to erase it off the face of this Earth. :( Delete Game2 from the +table of stats. + +4.) You now see your stats on the screen. Numbers, however, are too +much work and you decide to look at some graphs. Download the table +data as a csv file and open it in excel. Verify the data is the +same as the data in the tables. + +Test 1 +=== +1.) Participant Last Name: Hunt +2.) What problems did the user have with your design? + An error occurred that result in the values in that table being null, + I acknowledged this out loud, not thinking it would interrupt the test. + The user then said they didn't even see the totals and averages table + at the bottom of the UI. +3.) What comments did they make that surprised you? + When modifying a row of stats, they said they were going to leave fields + that they didn't want changed empty (so they wanted to modify assist, so they + left the "# kills" and "# deaths" fields of the form empty). This was not + actually supported. +4.) What would you change about the interface based on their feedback? + I would move the table that displays totals and averages somewhere else, probably + higher up on the screen so the user sees it when they see the other table. + +Test 2 +=== +1.) Participant Last Name: +2.) What problems did the user have with your design? + +3.) What comments did they make that surprised you? + +4.) What would you change about the interface based on their feedback? + + + +To-Do: +check for empty values +reset id when cleared stats +delete no longer works \ No newline at end of file diff --git a/public/js/scripts.js b/public/js/scripts.js index 21a1cb62..35c54d91 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -180,7 +180,7 @@ function updateResults(response){ //table, and showed me how to do it: //https://stackoverflow.com/questions/7271490/delete-all-rows-in-an-html-table let table = document.getElementById("results_list"); - let newBody = document.createElement('tbody'); + let newBody = document.createElement("tbody"); table.replaceChild(newBody, table.lastChild); //The following source showed me how to extract json from the HTTP diff --git a/server.improved.js b/server.improved.js index 93a807a1..8066f4f2 100644 --- a/server.improved.js +++ b/server.improved.js @@ -74,27 +74,41 @@ const handlePost = function( request, response ) { request.on( 'end', function() { let data = JSON.parse(dataString); - console.log(data); //Convert everything to a Number now so all operations //dont have to keep calling Number() convertDataToNum(data); - calculateKDandAD(data);//Calculate derived fields + console.log(data); //Call the proper function based on API call, then //send the updated table information in response so // index.html can display the updated table. if(request.url === "/add") { - addItem(data); - sendTable(response); + if(addItem(data)) { + sendTable(response) + }else{ + response.writeHead(400, "Add request failed", {'Content-Type': 'text/plain'}); + response.end(); + } + }else if(request.url === "/modify"){ - modifyItem(data); - sendTable(response); + if(modifyItem(data)) { + sendTable(response); + }else{ + response.writeHead(400, "Modify request failed", {'Content-Type': 'text/plain'}); + response.end(); + } + }else if(request.url === "/delete"){ - deleteItem(data); - sendTable(response); + if(deleteItem(data)) + sendTable(response); + else{ + response.writeHead(400, "Delete request failed", {'Content-Type': 'text/plain'}); + response.end(); + } + }else{ //Not recognized - response.writeHead(400, "Invalid request type", {'Content-Type': 'text/plain'}); + response.writeHead(401, "Invalid request type", {'Content-Type': 'text/plain'}); response.end(); } }) @@ -104,10 +118,13 @@ const handlePost = function( request, response ) { * Converts the stats given in the HTTP request to Numbers, and stores * them back into data. * - * @param data an object containing "kills", "assists", and "deaths" fields - * from the HTTP request. + * @param data an object containing any of the following fields from the + * the HTTP request: "id", "kills", "assists", and "deaths" fields */ const convertDataToNum = function(data){ + if(data.hasOwnProperty("id")) + data.id = parseInt(data.id, 10); + if(data.hasOwnProperty("kills")) data.kills = parseInt(data.kills, 10); @@ -120,15 +137,17 @@ const convertDataToNum = function(data){ /** * Calculates the kill/death ratio and assist/death ratio based on the - * kills, assists and deaths fields in data. + * given set of kills, assists and deaths. * - * @param data an object with "kills", "assists", and "deaths" fields for - * calculating the kill/death and assist/death ratio. All three fields - * are expected to contain Numbers. + * @param kills number of kills from the game + * @param assists number of assists from the game + * @param deaths number of deaths from the game */ -const calculateKDandAD = function(data){ - data.kd_ratio = parseFloat((data.kills / data.deaths).toFixed(DECIMAL_PRECISION)); - data.ad_ratio = parseFloat((data.assists / data.deaths).toFixed(DECIMAL_PRECISION)); +const calculateKDandAD = function(kills, assists, deaths){ + return { + kd_ratio: parseFloat((kills / deaths).toFixed(DECIMAL_PRECISION)), + ad_ratio: parseFloat((assists / deaths).toFixed(DECIMAL_PRECISION)) + } } /** @@ -141,17 +160,24 @@ const calculateKDandAD = function(data){ * @return {boolean} true on successful addition, false otherwise. */ const addItem = function(data){ + if(Number.isNaN(data.kills) || data.kills < 0 || + Number.isNaN(data.assists) || data.assists < 0 || + Number.isNaN(data.deaths) || data.deaths < 0) + return false; + + let ratios = calculateKDandAD(data.kills, data.assists, data.deaths); appdata.push({ "id": id, "kills": data.kills, "assists": data.assists, "deaths": data.deaths, - "kd_ratio": data.kd_ratio, - "ad_ratio": data.ad_ratio + "kd_ratio": ratios.kd_ratio, + "ad_ratio": ratios.ad_ratio }) id++; numEntries++; - updateTotalsAvgs(data); + updateTotalsAvgs(data.kills, data.assists, data.deaths); + return true; } /** @@ -165,21 +191,28 @@ const addItem = function(data){ * @return {boolean} true on successful modification, false otherwise. */ const modifyItem = function(data){ - let targetID = Number(data.id); + let targetID = data.id; for(let i = 0; i < numEntries; i++){ if(appdata[i]["id"] === targetID){ //Remove old values from running total totalKills -= appdata[i]["kills"]; - totalAssists -= appdata[i]["deaths"]; - totalDeaths -= appdata[i]["assists"]; - - appdata[i]["kills"] = data.kills; - appdata[i]["assists"] = data.assists; - appdata[i]["deaths"] = data.deaths; - appdata[i]["kd_ratio"] = data.kd_ratio; - appdata[i]["ad_ratio"] = data.ad_ratio; - - updateTotalsAvgs(data); + totalAssists -= appdata[i]["assists"]; + totalDeaths -= appdata[i]["deaths"]; + + //Modify only the fields that were provided + if(!Number.isNaN(data.kills) && data.kills >= 0) + appdata[i]["kills"] = data.kills; + if(!Number.isNaN(data.assists) && data.assists >= 0) + appdata[i]["assists"] = data.assists; + if(!Number.isNaN(data.deaths) && data.deaths >= 0) + appdata[i]["deaths"] = data.deaths; + + //Recalculate derived fields + let ratios = calculateKDandAD(appdata[i]["kills"], appdata[i]["assists"], appdata[i]["deaths"]); + appdata[i]["kd_ratio"] = ratios.kd_ratio; + appdata[i]["ad_ratio"] = ratios.ad_ratio; + + updateTotalsAvgs(appdata[i]["kills"], appdata[i]["assists"], appdata[i]["deaths"]); return true; } } @@ -195,17 +228,18 @@ const modifyItem = function(data){ * @return {boolean} true on successful deletion, false otherwise. */ const deleteItem = function(data){ + if(Number.isNaN(data.id) || data.id < 0) + return false; + let targetID = data.id; for(let i = 0; i < numEntries; i++){ if(appdata[i]["id"] === targetID){ numEntries--; totalKills -= appdata[i]["kills"]; - avgKills = totalKills / numEntries; totalAssists -= appdata[i]["deaths"]; - avgAssists = totalAssists / numEntries; totalDeaths -= appdata[i]["assists"]; - avgDeaths = totalDeaths / numEntries; + updateAvgs(); appdata.splice(i, 1); return true; @@ -215,9 +249,18 @@ const deleteItem = function(data){ return false; } +/** + * Wipe all the data stored on the server and reset count variables. + * Return an a json indicating an empty table so index.html knows to + * display and empty table. + * + * @param response an HTTP response that will be populate with an + * empty table to indicate that server data has been wiped. + */ const clearStats = function(response){ appdata = []; numEntries = 0; + id = 0; totalKills = 0; totalAssists = 0; totalDeaths = 0; @@ -229,19 +272,33 @@ const clearStats = function(response){ /** * Update the total and average kills, assists and deaths by taking into - * account the new set of kills, assists and death in data. + * account the new set of kills, assists and deaths. * - * @param data an object with stats to add to the table. It is expected - * to have fields for "kills", "assists", "deaths", "kd_ratio", and - * "ad_ratio." + * @param kills number of kills from the game + * @param assists number of assists from the game + * @param deaths number of deaths from the game + */ +const updateTotalsAvgs = function(kills, assists, deaths){ + totalKills += kills; + totalAssists += assists; + totalDeaths += deaths; + updateAvgs(); +} + +/** + * Update the average kills, assists and deaths based on the current number + * of kills, assists and deaths. */ -const updateTotalsAvgs = function(data){ - totalKills += data.kills; - avgKills = totalKills / numEntries; - totalAssists += data.assists; - avgAssists = totalAssists / numEntries; - totalDeaths += data.deaths; - avgDeaths = totalDeaths / numEntries; +const updateAvgs = function(){ + if(numEntries <= 0){ + numEntries = 0; + avgKills = 0; + avgAssists = 0; + avgDeaths = 0; + } + avgKills = parseFloat((totalKills / numEntries).toFixed(DECIMAL_PRECISION)); + avgAssists = parseFloat((totalAssists / numEntries).toFixed(DECIMAL_PRECISION)); + avgDeaths = parseFloat((totalDeaths / numEntries).toFixed(DECIMAL_PRECISION)); } /** From 9f58fb15cf8552f02fbaa45016b9266fad0205a5 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 16 Sep 2020 20:53:18 -0400 Subject: [PATCH 18/21] Finished README.md, and removed A2_Test_Task.md since it's now in README.md --- A2_Test_Task.md | 61 ------------------------------------------ README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 64 deletions(-) delete mode 100644 A2_Test_Task.md diff --git a/A2_Test_Task.md b/A2_Test_Task.md deleted file mode 100644 index 6c9321d0..00000000 --- a/A2_Test_Task.md +++ /dev/null @@ -1,61 +0,0 @@ -A2 Test Task -=== -You've played a few games of your favorite FPS game, and -you've decided that you want to play competetively against -other players. You open the FPS stat calculator and you -want to get some stats about your performance from the past -couple of games. - -1.) You've played five games and you want to enter the -following stats: - Game1: 5 kills, 6 assists, 2 deaths - Game2: 0 kills, 8 assists, 3 deaths - Game3: 3 kills, 1 assist, 5 deaths - Game4: 10 kills, 0 assists, 2 deaths - Game5: 3 kills, 4 assists, 6 deaths - -2.) You realized you made a typo on Game3 and need to -correct it. Modify the Game3 stats to: 10 kills, 5 assists, -3 deaths. - -3.) You're not happy with your performance in Game2 and want -to erase it off the face of this Earth. :( Delete Game2 from the -table of stats. - -4.) You now see your stats on the screen. Numbers, however, are too -much work and you decide to look at some graphs. Download the table -data as a csv file and open it in excel. Verify the data is the -same as the data in the tables. - -Test 1 -=== -1.) Participant Last Name: Hunt -2.) What problems did the user have with your design? - An error occurred that result in the values in that table being null, - I acknowledged this out loud, not thinking it would interrupt the test. - The user then said they didn't even see the totals and averages table - at the bottom of the UI. -3.) What comments did they make that surprised you? - When modifying a row of stats, they said they were going to leave fields - that they didn't want changed empty (so they wanted to modify assist, so they - left the "# kills" and "# deaths" fields of the form empty). This was not - actually supported. -4.) What would you change about the interface based on their feedback? - I would move the table that displays totals and averages somewhere else, probably - higher up on the screen so the user sees it when they see the other table. - -Test 2 -=== -1.) Participant Last Name: -2.) What problems did the user have with your design? - -3.) What comments did they make that surprised you? - -4.) What would you change about the interface based on their feedback? - - - -To-Do: -check for empty values -reset id when cleared stats -delete no longer works \ No newline at end of file diff --git a/README.md b/README.md index f12464e9..65fd86a3 100755 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and === ## FPS Stat Calculator +Link to application: https://a2-joeswetz.glitch.me + This application allows the users to enter in their kills, assists, and deaths from multiple rounds of an FPS game, and it then calculates and displays all these stats, as well as derived stats, to the user. The data @@ -81,6 +83,68 @@ delete request, the server returns a list containing the current contents of the There is also a request API call that gets the contents of the table when the HTML body has loaded. -### Design/Evaluation Achievements -- **Design Achievement 1**: Shown in `style.css`, the code... -- **Design Achievement 2**: We tested the application with n=X users, finding that... +## Design/Evaluation Achievements +I tested the program with two different students in CS4241 according to the design +achievement description. You'll find the list of tasks below as well as my answers to the +questions for each user. I think these tasks were so simple (since the application itself +is so simple) that there wasn't really a lot of thinking aloud. The users just went and did +it very quickly. So I apologize if the tests were not ideal, but I did get some useful +feedback, as well some important bug catches. All bugs that were found from testing were +fixed before submitting. + +###Test Tasks +These were the tasks given to each user. + +1.) Add a few rows of game stats to the table. + +2.) Modify one of the rows of stats. + +3.) Delete a row of stats. + +4.) Download the table data as a CSV file and verify the contents match +the data in the tables. + +###Test 1 +1.) **Participant Last Name:** Hunt + +2.) **What problems did the user have with your design?** + An error occurred that result in the values in the table being null. + I acknowledged this out loud, not thinking it would interrupt the test. + The user then said they didn't even see the totals and averages table + at the bottom of the UI in the first place. Considering the user didn't + see the table, I consider it a UI problem since the user's view should + be immediately drawn to the table. + +3.) **What comments did they make that surprised you?** + When modifying a row of stats, they said they were going to leave fields + that they didn't want changed empty (so they wanted to modify assists, so they + left the "# kills" and "# deaths" fields of the form empty). This was not + actually supported. + +4.) **What would you change about the interface based on their feedback?** + I would move the table that displays totals and averages somewhere else, probably + higher up on the screen so the user sees it when they see the other table. + +###Test 2 +1.) **Participant Last Name:** Desveaux + +2.) **What problems did the user have with your design?** + The user was able to successfully complete the tasks without any struggle. + However, they did try to put 0 for deaths, which didn't work since, after the + first test, I did some bug fixes. I consider this a design problem since the + design should have informed the user about why the provided stats were not added + to the table. They didn't really think-aloud because they just instantly knew what + form to use and added the data. + +3.) **What comments did they make that surprised you?** + I was surprised that the user genuinely tried to put 0 deaths for one of the + games. I forgot this is an actual valid value and should therefore be put in the table. + After the first test, I made it so 0 deaths was not allowed since you can't divide by + 0. In reality, 0 deaths is a valid value and in FPS games, when there are 0 deaths the + K/D = # kills and the A/D = # assists. + +4.) **What would you change about the interface based on their feedback?** + I would some sort of error box that gives the user an informative message explaining + why their row of stats was not added to the table (i.e. negative number, unrecognized + character, etc.). + From a2a6d55139ded59811c9a6d5544aeab0dd141c57 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 16 Sep 2020 21:32:12 -0400 Subject: [PATCH 19/21] Now handles 0 deaths, and README.md finalized. Ready for submission. --- README.md | 40 +++++++++++++++++++++++----------------- server.improved.js | 15 +++++++++++++-- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 65fd86a3..564e5be1 100755 --- a/README.md +++ b/README.md @@ -16,19 +16,6 @@ stats down by hand or do a bunch of math. This makes it easy for the user to see stats and create graphs for themselves to further improve their skills. -**Important Note**: I added an extra feature where the user can click -"Download as CSV" and the contents of both tables will be downloaded to the -user's computer as a file called "stats.csv." I know this was not a -requirement, but I just wanted to do it for fun! However, to learn how to do -this, I had to do some research, and I ended up using some code from stack -overflow. Since this was not an actual assignment requirement, Professor Roberts -said it was OK as long as I cite it and mention this in the readme. This code is -in handle_csv() in ./public/js/scripts.js. In that method, I put a comment -crediting the source, as well as my own explanation to prove my understanding of -it. The comments that start with "OA" are comments from the original post by the -Original Author, and I took out the => notation and made that part fit my coding -style (cause I didn't like the => notation). - I satisfied the requirements for the assignments in the following ways: - **Functionality**: - The server in server.improved.js serves the necessary files as well as @@ -58,15 +45,15 @@ I satisfied the requirements for the assignments in the following ways: assignment description. - **CSS**: - All the primary visual elements of the application have been styled with - CSS. Each elements has style rules in ./public/css/style.css. + CSS. Each element has style rules in ./public/css/style.css. - In ./public/css/style.css, Element, ID, and Class selectors have all been used (Ex: h1, .app-item, #add). - -The three forms and two tables are inside a flex box for formatting. The + - The three forms and two tables are inside a flex box for formatting. The div item with class "appgrid" has "display: flex" to contain these elements. - -All text uses the font Inconsolata from Google Fonts. It's linked into + - All text uses the font Inconsolata from Google Fonts. It's linked into index.html at line 7, and set as the font family for all text on line 7 .of public/css/style.css - -The CSS is all maintained in the external stylesheet ./public/css/style.css + - The CSS is all maintained in the external stylesheet ./public/css/style.css - **JS**: - Front-end JS is located in ./public/js/scripts.js to fetch data from the server. @@ -76,6 +63,25 @@ I satisfied the requirements for the assignments in the following ways: Project can be found on glitch at the following link: http://a2-joeswetz.glitch.me +####Important Note +I added an extra feature where the user can click "Download as CSV" and the +contents of both tables will be downloaded to the user's computer as a file +called "stats.csv." I know this was not a requirement, but I just wanted to do +it for fun! However, to learn how to do this, I had to do some research, and I +ended up using some code from stack overflow. Since this was not an actual +assignment requirement, Professor Robertssaid it was OK as long as I cite it and +mention this in the readme. This code is in handle_csv() in ./public/js/scripts.js. +In that method, I put a comment crediting the source, as well as my own explanation +to prove my understanding of it. The comments that start with "OA" are comments +from the original post by theOriginal Author, and I took out the => notation and +made that part fit my coding style (cause I didn't like the => notation). + +While I believe the process of writing and sending a CSV file server-side and then +doing research on how to download it on the client was enough of challenge that it +should be considered a technical achievement, I will of course leave it up to your +discretion, especially considering that I did have to use some code from stack +overflow. The other achievements can be found below. + ## Technical Achievements - **Real-Time Update**: The tables in index.html update automatically as the contents change based on the user input. Whenever the user makes an add, modify, or diff --git a/server.improved.js b/server.improved.js index 8066f4f2..002fdda8 100644 --- a/server.improved.js +++ b/server.improved.js @@ -144,9 +144,20 @@ const convertDataToNum = function(data){ * @param deaths number of deaths from the game */ const calculateKDandAD = function(kills, assists, deaths){ + let kd, ad; + //We want to avoid divide by zero errors, but still allows for 0 deaths. + //If there are 0 deaths, FPS games traditionally treat K/D = # kill and + //A/D as assists + if(deaths === 0) { + kd = kills; + ad = assists; + }else{ + kd = parseFloat((kills / deaths).toFixed(DECIMAL_PRECISION)); + ad = parseFloat((assists / deaths).toFixed(DECIMAL_PRECISION)); + } return { - kd_ratio: parseFloat((kills / deaths).toFixed(DECIMAL_PRECISION)), - ad_ratio: parseFloat((assists / deaths).toFixed(DECIMAL_PRECISION)) + kd_ratio: kd, + ad_ratio: ad } } From 0a4721a10b03df60d3e2bdb2501e91f9a03d718e Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 16 Sep 2020 21:38:46 -0400 Subject: [PATCH 20/21] Reformatted README.md to fit github markdown --- README.md | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 564e5be1..219b7943 100755 --- a/README.md +++ b/README.md @@ -98,22 +98,17 @@ it very quickly. So I apologize if the tests were not ideal, but I did get some feedback, as well some important bug catches. All bugs that were found from testing were fixed before submitting. -###Test Tasks -These were the tasks given to each user. - -1.) Add a few rows of game stats to the table. - -2.) Modify one of the rows of stats. - -3.) Delete a row of stats. - -4.) Download the table data as a CSV file and verify the contents match +These were the tasks given to each user: +1. Add a few rows of game stats to the table. +2. Modify one of the rows of stats. +3. Delete a row of stats. +4. Download the table data as a CSV file and verify the contents match the data in the tables. -###Test 1 -1.) **Participant Last Name:** Hunt +Test 1 +1. **Participant Last Name:** Hunt -2.) **What problems did the user have with your design?** +2. **What problems did the user have with your design?** An error occurred that result in the values in the table being null. I acknowledged this out loud, not thinking it would interrupt the test. The user then said they didn't even see the totals and averages table @@ -121,20 +116,20 @@ the data in the tables. see the table, I consider it a UI problem since the user's view should be immediately drawn to the table. -3.) **What comments did they make that surprised you?** +3. **What comments did they make that surprised you?** When modifying a row of stats, they said they were going to leave fields that they didn't want changed empty (so they wanted to modify assists, so they left the "# kills" and "# deaths" fields of the form empty). This was not actually supported. -4.) **What would you change about the interface based on their feedback?** +4. **What would you change about the interface based on their feedback?** I would move the table that displays totals and averages somewhere else, probably higher up on the screen so the user sees it when they see the other table. -###Test 2 -1.) **Participant Last Name:** Desveaux +Test 2 +1. **Participant Last Name:** Desveaux -2.) **What problems did the user have with your design?** +2. **What problems did the user have with your design?** The user was able to successfully complete the tasks without any struggle. However, they did try to put 0 for deaths, which didn't work since, after the first test, I did some bug fixes. I consider this a design problem since the @@ -142,15 +137,14 @@ the data in the tables. to the table. They didn't really think-aloud because they just instantly knew what form to use and added the data. -3.) **What comments did they make that surprised you?** +3. **What comments did they make that surprised you?** I was surprised that the user genuinely tried to put 0 deaths for one of the games. I forgot this is an actual valid value and should therefore be put in the table. After the first test, I made it so 0 deaths was not allowed since you can't divide by 0. In reality, 0 deaths is a valid value and in FPS games, when there are 0 deaths the K/D = # kills and the A/D = # assists. -4.) **What would you change about the interface based on their feedback?** +4. **What would you change about the interface based on their feedback?** I would some sort of error box that gives the user an informative message explaining why their row of stats was not added to the table (i.e. negative number, unrecognized - character, etc.). - + character, etc.). \ No newline at end of file From 0c4a4feed34bf7dc9d53202b0af51976aad86803 Mon Sep 17 00:00:00 2001 From: Joe Swetz Date: Wed, 16 Sep 2020 21:39:35 -0400 Subject: [PATCH 21/21] More formatting :( --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 219b7943..3474708a 100755 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ I satisfied the requirements for the assignments in the following ways: Project can be found on glitch at the following link: http://a2-joeswetz.glitch.me -####Important Note +**Important Note** I added an extra feature where the user can click "Download as CSV" and the contents of both tables will be downloaded to the user's computer as a file called "stats.csv." I know this was not a requirement, but I just wanted to do