Interested in this project?
Continue LearningGetting Started
In this project, we'll make our very own to-do list! This project will incorporate several JavaScript functions to add and remove tasks from our list.
Let's start by creating three files:
index.html
- for our HTML (also known as markup)style.css
- for stylingapp.js
- for the functionality (functions)
Our basic markup will consist of:
<html>
,<head>
,<title>
, and<body>
tags- linking the style.css and app.js files so that they can interact with the HTML
If you have played with html before, see if you can do this on your own!
Solution
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css" />
<title>
To-do List
</title>
</head>
<body>
</body>
<script src="app.js"></script>
</html>
If you are wondering about the <meta>
tags, those are to make sure your website looks nice on all devices. Check more about them here
You will see that your page is blank when you open it in your favorite browser. Let's add an element to it. (An element is something that is on your webpage)
We will add an input box, so that we can type in a todo. Try it out!
Solution
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css" />
<title>
To-do List
</title>
</head>
<body>
<!-- this is to enter in a todo -->
<input placeholder="What needs to be done?" />
</body>
<script src="app.js"></script>
</html>
The input tag is special because it closes itself like so <input />
. You will see a similar pattern with other tags like <img />
It has an attribute called placeholder
which allows you to put something in the input box in greyed out text before you start typing. Check it out in your browser.
Lastly, if we enter todos in, we need a place for them to go. We will use an unordered list to do that. An unordered list basically creates a list with bullets using the ul tag. Each bullet inside the unordered list gets created with an li tag
Try putting in just the <ul>
tag.
Solution
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css" />
<title>
To-do List
</title>
</head>
<body>
<!-- this is to enter in a todo -->
<input placeholder="What needs to be done?" />
<!-- this is to keep the list of todos -->
<ul></ul>
</body>
<script src="app.js"></script>
</html>
Just one last thing to do. We will need to refer to the input box and the unordered list when creating the functionality of this app. Let's give each of them ids.
Solution
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css" />
<title>
To-do List
</title>
</head>
<body>
<!-- this is to enter in a todo -->
<input id="input" placeholder="What needs to be done?" />
<!-- this is to keep the list of todos -->
<ul id="list"></ul>
</body>
<script src="app.js"></script>
</html>
Nice job, let's now move on to the functionality.
The function
To make our to-do list work, we need it to do three things at the minimum:
- add tasks on press of the enter key
- remove text in the input box when enter key is pressed
- remove tasks on click
How can we implement this? Well, we need to create a function that runs whenever the enter key is pressed.
document.body.onkeyup = function(e) {
console.log(e.keyCode);
};
Open this up in your browser and open up the console. Notice what happens every time you push a button on your keyboard.
Now try to make logging conditional on whether the enter key is pressed. Hint: use an if statement
Solution
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
}
};
Great! Now it will print "enter clicked!" to the console when you press enter. Let's make it do something more when you press enter.
Let's create a new function called newItem()
that logs on the console "Inside newItem".
Solution
function newItem() {
console.log("Inside newItem");
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
}
};
Let's make it so that every time you press enter, the newItem()
function happens.
Solution
function newItem() {
console.log("Inside newItem");
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
Everytime you press enter in your browser, you should see two messages in your console. The first will be "enter clicked!", the next line will be "Inside newItem".
Now lets get the value of what is inside the input box. We will store this text in a variable and console.log()
it.
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
}
Now, everytime you type something in the input box and press enter, you should see 3 lines of output. The two from before, and a third that is what's been typed in the input box.
Now we need to take this value and put it into our unordered list.
Lets start by storing the <ul>
tag from our html into a variable. Hint: use .getElementById()
Solution
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
// store the unordered list as a variable (now we can refer to it as "ul")
var ul = document.getElementById("list");
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
Let's now create a list item (<li>
tag) with .createElement()
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
// store the unordered list as a variable (now we can refer to it as "ul")
var ul = document.getElementById("list");
var li = document.createElement("li");
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
Now we are going to add the text to our newly created list item.
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
// store the unordered list as a variable (now we can refer to it as "ul")
var ul = document.getElementById("list");
var li = document.createElement("li");
// now put text in list item
li.appendChild(document.createTextNode("- " + item));
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
.createTextNode()
means bundle this text as a single thing (read node) that can now be appended to the list item.
A child
of a tag is something that is inside the tag.
Now, we have a list item with text inside of it. We need to put this list item (<li>
) in our unordered list (<ul>
) now. Try using .appendChild()
to do that.
Solution
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
// store the unordered list as a variable (now we can refer to it as "ul")
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("- " + item)); // now put text in list item
ul.appendChild(li); // put list item in our unordered list
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
You're most of the way there! Try it out in your browser. Everytime you type something in the box and press enter, you should have a list item show up!
Now, your text stays every time you type a todo and press enter. Let's clear it. Hint: use the same thing you used to set item
.
Solution
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
// store the unordered list as a variable (now we can refer to it as "ul")
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("- " + item)); // now put text in list item
ul.appendChild(li); // put list item in our unordered list
document.getElementById("input").value = ""; // erase what is currently in todo list
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
Cool! Check it out in the browser. Now let's get rid of a todo everytime we click on it. Let's create a function to remove something.
function removeItem(e) {
e.target.remove()
}
So, what's happening here? When this function gets called on an element (read tag) the tag gets stored into the variable e.target
. The e
variable is an object that contains the tag. Then we simply remove()
e.target
.
Now lets use this function:
function newItem() {
console.log("Inside newItem");
var item = document.getElementById("input").value;
console.log(item);
// store the unordered list as a variable (now we can refer to it as "ul")
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("- " + item)); // now put text in list item
ul.appendChild(li); // put list item in our unordered list
document.getElementById("input").value = ""; // erase what is currently in todo list
li.onclick = removeItem; // run removeItem when the li is clicked
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
console.log("enter clicked!");
newItem();
}
};
function removeItem(e) {
e.target.remove()
}
That's everything! Nice job.
Let's make it look a bit nicer now.
Stylin' it up
Right now, all we have is an ugly bulleted list. Here are some styles to make it look nicer. Notice how the CSS Selectors work. However, feel free to experiment with the styling yourself.
Copy these styles into your style.css
file.
html {
font-family: "Avenir Next", Helevetica, sans-serif;
text-align: center;
}
body {
max-width: 500px;
margin: 0 auto;
}
input {
padding-top: 30px;
width: 500px;
height: 60px;
font-size: 40px;
border: 0;
}
input:focus {
outline: none;
}
li {
text-align: left;
font-size: 40px;
list-style: none;
margin: 0;
}
li:hover {
text-decoration: line-through;
}
Hope you had fun building this! See if you can style it better! Post your finished project in the discord.
What Next?
See if you can make the todos stay when you refresh the page with localstorage.
Also try making it your newtab page with a chrome extension!
Appendix
Adding localStorage by @oliver10852
For local storage, there are a few moving parts. To understand how this works fully with local storage, we should make the distinction between the frontend and backend. Right now, our newItem() function creates the HTML that the user sees on the frontend. Now, we will dive into working on the backend using the localStorage object built into Javascript.
localStorage Review
Before we begin, here is a good resource on localStorage I found after some research in case you want a more in-depth explanation. Here is another resource that you should read on about dictionaries.
For this extension of the to-do list, all you need to know is that the localStorage object has a function called setItem() that takes in two inputs, a key and a value (By key and value, think of a regular English dictionary. The key is the word, and the value is the definition of that word. Another way to think about it is by having a key, you can unlock the value).
localStorage.setItem("key", "value")
If we want to store Javascript arrays or objects, we have to convert them to strings for the setItem() function to work. Note (IMPORTANT): the setItem() function replaces the pre-existing value associated with that key. For our project, the array we will be storing our to-do list information will be called data. We do that by using the following function which converts a Javascript object to a JSON string:
JSON.stringify(<insert array/object name>);
localStorage.setItem("key", JSON.stringify(data));
If we want to get what is currently in storage, we first pass in the name of the key to unlock what's in the storage. We'll be calling it StorageKey, but it can be named anything you want.
localStorage.getItem("StorageKey");
When we run localStorage.getItem, it actually is going to return a JSON object. We know how to go from Javascript → JSON, but how do we go from JSON → Javascript? We use the following function:
let variable = JSON.parse(<insert JSON object>);
Now that we know how localStorage works, we will be working from the beginning of the process and editing the old to-do list code to make it work with our new localStorage functionality.
Let's get to coding!
The first thing we want to do is get the actual storage. We'll choose to use StorageKey as the key name and store it into a variable called storage (Note: you can use any key name here, just keep it consistent). Then, there are two cases we have to check for.
- If storage is not empty, we want to get the data in the form of a Javascript object named data so we can use it. Then, we want to load that data using a function called loadData(). We also want to prepare a variable id that will essentially act as the index or location of the next to-do in the data array.
Hint
// Let's say this is our current array.
array = [0,1,2,3,4]
// How can we return the index of the next number in the sequence (5)?
// We can use the .length property of arrays.
array.length will return **5**.
Solution
// Use the StorageKey to access the data
var storage = localStorage.getItem('StorageKey');
//if storage is not empty/if there is something in storage
if(storage !== null){
//converting JSON String -> Javascript object
var data = JSON.parse(storage);
// load all data from the backend to the frontend.
loadData(data);
//preparing the next todo item's index
var id = data.length;
}
- We then want to check if the storage is empty. If storage is empty, initialize id to 0 and the data array to an empty array.
Solution
// Use the StorageKey to access the data
var storage = localStorage.getItem('StorageKey');
//if storage is not empty/if there is something in storage
if(storage !== null){
//converting JSON String -> Javascript object
var data = JSON.parse(storage);
// load all data from the backend to the frontend.
loadData(data);
//preparing the next todo item's index
var id = data.length;
}
// if there is nothing in storage
else{
// initialize the id to 0 and initialize data
id = 0;
data = [];
}
You may be wondering why we did not create the loadData() function yet! Don't worry, we will come back to this way later.
Let's return to this function that we wrote in our earlier to-do list:
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
newItem();
}
};
We want to edit it such that when the user hits enter, it grabs the text of the input box and saves it in a variable named todo.
Solution
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
**var todo = document.getElementById("input").value;**
newItem();
}
};
// I just googled: "get value of input box javascript"
// https://www.w3schools.com/jsref/prop_text_value.asp
We also want to add the following information to each to-do.
Trash boolean (True or False) to signify if a to-do has been clicked. When we add a new item, it will be set to false by default because it is not in the trash yet.
Id of that to-do so we know where in the data array it is when we delete it.
We want to edit the newItem() function such that it takes into account these variables as well. We want to create a dictionary for each to-do that contains the name of the to-do, whether or not it is in the trash, and the id/index of the to-do. The keys and value pairs (key, value) will be as follows: (name: todo), (trash: false), (id: id).
Solution
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
var todo = document.getElementById("input").value;
// adding the to-do to the front end with our new information
newItem(todo, false, id);
// adding it to the back end
data.push({
name: todo,
trash: false,
id: id
});
}
};
Great, once the user hits enter the information about the to-do is added both to the front-end and back-end! But wait, we updated the data array. But did we update the localStorage? Let's update that to match our data array. Let's set our localStorage to a key called StorageKey.
Review the section above regarding how to set localStorage.
Solution
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
// get the text of the input box after the user hits enter
var todo = document.getElementById("input").value;
// adding the to-do to the front end with our new information
newItem(todo, false, id);
// adding it to the back end
data.push({
name: todo,
trash: false,
id: id
});
// set storage equal to newest changes
**localStorage.setItem("StorageKey", JSON.stringify(data))**
}
};
Now that we have added a to-do, we need to increment the id to set up for the next to-do list item to be added.
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
var todo = document.getElementById("input").value;
// adding the to-do to the front end with our new information
newItem(todo, false, id);
// adding it to the back end
data.push({
name: todo,
trash: false,
id: id
});
id = id + 1;
}
};
Code Checkpoint #1
So far, we have created a structure for retrieving storage and have edited our event listener for when the user hits the enter key.
// Use the StorageKey to access the data
var storage = localStorage.getItem('StorageKey');
//if storage is not empty/if there is something in storage
if(storage !== null){
//converting JSON String -> Javascript object
var data = JSON.parse(storage);
// load all data from the backend to the frontend.
loadData(data);
//preparing the next todo item's index
var id = data.length;
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
var todo = document.getElementById("input").value;
// adding the to-do to the front end with our new information
newItem(todo, false, id);
// adding it to the back end
data.push({
name: todo,
trash: false,
id: id
});
id = id + 1;
}
};
Now, let's return to our newItem() function. We need to fix the parameters of this function to match the changes that we just made above (adding the todo, trash, and id).
Solution
function newItem(todo, trash, id) {
//code from to-do list tutorial
}
We then want to check if the trash is true. If is true, we want to return nothing, or in other words do nothing. We also should get rid of the following line:
var item = document.getElementById("input").value;
We don't need this line anymore since we just did that in our enter key event listener function.
Solution
function newItem(todo, trash, id) {
// if it's in the trash, do absolutely nothing
if (trash == true) {
return;
}
}
Let's take a look at the rest of the code that we had created from before combined with what we have now. We should also change the item variable name to todo since that is the new variable name we are using.
// function that creates the front-end HTML
function newItem(todo, trash, id) {
// if it's in the trash, do absolutely nothing
if(trash == true){
return;
}
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("-" + todo));
ul.appendChild(li);
// reset whatever is in the box. don't want last to do's text to be in there.
todo = document.getElementById("input").value = "";
// li.onclick --> event listener, removeItem is telling it what to do when you click
li.onclick = removeItem;
}
We now want to set an attribute of id of the li tag to the value of the id variable we initialized in our storage if statements. Where should we do this?
Hint
We want to set the attribute once we are done creating the <li>
HTML element.
https://www.bitdegree.org/learn/setattribute-javascript
Solution
// function that creates the front-end HTML
function newItem(todo, trash, id) {
// if it's in the trash, do absolutely nothing
if(trash == true){
return;
}
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("-" + todo));
// for the li tag, set an attribute named id, set the value equal to id variable
**li.setAttribute('id', id);**
ul.appendChild(li);
// reset whatever is in the box. don't want last to do's text to be in there.
todo = document.getElementById("input").value = "";
// li.onclick --> event listener, removeItem is telling it what to do when you click
li.onclick = removeItem;
}
Code Checkpoint #2
// Use the StorageKey to access the data
var storage = localStorage.getItem('StorageKey');
//if storage is not empty/if there is something in storage
if(storage !== null){
//converting JSON String -> Javascript object
var data = JSON.parse(storage);
// load all data from the backend to the frontend.
loadData(data);
//preparing the next todo item's index
var id = data.length;
}
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
var todo = document.getElementById("input").value;
// adding the to-do to the front end with our new information
newItem(todo, false, id);
// adding it to the back end
data.push({
name: todo,
trash: false,
id: id
});
id = id + 1;
}
};
// function that creates the front-end HTML
function newItem(todo, trash, id) {
// if it's in the trash, do absolutely nothing
if(trash == true){
return;
}
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("-" + todo));
// for the li tag, set an attribute named id, set the value equal to id variable
**li.setAttribute('id', id);**
ul.appendChild(li);
// reset whatever is in the box. don't want last to do's text to be in there.
todo = document.getElementById("input").value = "";
// li.onclick --> event listener, removeItem is telling it what to do when you click
li.onclick = removeItem;
}
Let's return to the last line of the newItem() function. The onclick event listener function for the li tag should we updated.
We first want to remove the HTML element like before. But this time, we also need to know the id of that specific HTML element we click (Remember, we just set this above!)
Solution
function removeItem(event) {
// get the html code for what you have clicked
element = event.target;
//remove the html element after you click it
element.remove();
}
//NOTE: element.id gives us the value of the id attribute
Now instead of just simply removing the targeted HTML element, we should locate the todo in the data array by using the id we fetch from the HTML and set the value of the trash property for that todo to true. We should also make sure to make the localStorage and data variable match by setting the localStorage again.
function removeItem(event) {
// THIS IS ALL FRONT END BELOW
// get the html code for what you have clicked
element = event.target;
//remove the html element after you click it
element.remove();
// THIS IS BACKEND BELOW
// get the HTML id, find the trash in the backend, set trash property in data to true.
data[element.id].trash = true;
// set storage equal to newest changes
localStorage.setItem("StorageKey", JSON.stringify(data));
}
Lastly, let's go back and finally work on that loadData() function. Essentially what the loadData function is going to do is just run the newItem() function for each todo to display what is in storage on the screen in HTML format. We are going to be using the forEach function, which is special to Javascript to make this process easier for us.
function loadData(array) {
array.forEach(function(todo) {
newItem(todo.name, todo.trash, todo.id);
});
}
Okay, woah. There's a lot going on here. Let's break it down. First, we are looking to create a function that takes in an array. The array that we have is data, which is an array of several dictionaries that have information about each todo.
function loadData(array) {}
Now, we want to say for each todo in the data array, we want to run the newItem() function. But to use this function, we need to pass in each todo's name, whether or not it is in the trash, and the id of that todo. We can access the values of those keys in the each todo by using the dot operator because it is a dictionary. (Note: You can also use brackets[] if you're more familiar with python, which is also mentioned in the resource)
function loadData(array){
array.forEach(function(todo){
newItem(**todo.**name, **todo.**trash, **todo.**id);
});
}
Running Through the Code
Let's use a fictional data array so that we can understand what is going on behind the scenes.
// Imagine we have populated the data array by entering different todos
data = [{'name': 'Running', 'trash': false, 'id': 0},
{'name': 'Coding', 'trash': false, 'id': 1}
{'name': 'Eating', 'trash': false, 'id': 2}
]
// Let's take the first to-do as an example
// todo.name -> Running
// todo.trash -> false
// todo.id -> 0
newItem(todo.name, todo.trash, todo.id);
newItem("Running", false, 0);
// Now, Let's jump to the newItem() function!
// function that creates the front-end HTML
function newItem("Running", false**, 0) {
// if it's in the trash, do absolutely nothing
if(trash (false) == true){
return; // since trash is false, which is not true, we continue.
}
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("-" + "Running"));
// for the li tag, set an attribute named id, set the value equal to id variable
li.setAttribute('id', 0);
ul.appendChild(li);
// reset whatever is in the box. don't want last to do's text to be in there.
todo = document.getElementById("input").value = "";
// li.onclick --> event listener, removeItem is telling it what to do when you click
li.onclick = removeItem;
}
Phew! That was a lot of stuff. Let's bring it all back together and summarize what we did and how this works.
- First, we get the storage, and check if it contains anything.
If it contains stuff, we load the todos using the loadData() function and initialize the id for the next todo. If not, start over from scratch and initialize id and the data array.
We jump to the event listener for when the user hits enter. Once the user hits enter, we save that text and add it to the frontend by calling the newItem() function and push it to the backend data array. We also make sure that data matches localStorage.
The newItem() function takes in new parameters of todo, trash and id. We also added the id attribute with the value of the id variable we set in the storage if/else statement.
The removeItem() function fetches the id in the HTML element we just set in the newItem() function using the dot operator. This helps us to locate the todo in the data array.
Final Code Snippet
// Use the StorageKey to access the data
var storage = localStorage.getItem("StorageKey");
//if storage is not empty/if there is something in storage
if (storage !== null) {
//converting JSON String -> Javascript object
var data = JSON.parse(storage);
// load all data from the backend to the frontend.
loadData(data);
//preparing the next todo item's index
var id = data.length;
}
// if there is nothing in storage
else {
// initialize the id to 0 and initialize data
id = 0;
data = [];
}
// function to load the storage
function loadData(array) {
array.forEach(function(todo) {
newItem(todo.name, todo.trash, todo.id);
});
}
// when the user hits the enter key, run this function
document.body.onkeyup = function(e) {
if (e.keyCode == 13) {
// get the text of the input box after the user hits enter
var todo = document.getElementById("input").value;
// adding the todo to the front end with our new information
newItem(todo, false, id);
// adding it to the back end
data.push({
name: todo,
trash: false,
id: id
});
// set storage equal to newest changes
localStorage.setItem("StorageKey", JSON.stringify(data));
}
};
// function that creates the front-end HTML
function newItem(todo, trash, id) {
// if it's in the trash, do absolutely nothing
if (trash == true) {
return;
}
var ul = document.getElementById("list");
var li = document.createElement("li");
li.appendChild(document.createTextNode("-" + todo));
// for the li tag, set an attribute named id, set the value equal to id variable
li.setAttribute("id", id);
ul.appendChild(li);
// reset whatever is in the box. don't want last to do's text to be in there.
todo = document.getElementById("input").value = "";
// li.onclick --> event listener, removeItem is telling it what to do when you click
li.onclick = removeItem;
}
// function to remove the todo from the frontend and backend
function removeItem(event) {
// THIS IS ALL FRONT END BELOW
// get the html code for what you have clicked
element = event.target;
//remove the html element after you click it
element.remove();
// THIS IS BACKEND BELOW
// get the HTML id, find the trash in the backend, set trash property in data to true.
data[element.id].trash = true;
// set storage equal to newest changes
localStorage.setItem("StorageKey", JSON.stringify(data));
}
Comments (0)