diff --git a/README.md b/README.md index 996cf12d..6943472f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,29 @@ -Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js +### Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js === +## A Random Survey About Destiny (The Game) +Michael Lai http://a2-azinxtheonix.glitch.me + +With this assignment, I've created a scoreboard for people to record their highscores to. This scoreboard also keeps track who has the highscore in each game. + +For the technical side of the project, it shows that I can design an application that involves sending data to a server and sending data back to the client to update. I used CSS grids to position items. + +As for instructions on how to use the application, I've already put them in the site itself. + +NOTE: There's an error that's stating the receiving end does not exist, which in turn causes the whole application not to work. From debugging, it seems to be a server side error when it tries to unsuccessfully load the site in node.js (try node ./server.improved.js in console, go to the localhost on port 3000, and loading the local.html file; it won't work). I've looked through my code and but I can't see what's causing this. I'm fairly certain that the rest of the code works (if the client can connect properly). + +The server should also be calculating the highscore as the derived data, but with the server connection bug, nothing can be seen. + +## Technical Achievements +**1 - Updating Data:** +The code here should work if it weren't for the weird server connection bug that's going on right now. Essentially what the code should be doing is every time a delete, submit, or modify action is taken, it will beforehand update the client side data to make sure the data the action working with is up to date. After the action is complete, then the table is updated (with another call to the server for the JSON data). + +## Design/Evaluation Achievements +N/A + +## Old Readme Below +--- + Due: September 9th, by 11:59 AM. This assignment aims to introduce you to creating a prototype two-tiered web application. @@ -82,16 +105,4 @@ You'll need to use sometype of collaborative software that will enable you both 3. What comments did they make that surprised you? 4. What would you change about the interface based on their feedback? -*You do not need to actually make changes based on their feedback*. This acheivement is designed to help gain experience testing user interfaces. If you run two user studies, you should answer two sets of questions. - -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 -Include a very brief summary of your project here. Be sure to include the CSS positioning technique you used, and any required instructions to use your application. - -## Technical Achievements -- **Tech Achievement 1**: Using a combination of... - -### Design/Evaluation Achievements -- **Design Achievement 1**: +*You do not need to actually make changes based on their feedback*. This acheivement is designed to help gain experience testing user interfaces. If you run two user studies, you should answer two sets of questions. \ No newline at end of file diff --git a/package.json b/package.json index 988f135f..cef7a3c7 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "", - "version": "", + "name": "a2-shortstack", + "version": "1.0.0", "description": "", - "author": "", + "author": "Michael Lai", "scripts": { "start": "node server.improved.js" }, diff --git a/public/Floofle2.png b/public/Floofle2.png new file mode 100644 index 00000000..e7b7b320 Binary files /dev/null and b/public/Floofle2.png differ diff --git a/public/css/style.css b/public/css/style.css index d5f842ab..24964659 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1 +1,87 @@ -/*Style your own assignment! This is fun! */ \ No newline at end of file +/* +Color Theme Notes: +#010400 (Black) +#B7B7B7 (Gray) +#FFFBFC (White) +#62BBC1 (Cyan) +#EC058E (Magenta) +*/ + +/*General Formatting*/ +body { + font-family: 'Urbanist', sans-serif; + color: black; + background-color: #62BBC1; +} + +.gridTitle { + grid-area: header; +} + +.gridForm { + grid-area: form; + background-color: #EC058E; + margin-left: auto; + margin-right: auto; + text-align: center; + border-radius: 10px; +} + +.gridTable { + grid-area: table; +} + +.gridContainer { + display: grid; + grid-template-areas: + 'header header header header' + 'form table table table'; + grid-gap: 10px; + padding: 10px; +} + +/*Form*/ + +.instructions { + padding: 10px; +} + +.defaultSelection { + display: none; +} + +.button { + cursor: pointer; +} + +/*Table*/ +table { + border-collapse: collapse; + margin-left: auto; + margin-right: auto; + text-align: center; + background-color: #B7B7B7; + border-radius: 10px; +} + +th, td { + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; +} + +/*Icons*/ + +.material-icons-outlined { + user-select: none; +} + +.iconButton { + color:black; +} + +.iconButton:hover { + cursor: pointer; + color: #FFFBFC; +} diff --git a/public/index.html b/public/index.html index c56d620e..e1b275e8 100644 --- a/public/index.html +++ b/public/index.html @@ -3,39 +3,81 @@ CS4241 Assignment 2 + + + + -
- - -
- - +
+ + + + + + + + + + + + + + + +
NameScoreGameHigh Score?
Test0TestNo + edit + + delete_forever +
+
+ + + diff --git a/public/js/scripts.js b/public/js/scripts.js index de052eae..178086d2 100644 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,3 +1,229 @@ -// Add some Javascript code here, to run on the front end. +const appdata = []; +let modifyID = -1; -console.log("Welcome to assignment 2!") \ No newline at end of file +// Updates the local appdata file +function updateJSON() { + fetch('/update', { + method: 'GET' + }).then(function(response) { + return response.json(); + }).then(function(data) { + appdata = data; + }); + console.log(appdata); +} + +// Takes an entry and places it in the form +function editEntry(name, game, score, id) { + document.getElementById("submitButton").innerHTML = "Modify"; + document.getElementById('nameForm').value = name; + document.getElementById('scoreForm').value = score; + modifyID = id; + let gameSelect = document.getElementById('gameForm'); + switch(game) { + case 'Mario Bros.': + gameSelect.selectedIndex = 1; + case 'Donkey Kong': + gameSelect.selectedIndex = 2; + case 'Street Racing': + gameSelect.selectedIndex = 3; + case 'Tetris': + gameSelect.selectedIndex = 4; + default: + console.log("Uh oh"); + } +} + +// Deletes an entry +function deleteEntry(id) { + updateJSON(); + if(containsID === true) { // ID is still in server memory + fetch('/delete', { + method: 'POST' + }).then(function (response) { + return response.json() + }).then(function (data) { + appdata = data; + }); + } + updateForm(); +} + +// Creates the JSON String +const makeJSONString = function () { + const name = document.getElementById('nameForm'); + const gameSelect = document.getElementById('gameForm'); + let game = gameSelect.options[gameSelect.selectedIndex]; + const score = document.getElementById('scoreForm'); + + if(modifyID != -1 && containsID(modifyID)) { // We are modifying an ID that exists + const id = modifyID; + } else { // We are adding an ID or the ID no longer exists + const id = getNextAvailableID(); + } + + const json = { + name: name.value, + game: game.value, + score: parse.Int(score.value), + highscore: false, + id: id + }; + return JSON.stringify(json); +}; + +// Gets the next available ID +// Note: Critical that you update appdata before you call this! +function getNextAvailableID() { + let currentID = 0; + for(let i = 0; i < appdata.length; i++) { + let scoreEntry = appdata[i]; + + if(scoreEntry['id'] >= currentID) { + currentID = scoreEntry['id'] + 1; + } + } + return currentID; +} + +// Checks if the given ID is in appdata +// Note: Critical that you update appdata before you call this! +function containsID(id) { + for(let i = 0; i < appdata.length; i++) { + let scoreEntry = appdata[i]; + + if(scoreEntry['id'] === id) { + return true; + } + } + return false; +} + +// Updates the form +function updateForm() { + console.log("Update Form Called"); + updateJSON(); + console.log("Update JSON Called"); + const table = document.getElementById("scoreTable"); + table.innerHTML = ""; + + // Create table header/titles + let tableHeader = document.createElement('tr'); + let h1 = document.createElement('th'); + let h2 = document.createElement('th'); + let h3 = document.createElement('th'); + let h4 = document.createElement('th'); + + h1.innerHTML = "Name"; + h2.innerHTML = "Game"; + h3.innerHTML = "Score"; + h4.innerHTML = "High Score?"; + + tableHeader.appendChild(h1); + tableHeader.appendChild(h2); + tableHeader.appendChild(h3); + tableHeader.appendChild(h4); + + table.appendChild(tableHeader); + + for(let i = 0; i < appdata.length; i++) { + let scoreEntry = appdata[i]; + + let newRow = document.createElement('tr'); + let nameData = document.createElement('td'); + let gameData = document.createElement('td'); + let scoreData = document.createElement('td'); + let highscoreData = document.createElement('td'); + + nameData.innerHTML = scoreEntry['name']; + gameData.innerHTML = scoreEntry['game']; + scoreData.innerHTML = scoreEntry['score']; + highscoreData.innerHTML = scoreEntry['highscore']; + + newRow.appendChild(nameData); + newRow.appendChild(gameData); + newRow.appendChild(scoreData); + newRow.appendChild(highscoreData); + + let editIcon = document.createElement('td'); + editIcon.innerHTML = 'edit' + editIcon.onclick = function(e) { + e.preventDefault(); + editEntry(scoreEntry['name'], scoreEntry['game'], scoreEntry['score'], scoreEntry['id']); + } + + let deleteIcon = document.createElement('td'); + deleteIcon.innerHTML = 'delete_forever' + deleteIcon.onclick = function(e) { + e.preventDefault(); + deleteEntry(scoreEntry['id']); + } + + newRow.appendChild(editIcon); + newRow.appendChild(deleteIcon); + + table.appendChild(newRow); + } +} + +const submitEntry = function(e) { + // Prevent default form action from being carried out + e.preventDefault(); + + updateJSON(); + let body = makeJSONString(); + let json = JSON.parse(body); + + if(json['name'] === "" || + json['game'] === "-" || + json['score'] < 0) { + alert("One or more fields aren't filled properly."); + } + + if(modifyID === -1) { // Submit + fetch( '/submit', { + method:'POST', + body + }).then(function(response) { + updateForm(); + document.getElementById('nameForm').value = ""; + document.getElementById('gameForm').value = ""; + let scoreSelect = document.getElementById('scoreForm'); + scoreSelect.selectedIndex = 0; + console.log(response); + }); + } else { // Modify + fetch( '/modify', { + method:'POST', + body + }).then(function(response) { + updateForm(); + document.getElementById('nameForm').value = ""; + document.getElementById('gameForm').value = ""; + let scoreSelect = document.getElementById('scoreForm'); + scoreSelect.selectedIndex = 0; + console.log(response); + }); + } + newEntry; + updateForm(); + return false; +} + +const newEntry = function(e) { + e.preventDefault(); + modifyID = -1; + document.getElementById('nameForm').value = ""; + let gameSelect = document.getElementById('gameForm'); + gameSelect.selectedIndex = 0; + document.getElementById('scoreForm').value = ""; + document.getElementById("submitButton").innerHTML = "Submit"; +} + +window.onload = function() { + const submitButton = document.getElementById("submitButton"); + submitButton.onclick = submitEntry; + const newButton = document.getElementById("newButton"); + newButton.onclick = newEntry; + updateForm(); +} \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index 26673fc0..73011421 100644 --- a/server.improved.js +++ b/server.improved.js @@ -7,26 +7,33 @@ const http = require( 'http' ), port = 3000 const appdata = [ - { 'model': 'toyota', 'year': 1999, 'mpg': 23 }, - { 'model': 'honda', 'year': 2004, 'mpg': 30 }, - { 'model': 'ford', 'year': 1987, 'mpg': 14} + { 'name': 'AAA', 'score': 43, 'game': 'Mario Bros.', 'highscore': true, 'id': 0}, + { 'name': 'ABC', 'score': 67, 'game': 'Donkey Kong', 'highscore': true, 'id': 1}, + { 'name': 'ZZZ', 'score': 168, 'game': 'Street Racing', 'highscore': true, 'id': 2}, + { 'name': 'E', 'score': 2, 'game': 'Mario', 'highscore': false, 'id': 3} ] const server = http.createServer( function( request,response ) { if( request.method === 'GET' ) { - handleGet( request, response ) + handleGet( request, response ); }else if( request.method === 'POST' ){ - handlePost( request, response ) + handlePost( request, response ); } }) const handleGet = function( request, response ) { - const filename = dir + request.url.slice( 1 ) + console.log(request.url); + const filename = dir + request.url.slice( 1 ); if( request.url === '/' ) { - sendFile( response, 'public/index.html' ) - }else{ - sendFile( response, filename ) + sendFile( response, 'public/index.html' ); + } else if(request.url === '/update') { + const type = mime.getType(appdata); + response.writeHead(200, {'Content-Type': type}); + response.write(JSON.stringify(appdata)); + response.end(); + } else { + sendFile( response, filename ); } } @@ -38,9 +45,87 @@ const handlePost = function( request, response ) { }) request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) - - // ... do something with the data here!!! + const json = JSON.parse(dataString) + console.log(json); + + if(request.url === '/delete') { + for(let i = 0; i < appdata.length; i++) { + if(appdata[i].id === json.id) { // ID Matches + appdata.splice(i, 1); + console.log("Deletion complete"); + break; + } + } + if(json.highscore === true) { // Highscore Update + let high = -1; + let score = 0; + for(let i = 0; i < appdata.length; i++) { + if(appdata[i].game === json.game && appdata[i].score > score) { + high = i; + score = appdata[i].score; + } + } + if(high !== -1) { // New Highscore + appdata[high].highscore = true; + } + } + + } else if(request.url === '/submit') { + let high = -1; + for(let i = 0; i < appdata.length; i++) { // Highscore Update + if(appdata[i].game === json.game && appdata[i].score < json.score) { + high = i; + appdata.highscore = false; + json.highscore = true; + } else if(appdata[i].game === json.game && appdata[i].score > json.score) { + json.highscore = false; + break; + } + } + if(high === -1) { + json.highscore = true; + } + appdata.push(json); + } else if(request.url === '/modify') { + let modifyLocation = -1; + for(let i = 0; i < appdata.length; i++) { + if(appdata[i].id === json.id) { // ID Matches + appdata[i] = json; + modifyLocation = i; + console.log("Modify complete"); + break; + } + } + if(json.highscore === true) { // Check if highscore is maintained + let high = -1; + let score = json.score; + for(let i = 0; i < appdata.length; i++) { + if(appdata[i].game === json.game && appdata[i].score > score) { + high = i; + score = appdata[i].score; + } + } + if(high !== -1) { // Lost Highscore + appdata[modifyLocation].highscore = false; + appdata[high].highscore = true; + } + } else { // Check if highscore is gained (either there is a highscore, or there isn't) + let high = -1; + for(let i = 0; i < appdata.length; i++) { + if(appdata[i].game === json.game && appdata[i].highscore === true) { + high = i; + if(appdata[i].score < json.score) { + appdata[i].highscore = false; + appdata[modifyLocation].highscore = true; + } + } + } + if(high === -1) { // No highscore in dataset, thus we're now the highscore + appdata[modifyLocation].highscore = false; + appdata[high].highscore = true; + } + } + } response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) response.end() @@ -69,4 +154,4 @@ const sendFile = function( response, filename ) { }) } -server.listen( process.env.PORT || port ) +server.listen( process.env.PORT || port ) \ No newline at end of file