diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..65ddb07f --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ + +node_modules/.bin/mime +node_modules/.bin/mime.cmd +node_modules/.bin/mime.ps1 +node_modules/.package-lock.json +node_modules/mime/CHANGELOG.md +node_modules/mime/cli.js +node_modules/mime/index.js +node_modules/mime/LICENSE +node_modules/mime/lite.js +node_modules/mime/Mime.js +node_modules/mime/package.json +node_modules/mime/README.md +node_modules/mime/types/other.js +node_modules/mime/types/standard.js +package-lock.json diff --git a/README.md b/README.md index 493995c1..7e9c3cb4 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,54 @@ Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js === -Due: September 11th, by 11:59 AM. +Nicholas Heineman +https://webware-character-creator.glitch.me/ -This assignment aims to introduce you to creating a prototype two-tiered web application. -Your application will include the use of HTML, CSS, JavaScript, and Node.js functionality, with active communication between the client and the server over the life of a user session. - -Baseline Requirements --- -There is a large range of application areas and possibilities that meet these baseline requirements. -Try to make your application do something useful! A todo list, storing / retrieving high scores for a very simple game... have a little fun with it. - -Your application is required to implement the following functionalities: - -- a `Server` which not only serves files, but also maintains a tabular dataset with 3 or more fields related to your application -- a `Results` functionality which shows the entire dataset residing in the server's memory -- a `Form/Entry` functionality which allows a user to add or delete data items residing in the server's memory -- a `Server Logic` which, upon receiving new or modified "incoming" data, includes and uses a function that adds at least one additional derived field to this incoming data before integrating it with the existing dataset -- the `Derived field` for a new row of data must be computed based on fields already existing in the row. -For example, a `todo` dataset with `task`, `priority`, and `creation_date` may generate a new field `deadline` by looking at `creation_date` and `priority` - -Your application is required to demonstrate the use of the following concepts: +## Character Manager -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 `
+ +
+ + + + + + + + +
+
+ + + + + + +
Era Name:Starting Year:Description:
+ + + + + +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/public/js/characterSheet.js b/public/js/characterSheet.js new file mode 100644 index 00000000..fb768007 --- /dev/null +++ b/public/js/characterSheet.js @@ -0,0 +1,211 @@ +const addCharacter = async function (event) { + + + event.preventDefault() + + const nameInput = document.querySelector('#characterName'); + const startInput = document.querySelector('#characterStart'); + const endInput = document.querySelector('#characterEnd'); + + const errorMsg = document.getElementById("characterErrorMessage") + + + //name duplicate check preprocessing + const characterTable = document.getElementById("characterTable"); + const nameArray = []; + + const rows = characterTable.getElementsByTagName("tr"); + + for (let i = 1; i < rows.length; i++) { + const cell = rows[i].getElementsByTagName("td")[0]; + nameArray.push(cell.textContent.trim()); + } + if (nameArray.includes(nameInput.value)) { + errorMsg.textContent = "Name must be unique" + errorMsg.style.display = "block"; + } else if (isNaN(startInput.value) || isNaN(endInput.value)) { + errorMsg.textContent = "Enter a numerical value for birth and death" + errorMsg.style.display = "block"; + } else { + + document.getElementById("characterErrorMessage").style.display = "none"; + + const json = { name: nameInput.value, start: startInput.value, end: endInput.value, era: "" }, body = JSON.stringify(json) + + const response = await fetch('/submit', { + method: 'POST', + body + }) + + const data = await response.json() + + console.log('text:', data) + CreateCharacterTable(data); + } + +} + +function CreateCharacterTable(data) { + + const characterTable = document.getElementById("characterTable"); + characterTable.innerHTML = ""; + characterTable.append(CreateHeaderRow()); + for (let i = 0; i < data.length; i++) { + characterTable.append(CreateRow(data[i].name, data[i].start, data[i].end, data[i].era, i)); + } +} + +function CreateHeaderRow() { + let row = document.createElement("tr"); + row.append(CreateHeaderCell("Name")); + row.append(CreateHeaderCell("Birth")); + row.append(CreateHeaderCell("Death")); + row.append(CreateHeaderCell("Eras")); + row.append(CreateHeaderCell("Delete/Modify")); + return row; +} + +function CreateHeaderCell(cellInfo) { + const cell = document.createElement('th'); + cell.innerHTML = `

${cellInfo}

`; + return cell; +} + +function CreateCell(cellInfo) { + const cell = document.createElement('td'); + cell.innerHTML = `

${cellInfo}

`; + return cell; +} + +function CreateRow(name, start, end, era, id) { + let row = document.createElement("tr"); + row.append(CreateCell(name)); + row.append(CreateCell(start)); + row.append(CreateCell(end)); + row.append(CreateCell(era)); + row.append(CreateDeleteAndModifyButton(name,id)); + return row; +} + +async function DeleteRow(name) { + console.log("Delete Row") + const response = await fetch("/characterData", { + method: "DELETE", + body: name + }) + + const output = await response.json() + + + CreateCharacterTable(output); +} + +async function ModifyRow(id) { + + const nameInput = document.querySelector('#characterName'); + const startInput = document.querySelector('#characterStart'); + const endInput = document.querySelector('#characterEnd'); + + const errorMsg = document.getElementById("characterErrorMessage") + + + //name duplicate check preprocessing + const characterTable = document.getElementById("characterTable"); + const nameArray = []; + + const rows = characterTable.getElementsByTagName("tr"); + + for (let i = 1; i < rows.length; i++) { + const cell = rows[i].getElementsByTagName("td")[0]; + nameArray.push(cell.textContent.trim()); + } + + const location = nameArray.indexOf(nameArray[id]); + if (nameArray.includes(nameInput.value) && location != id) { + errorMsg.textContent = "Name must be unique" + errorMsg.style.display = "block"; + } else if (isNaN(startInput.value) || isNaN(endInput.value)) { + errorMsg.textContent = "Enter a numerical value for birth and death" + errorMsg.style.display = "block"; + } else { + document.getElementById("characterErrorMessage").style.display = "none"; + + console.log("Delete Row") + fetch("/characterData", { + method: "DELETE", + body: nameArray[id] + }) + + + for (let i = 1; i < rows.length; i++) { + const cell = rows[i].getElementsByTagName("td")[0]; + if (cell.textContent === name) { + rows[1].innerHTML = ""; + break; + } + } + + const json = { name: nameInput.value, start: startInput.value, end: endInput.value, era: "" }, body = JSON.stringify(json) + + const response = await fetch('/submit', { + method: 'POST', + body + }) + + const data = await response.json() + + console.log('text:', data) + CreateCharacterTable(data); + } + + + + +} +function CreateDeleteAndModifyButton(jsonString, id) { + const cell = document.createElement('td'); + cell.className = "delete"; + + const deleteButton = document.createElement('button'); + deleteButton.className = "delete-button" + id; + deleteButton.innerHTML = '

X

'; + deleteButton.onclick = () => { + DeleteRow(jsonString); + } + + const modifyButton = document.createElement('button'); + modifyButton.className = "modify-button" + id; + modifyButton.innerHTML = '

Modify

'; + modifyButton.onclick = () => { + ModifyRow(id); + } + + cell.append(deleteButton); + cell.append(modifyButton); + return cell; +} + +window.addEventListener('load', async function () { + + const button = document.getElementById("addCharacterButton"); + button.onclick = addCharacter; + + const response = await fetch('/characterData', { + method: 'GET' + }) + + const data = await response.json(); + CreateCharacterTable(data); + + + +}) + +window.addEventListener('updateCharacters', async function handleUpdateCharacters() { + const response = await fetch('/characterData', { + method: 'GET' + }) + + const data = await response.json(); + CreateCharacterTable(data); +}); diff --git a/public/js/main.js b/public/js/main.js index a569258f..4bf6ba29 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -21,7 +21,25 @@ const submit = async function( event ) { console.log( 'text:', text ) } -window.onload = function() { - const button = document.querySelector("button"); - button.onclick = submit; -} \ No newline at end of file +function openTab(evt, tabName){ + + let i, tabContent, tabLinks; + + //hide all content + tabContent = document.getElementsByClassName("tabContent"); + for(i=0; i { + let text = "timelineItem" + i; + const tempButton = document.getElementById(text).outerHTML=""; + let json = JSON.stringify(data[i]); + fetch( "/timelineData", { + method:"DELETE", + body: json + }) + //account for in backend + console.log(tempButton.id); + window.dispatchEvent(updateCharactersEvent) + + } + + modifyButton.onclick = () => { + + //TODO: create error checking features + let text = "timelineItem" + i; + + const eraInput = document.querySelector('#era'); + const dateInput = document.querySelector('#date'); + const descriptionInput = document.querySelector('#description'); + + const errorMsg = document.getElementById("timelineErrorMessage") + if (isNaN(dateInput.value)) { + errorMsg.style.display = "block"; + } else{ + const tempButton = document.getElementById(text).outerHTML=""; + let json = JSON.stringify(data[i]); + fetch( "/timelineData", { + method:"DELETE", + body: json + }) + //account for in backend + console.log(tempButton.id); + window.dispatchEvent(updateCharactersEvent) + addTimelineItem(event); + } + + + + + + } + timelineItem.appendChild(deleteButton); + timelineItem.appendChild(modifyButton); + + timelineItem.className = "container"; + const innerItemText = document.createElement("div"); + innerItemText.className = "content"; + innerItemText.innerHTML = "

" + data[i].era + "

" + data[i].date + "

" + data[i].description + "

" + timelineItem.appendChild(innerItemText); + + + timeline.appendChild(timelineItem); + } +} + + + +window.onload = async function () { + const button = document.getElementById("addTimelineItemButton"); + button.onclick = addTimelineItem; + + const response = await fetch('/timelineData', { + method: 'GET' + }) + + const data = await response.json(); + CreateTimeline(data); +} + +const updateCharactersEvent = new CustomEvent('updateCharacters', { }); \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index 9ac27fb8..6f0eb665 100644 --- a/server.improved.js +++ b/server.improved.js @@ -1,3 +1,5 @@ +const { time } = require('console') + const http = require( 'http' ), fs = require( 'fs' ), // IMPORTANT: you must run `npm install` in the directory for this assignment @@ -14,24 +16,95 @@ const appdata = [ { 'model': 'ford', 'year': 1987, 'mpg': 14} ] +let characterData =[ + {'name': 'Aragorn', 'start': 1065, 'end' : 1403, 'era':""} +] + +let timelineData = [ + {'era': 'First Age', 'date': 1000, 'description': 'The beginning'}, + {'era': 'Second Age', 'date': 1567, 'description': 'The defeat of the witch-king of Angmar'}, + {'era': 'The Space Age', 'date': 2552, 'description': 'The Fall of Reach'} +] + const server = http.createServer( function( request,response ) { if( request.method === 'GET' ) { handleGet( request, response ) }else if( request.method === 'POST' ){ handlePost( request, response ) + } else if(request.method === 'DELETE'){ + handleDelete( request, response) } }) +const handleDelete = function( request, response) { + if(request.url === '/timelineData'){ + console.log("Handle Delete"); + let dataString = '' + + request.on( 'data', function( data ) { + dataString += data + }) + + console.log(dataString); + + request.on( 'end', function() { + let data = JSON.parse(dataString); + let index = -1; + + + index = timelineData.findIndex(item => + item.era === data.era && item.date === data.date && item.description === data.description + ); + + if (index > -1) { + timelineData.splice(index, 1); + } + RecheckCharacters(); + }) + } else if(request.url === '/characterData'){ + console.log("Handle Delete"); + let dataString = '' + + request.on( 'data', function( data ) { + dataString += data + }) + + console.log(dataString); + + request.on( 'end', function() { + let data = dataString + + + index = characterData.findIndex(item => + item.name === data ); + + if (index > -1) { + characterData.splice(index, 1); + } + console.log(characterData); + response.writeHead( 200, "OK", {'Content-Type': 'text/json' }) + response.end(JSON.stringify(characterData)); + }) + } +} + const handleGet = function( request, response ) { const filename = dir + request.url.slice( 1 ) - if( request.url === '/' ) { sendFile( response, 'public/index.html' ) + }else if (request.url === '/timelineData' ) { + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify(timelineData)); + }else if (request.url === '/characterData' ) { + RecheckCharacters(); + response.setHeader('Content-Type', 'application/json'); + response.end(JSON.stringify(characterData)); }else{ sendFile( response, filename ) } } + const handlePost = function( request, response ) { let dataString = '' @@ -40,12 +113,79 @@ const handlePost = function( request, response ) { }) request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) - // ... do something with the data here!!! + let value = JSON.parse( dataString ); + + if(value.hasOwnProperty('date')){ + timelineData.push(value) + SortTimeline() + + RecheckCharacters() + + response.writeHead( 200, "OK", {'Content-Type': 'text/json' }) + response.end(JSON.stringify(timelineData)) + } else if(value.hasOwnProperty('name')){ + + let character = AssignEra(value) + characterData.push(character); + RecheckCharacters(); + + + response.writeHead( 200, "OK", {'Content-Type': 'text/json' }) + response.end(JSON.stringify(characterData)) + } else{ + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.end('test') + } + + + }) +} + + + +//passed a json object with date and era, assigns era based on given timeline info, returns new json object + +function RecheckCharacters(){ + for(let i = 0; i < characterData.length; i++){ + AssignEra(characterData[i]); + } +} + +function AssignEra(value){ + value.era = "unknown" + + if(timelineData.length === 0){ + return; + } + for(let i = 0; i < timelineData.length - 1; i++){ + //check if incoming character is contained in each age + let total = value.end - value.start; + if( (value.start >= timelineData[i].date && value.start <= timelineData[i+1].date - 1) || (timelineData[i].date >= value.start && timelineData[i+1].date - 1 <= value.end) || (value.end >= timelineData[i].date && value.end <= timelineData[i+1].date - 1)){ + if(value.era === "unknown"){ + value.era = ""; + value.era += timelineData[i].era; + } else{ + value.era += ", " + timelineData[i].era; + } + } + } + + if((value.start >= timelineData[timelineData.length - 1].date) || (value.end >= timelineData[timelineData.length - 1].date)){ + if(value.era === "unknown"){ + value.era = ""; + value.era += timelineData[timelineData.length - 1].era; + } else{ + value.era += ", " + timelineData[timelineData.length - 1].era; + } + } + return value; +} - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) - response.end('test') +//sorts timeline by date +function SortTimeline(){ + timelineData.sort(function(a, b){ + return a.date - b.date; }) }