Tutorial: Javascript ToDo list web application
- 11 minsThe Todo app has become the hello world of web applications. In this three part tutorial we’re going to create a simple web app using HTML5, CSS3 and JavaScript. We’re will utilise LocalStorage and AppCache to create an installable hosted web app that remembers the state of the users list and works offline. As you can probably guess, our app is aimed at mobile devices.
You can develop this app in any text editor and debug in any browser. I’m using sublime text and Firefox developer edition, testing it using the Firefox OS emulator.
For reference I’ve created a Gist of the the code we will produce throughout this tutorial.
Lets get started.
Project setup
First things first, we need to set up our project. Create a folder called todo and open this within your text editor. We need three files for this application, index.html, style.css and script.js. Go ahead and create those, open index.html
and add the following code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My todo app</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel='stylesheet' href="style.css" type='text/css' media='all' />
</head>
<body>
<header>
<h1>ToDo List</h1>
</header>
<main>
<p>Add things to do below</p>
<form>
<label for="inputItem" id="inputLabel"></label>
<input id="inputItem">
<button id="addItem">Submit</button>
</form>
<ul id="theList"></ul>
</main>
<script src="script.js"></script>
</body>
</html>
Nothing to fancy here. We have a form with an input area and a button as well as a empty list to insert out items. As were creating a mobile web app we’ve included the viewport
meta tag to make sure the app expands too, and stays at full screen on mobile devices. We’ve also included reference to the CSS and Javascript files. We will come to the JavaScript later, but for now lets add the basic styles to the style.css
file. Open that file and add the following.
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
h1,
main,
footer{
margin: 0 10px;
margin: 0 10px;
}
main,
footer{
margin: 0 10px;
}
html,
button{
font-family:'Verdana',Arial,sans-serif;
font-size:16px;
}
header{
height: 38px;
border-bottom: 2px solid #ccc;
margin-bottom: 10px;
}
p{
padding: 10px 0;
}
ul{
margin: 20px 0;
list-style: none;
}
li{
padding: 5px;
margin: 3px 0;
border-bottom: 2px solid #7cceee;
}
input{
padding:6px;
width: 100%;
border-radius: 5px;
margin-bottom: 10px;
font-size: 16px;
}
button{
box-shadow:rgba(0,0,0,0.2) 0 1px 0 0;
border-bottom-color:#333;
border:1px solid #61c4ea;
background-color:#7cceee;
border-radius:5px;
color:#333;
text-shadow:#b2e2f5 0 1px 0;
padding:5px 15px;
transition: 0.3s ease-in-out;
cursor:pointer;
}
button:hover{
transition: 0.7s ease-in-out;
background-color:#7cce00;
}
Again, nothing too fancy here. We’ve added some padding
and margin
to space things out and provided a full width input area. Transitions give simple feedback to the user when the button is pressed.
In practice you would need to provide better cross browser support than we have here. Transitions, for example, require prefixing in some older browsers. Using autoprefixer in your workflow can completely eliminate the need to ever think about prefixes. HTML elements such as main and header don’t work in IE8 and below. You can check what works where on the excellent caniuse.com.
Now for the fun stuff.
Adding items to the list
Before writing any JavaScript, we need to settle on some requirements for our application. I’ve identified four main requirements for the app which are:
- A user can add an item to the list.
- When an item from the list is clicked it will be marked as done.
- When a done item is clicked it will be removed.
- When a user leaves and returns to the app, it will resume in the same state.
Lets start on the functionality by getting reference to the dom items.
var theList = document.getElementById('theList'),
form = document.querySelector('form'),
input = form.querySelector('input');
We retrieved the elements by using either querySelector
or ID. <a title="query selector mdn article" href="https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector">document.querySelector()</a>
returns the first element from the document that matches the specified selector. Now we have reference to the form we can attach an event listener, which will sit quietly in the background waiting for the forms submit action.
Below your variable declarations add the following.
form.addEventListener('submit', function(ev){
"use strict";
ev.preventDefault();
});
The line "use strict"
forces us to write ECMAScript version 5 compliant JavaScript. You can read more about strict mode here. Use of strict is a personal preference. There are consequences to using it in general, so feel free to omit it if necessary. The line ev.preventDefault()
stops the browser from performing its default action, in this case refreshing with every submission.
It’s time to get some input from the user. On the next free line add:
var value = input.value;
Pretty self explanatory. Get the value from the input box and store it in a variable called value
. Now we can add it to the list. Using our reference to the list we can inject this input into the page. On the next free line add this.
theList.innerHTML += '<li>' + value + '</li>';
Here were building a string, sandwiching the user input into a list item. That list item is added inside of our on page list. We now have user input that is added to the list when the user presses submit.
One small issue we have is that the user is able to add duplicate items to the list, e.g ‘wash’ followed by another ‘wash’. To prevent this we can use a simple cache mechanism that will prevent a duplicate item being added within each visit. Alter your variable declarations at the top of the page to look like this.
var theList = document.getElementById('theList'),
form = document.querySelector('form'),
input = form.querySelector('input'),
cache = {};
We have added a cache
variable and instantiated it to an empty object. Each time a user adds a new item we will add this as a key within the object which can be checked for each time a new item is added, without iteration. This is slightly hacky but suits our purpose. Modify the your list item injection code to look like this.
if (!cache[value]) {
cache[value] = true;
theList.innerHTML += '<li>' + value + '</li>';
}
We’ve tested to see if the value the user has added currently exists within the cache
. If not we store that value, as a key, within the object and inject the item as before.
There’s one last thing to do before we move on. As it stands the user can press submit and insert a blank entry. We can prevent this by including a test for an empty string within our if
statement. Modify your if
to the following.
if (!cache[value] && !(value == ""))
Our ToDo list can add users input to the list, stop duplicate entries and prevent blank tasks being entered. Not bad eh? Give it a try to test it’s all working before moving on.
Now we can move onto the next couple requirements, allowing items to be marked as done and removing them completely.
Acting on clicked items
To mark items as done we will again be using an eventListener
, except this time its on out list. When an item is clicked we will be applying a css class in order to style it. Open up the style.css
file and add this class.
li.done{
background: #eee;
transition: 0.3s ease-in;
}
This applies a background with a simple transition to any list item with the class of done. Now we can start building the JavaScript that applies this class.
Open up the script.js
file. Underneath all the code so far add another event listener, this time on our actual list
theList.addEventListener('click', function(ev){
"use strict";
var t = ev.target;
});
This is similar to the last event listener, except this time we have stored the event target as the variable t
because we will be using it a few times. With this we can test for the class .done
on the event target.
On the free line add the following if
statement.
if (!t.classList.contains('done')) {
t.classList.add('done');
}
This says if the target does not have the class of done, add the class of done. The style we added previously has been applied to a clicked list item. Go ahead and try it out.
To remove the item from the list we can add an else statement to our if.
if (!t.classList.contains('done')) {
// other code
} else {
t.parentNode.removeChild(t);
}
By adding this else we’re saying if the list item does have the class done it’s safe to remove. This is done by removing the child (the target) from the parentNode (theList).
You should now be able to remove an item from the list by clicking it twice. Seeing as we’re on a roll, lets add some more style to the done items. Alter the li.done css class to include the following line.
border-right: 30px solid #7cceee;
We have a nice fat border that is asking for something to be put into it. We can use the after
pseudo selector to add content after and list item with the class .done
applied to it. Add the following class to you style.css file.
li.done:after{
content: "\2713";
float:right;
margin-right: -25px;
}
The string “\2713” is the unicode declaration for a tick (check mark). By floating it to the right and applying a negative margin of -25px we position it above the thick blue border of the li.done
style. Simple but effective.
Our app is shaping up nicely. It’s time to move on and store the users list in case they leave and return later on. Once again, make sure everything is working as expected before moving on.
Adding LocalStorage
LocalStorage is a simple way to store small amounts of persistent data on a users device which works pretty much everywhere with the exception of Opera Mini.
To implement, the first thing we need to do is add any new items the user inputs into localStorage
. Modify the code within the form.addEventListener()
if statement in the to include the following line.
if (!cache[value] && !(value == "")) {
// other code
localStorage.myToDo = theList.innerHTML;
}
Here we’re creating an item in local storage called myToDo
and inserting the whole list in one hit. Not very elegant I’ll admit, but very effective. Every time an item is added the whole list will be saved again. Perfect.
To store the state of the list (if an item is done or removed), copy that line of code and insert it after the if statement within the theList.addEventListener()
.
if (!t.classList.contains('done')) {
// other code
} else {
// other code
}
localStorage.myToDo = theList.innerHTML;
As before, we’re dumping the entire list into the myToDo
localStorage variable any time a change is made to the list. With that there is one last thing to do, populate the list when the user returns to the page. To do this we need to check that the myToDo
variable exists with an if
statement.
At the bottom of your script.js
file add this.
if (localStorage.myToDo !== undefined) {
// insert code here
}
This is checking to see if myToDo
is not equal to undefined. If it does, the internal code will run. Now we have to do the opposite of before, intect our localStorage
into the on page list. Within the previous if statement add the following line of code.
theList.innerHTML = localStorage.myToDo;
Now the list is populated with the previous visits state.
Summary
Thats it. We have a working todo list with with minimal styling primed for you to extend. I’ve created a Gist of the full working app so far for reference.
This tutorial heavily influenced by a workshop given by Christian Heilmann.
Here’s a few things you can try for yourself:
- Add animation to the list item when they are removed.
- Add menu to house multiple lists.
- Store removed items in a separate completed list.