Javascript/HTML web server - using POST request to save user's list items

So I have a javascript/html program below that allows users to enter an item in a textbox and add the item to a checklist that appears under the textbox. They can also remove, highlight, and sort existing items. No duplicate items are allowed to be added.

The application itself works fine but I’m tasked with transferring this program to an HTTP server that handles requests using XMLHttpRequest. The code I’m struggling with is the todo-server.js file.

Task:

I’m struggling with a task where I’m supposed to modify the Add Item button click handler so that the new item name is added to the server’s to-do list data. To do this, my client Javascript will have to make an asynchronous request to the server and include the item data inside the request body, and this is to be done using a POST request. I’m basically using a GET /list request to retrieve the list data and a POST /list request to create a new item within that list data. I think I did the GET /list request correctly, but the POST /list request does not seem to be working.

After the Add button is pressed, I’m not supposed to add the new item to the client’s page until it confirms the request was successful. My approach to doing this was to use the status code in the response from the server where if there is an error that prevents the item from being added, I display an alert that says “Try again!” to the user so that they know to retry their request. I don’t know if I’m putting the alert in the correct place and would appreciate some input on this.

Testing:

I was told that to test this functionality, I’m supposed to load the to-do list page in my browser, add a new item, and then refresh the page. Since the client initializes the page by requesting the list data
from the server, any new items should persist after the refresh instead of the list resetting to its original state every time the page loaded. I tested this out and my items never save whenever I refresh the page, so I assume there’s something wrong with my POST request.

I would appreciate some guidance or a push in the right direction for getting this to work properly!

To test the server, I run node todo-server.js in cmd and then open my chrome browser and type localhost:3000 in the address bar.

todo.js

let highlightColor = "yellow";

//The array to store all items in the list
let items = []

function init(){
	//Initialize the event handlers
	document.getElementById("additem").addEventListener("click", addItem);
	document.getElementById("removeitem").addEventListener("click", removeItem);
	document.getElementById("highlight").addEventListener("click", highlightItems);
	document.getElementById("sort").addEventListener("click", sortItems);
	
	renderList(); //call function to fill in the list div
}

//Returns true if an item with that name exists
function isDuplicate(itemName){
	for(let i = 0; i < items.length; i++){
		if(items[i].name === itemName){
			return true;
		}
	}
	return false;
}

function addItem(){
	//Verify an item name was entered
	let itemName = document.getElementById("itemname").value;
	if(itemName.length == 0){
		alert("You must enter an item name.");
		return;
	}
	
	//If it is not a duplicate
	if(!isDuplicate(itemName)){
		//Add a new object to the items array and render
		items.push({name: itemName, light: false, checked: false});
		renderList();
	}else{
		alert("Duplicate item names not allowed.");
	}
}

//Removes selected items
//Strategy is actually to build a new array of items to keep
//Then re-assign the items array to this new array
function removeItem(){
	let newItems = [];
	items.forEach(elem =>{
		//If an item isn't checked, we want to keep it
		if(!elem.checked){
			newItems.push(elem);
		}
	});
	items = newItems;
	renderList();
}

//Toggles highlight of selected items
function highlightItems(){
	items.forEach(elem =>{
		//If the item is checked, toggle its light property
		if(elem.checked){
			elem.light = !elem.light;
		}
	});
	renderList();
}

//Sort the array, render the list again
function sortItems(){
	items.sort(function(a,b){
		if(a.name < b.name){
			return -1;
		}else if(a.name > b.name){
			return 1;
		}else{
			return 0;
		}
	})
	renderList();
}

function toggleCheck(){
	//'this' refers to the calling object
	//In this case, the checkbox that was clicked
	//We saved the 'value' property with the item name
	let itemName = this.value;
	items.forEach(elem => {
		if(elem.name === itemName){
			elem.checked = !elem.checked;
			renderList();
			return;
		}
	});
}

//Creates new items list HTML and replaces the old HTML
function renderList(){
	let highlightColor = "yellow";
	
	//Create a new div to hold the list
	//This will replace the old one
	let newList = document.createElement("div");
	newList.id = "list";
	
	//For each item in the array of items
	items.forEach(elem => {
		//Create a new div to be child of 'list' div
		//Set highlighting based on property of item
		let newDiv = document.createElement("div");
		if(elem.light){
			newDiv.style.backgroundColor = highlightColor
		}
		
		//Create and add the new checkbox
		let newItem = document.createElement("input");
		newItem.type = "checkbox";
		newItem.value = elem.name;
		newItem.id = elem.name;
		newItem.checked = elem.checked
		newItem.onclick = toggleCheck;
		newDiv.appendChild(newItem);
	
		//Create and add the new text node (the item name)
		let text = document.createTextNode(elem.name);
		newDiv.appendChild(text);
	
		//Add newly created div to children of list div
		newList.appendChild(newDiv);
	});
	
	let origList = document.getElementById("list");
	origList.parentNode.replaceChild(newList, origList);
}

todo.html

<html>
	<head><title>To-Do List</title></head>

	<body onload="init()">
		<div>Item Name: <input type="text" id="itemname" /> <button type="button" id="additem">Add Item</button></div><br/>
		
		<div id="list"></div>

		<div>
			<button type="button" id="removeitem">Remove Selected</button>
			<button type="button" id="highlight">Highlight/Unhighlight</button>
			<button type="button" id="sort">Sort Items</button>
		</div>
		<script src="todo.js"></script>
	</body>
<html>

todo-server.js:

const http = require('http');
const fs = require("fs");
		

let items = [					
	{name: "test"}
]
function send404(response){
	response.statusCode = 404;
	response.write("Unknown resource.");
	response.end();
}

function send500(response){
	response.statusCode = 500;
	response.write("Server error.");
	response.end();
}

const server = http.createServer(function (request, response) {
	console.log(request.url);
	if(request.method === "GET"){
		if(request.url === "/" || request.url === "/todo.html"){
			fs.readFile("todo.html", function(err, data){
				if(err){
					response.statusCode = 500;
					response.write("Server error.");
					response.end();
					return;
				}
				response.statusCode = 200;
				response.setHeader("Content-Type", "text/html");
				response.write(data);
				response.end();
			});
		}else if(request.url === "/todo.js"){
			fs.readFile("todo.js", function(err, data){
				if(err){
					response.statusCode = 500;
					response.write("Server error.");
					response.end();
					return;
				}
				response.statusCode = 200;
				response.setHeader("Content-Type", "application/javascript");
				response.write(data);
				response.end();
			});

		}else if(request.url === "/list"){
			fs.readFile("todo.js", function(err, data) {
				if (err) {
					response.statusCode = 500;
					response.write("Server error.");
					response.end();
					return;
				}
				response.statusCode = 200;
				response.setHeader("Content-Type", "application/javascript");
				response.write(data);
				response.end();
			});
		
		}else{
			response.statusCode = 404;
			response.write("Unknwn resource.");
			response.end();
		}
	}else if(request.method === "POST"){
		if(request.url === "/list"){
			let body = "";
			request.on('data', (chunk) => {
				body += chunk;
			})
			request.on('end', () => {
				let newItem = JSON.parse(body);
				if(newItem.hasOwnProperty("itemname")){
					items.push(newItem);
					response.statusCode = 201;
					response.write(String(newItem.name));
					response.end();
					return;
				}else{
					send404(response);
					alert("Try again!");
				}
			})
		}else{
			send404(response);
			alert("Try again!");
		}
	}
});

server.listen(3000);
console.log('Server running at http://127.0.0.1:3000/');

Hi,
I’ve made some changes on both clientside (todo.js) and serverside (todo-server.js) . The html part is the same.

todo.js:

let highlightColor = "yellow";

//The array to store all items in the list
let items = [];

function init() {
  document.getElementById("additem").addEventListener("click", addItem);
  document.getElementById("removeitem").addEventListener("click", removeItem);
  document
    .getElementById("highlight")
    .addEventListener("click", highlightItems);
  document.getElementById("sort").addEventListener("click", sortItems);
  getItems();
}

function getItems() {
  let oReq = new XMLHttpRequest();
  oReq.addEventListener("load", onGettingItems);
  oReq.open("GET", "http://127.0.0.1:3000/list");
  oReq.send();
}

function onGettingItems(evt) {
  if (this.readyState === XMLHttpRequest.DONE) {
    if (this.status === 200) {
      items = JSON.parse(evt.currentTarget.response);
      renderList();
    } else {
      console.log(evt, "Something went wrong");
    }
  }
}

function postItem(item) {
  var oReq = new XMLHttpRequest();
  oReq.open("POST", "http://127.0.0.1:3000/list", true);
  oReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  oReq.addEventListener("readystatechange", onPostItem);
  oReq.send(JSON.stringify(item));
}

function onPostItem(evt) {
  if (this.readyState === XMLHttpRequest.DONE) {
    if (this.status === 200) {
      items.push(JSON.parse(evt.currentTarget.response));
      renderList();
    } else {
      console.log(evt, "Something went wrong or item is duplicated");
    }
  }
}

function addItem() {
  let itemName = document.getElementById("itemname").value;
  postItem({ name: itemName, light: false, checked: false });
}

function removeItem() {
  const itemsToRemove = [];
  items.forEach((item) => {
    if (item.checked) {
      itemsToRemove.push(item.name);
    }
  });

  const oReq = new XMLHttpRequest();
  oReq.open("POST", "http://127.0.0.1:3000/remove", true);
  oReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  oReq.addEventListener("readystatechange", onItemsRemoved);
  oReq.send(JSON.stringify(itemsToRemove));
}

function onItemsRemoved(evt) {
  if (this.readyState === XMLHttpRequest.DONE) {
    if (this.status === 200) {
      items = JSON.parse(evt.currentTarget.response);
      renderList();
    } else {
      console.log(evt, "Server error");
    }
  }
}

//Toggles highlight of selected items
function highlightItems() {
  items.forEach((elem) => {
    //If the item is checked, toggle its light property
    if (elem.checked) {
      elem.light = !elem.light;
    }
  });
  renderList();
}

//Sort the array, render the list again
function sortItems() {
  items.sort(function (a, b) {
    if (a.name < b.name) {
      return -1;
    } else if (a.name > b.name) {
      return 1;
    } else {
      return 0;
    }
  });
  renderList();
}

function toggleCheck() {
  //'this' refers to the calling object
  //In this case, the checkbox that was clicked
  //We saved the 'value' property with the item name
  let itemName = this.value;
  items.forEach((elem) => {
    if (elem.name === itemName) {
      elem.checked = !elem.checked;
      renderList();
      return;
    }
  });
}

//Creates new items list HTML and replaces the old HTML
function renderList() {
  let highlightColor = "yellow";

  //Create a new div to hold the list
  //This will replace the old one
  let newList = document.createElement("div");
  newList.id = "list";

  //For each item in the array of items
  items.forEach((elem) => {
    //Create a new div to be child of 'list' div
    //Set highlighting based on property of item
    let newDiv = document.createElement("div");
    if (elem.light) {
      newDiv.style.backgroundColor = highlightColor;
    }

    //Create and add the new checkbox
    let newItem = document.createElement("input");
    newItem.type = "checkbox";
    newItem.value = elem.name;
    newItem.id = elem.name;
    newItem.checked = elem.checked;
    newItem.onclick = toggleCheck;
    newDiv.appendChild(newItem);

    //Create and add the new text node (the item name)
    let text = document.createTextNode(elem.name);
    newDiv.appendChild(text);

    //Add newly created div to children of list div
    newList.appendChild(newDiv);
  });

  let origList = document.getElementById("list");
  origList.parentNode.replaceChild(newList, origList);
}

todo-server.js:

const http = require("http");
const fs = require("fs");
const { measureMemory } = require("vm");

let items = [
  { name: "Item 1", light: false, checked: false },
  { name: "Item 2", light: false, checked: false },
  { name: "Item 3", light: false, checked: false },
];

function sendError(response, code, message) {
  response.statusCode = code;
  response.write(message);
  response.end();
}

const server = http.createServer(function (request, response) {
  console.log(request.url);
  if (request.method === "GET") {
    if (request.url === "/" || request.url === "/todo.html") {
      fs.readFile("todo.html", function (err, data) {
        if (err) {
          sendError(response, 500, "Server error.");
        }
        response.statusCode = 200;
        response.setHeader("Content-Type", "text/html");
        response.write(data);
        response.end();
      });
    } else if (request.url === "/todo.js") {
      fs.readFile("todo.js", function (err, data) {
        if (err) {
          sendError(response, 500, "Server error.");
        }
        response.statusCode = 200;
        response.setHeader("Content-Type", "application/javascript");
        response.write(data);
        response.end();
      });
    } else if (request.url === "/list") {
      response.statusCode = 200;
      response.setHeader("Content-Type", "application/json");
      response.end(JSON.stringify(items));
    } else {
      sendError(response, 404, "Unknown resource.");
    }
  } else if (request.method === "POST") {
    if (request.url === "/list") {
      let body = "";
      request.on("data", (chunk) => {
        body += chunk;
      });
      request.on("end", () => {
        let newItem = JSON.parse(body);
        let filtered = items.filter((item) => {
          item.name === newItem.name;
        });
        if (!newItem.hasOwnProperty("name") || newItem.name === "") {
          sendError(response, 400, "Item has no name.");
        } else if (
          items.filter((item) => item.name === newItem.name).length > 0
        ) {
          sendError(response, 400, "Duplicate item.");
        } else {
          items.push(newItem);
          response.statusCode = 200;
          response.setHeader("Content-Type", "application/json");
          response.end(JSON.stringify(newItem));
        }
      });
    } else if (request.url === "/remove") {
      let body = "";
      request.on("data", (chunk) => {
        body += chunk;
      });
      request.on("end", () => {
        let itemsToRemove = JSON.parse(body);
        items = items.filter((item) => {
          let toRemove = true;
          for (let i = 0; i < itemsToRemove.length; i++) {
            if (item.name === itemsToRemove[i]) {
              toRemove = false;
              break;
            }
          }
          return toRemove;
        });
        response.statusCode = 200;
        response.setHeader("Content-Type", "application/json");
        response.end(JSON.stringify(items));
      });
    } else {
      sendError(response, 404, "Unknown resource.");
    }
  }
});

server.listen(3000);
console.log("Server running at http://127.0.0.1:3000/");

1 Like