Having a list

So, what should happen next? Our code now stands there, with a text for a todo item at hand, but has no idea what to do with it. So, what would be the immediate next steps?

  • The new item should be added to our list of todo items
  • The input field should be wiped so that we can add the next item

Now, the first step might actually be two steps: Saving the item somewhere for later and adding the item to an actual list in the HTML. Let’s start with the second part. Where is that list? Let’s add it to the HTML, just below the input field:

1 <input id="new-todo" placeholder="What needs to be done?" autofocus>
2 <ul id="todo-list"></ul>

innerHTML inside

Since we have committed ourselves to not using any library, we are now bound to build that todo-list item by hand. There are two variants of this, one simple, one complex. Let’s start with the simple version:

1 if (event.keyCode === 13) {
2   var todo = document.getElementById('new-todo').value;
3   var list = document.getElementById('todo-list');
4   list.innerHTML += ("<li>" + todo  + "</li>");
5 }

innerHTML allows you to replace or modify everything within the given tag as if it was just only a big string. It works, but it has some drawbacks: For example, if you had any event handlers on the other list items, they would now be gone, since our line essentially is the same as

1 list.innerHTML = list.innerHTML + ("<li>" + todo + "</li>")

As you can see, the action happens in two steps: first, all DOM elements inside the <ul> are converted to a string representation, then we append our stuff and then everything is converted back to HTML. Since event handlers are JavaScript only and are not expressed in the HTML that innerHTML gives us, they get lost in the process.

innerHTML is not completely useless, though. If you quickly want to add or replace some text in a single element, it is often the best choice.

Nodes inside of nodes inside of…

So let’s try the more complex way: creating a list node by hand. It’s not that hard:

1 var list = document.getElementById('todo-list');
2 var item = document.createElement("li");
3 item.appendChild(document.createTextNode(todo));
4 list.appendChild(item);

We are using three different methods here, so let me quickly explain them:

  • document.createElement creates a DOM node of the given type (so “li” creates a list item element, “div” would create a huge Stegosaurus, you get the idea). The element only exists in JavaScript-land so far, but apart from that, you can do everything with it you could do with a node you got via document.getElementById, for example.
  • node.appendChild appends a given element to the node.
  • document.createTextNode seems a little weird, but to understand it you only have to realise that text inside a tag (for example the foo in <b>foo</b>) is also represented as a node in the DOM, a so called text node. So to actually be able to insert text into a tag programmatically, you create a text node and insert it. (The clever reader might suggest using innerHTML for that - We’ll get to the differences in a minute)

That looks quite okay so far. Oh, one last thing to make it work: we should clear the input field. And since this is the second time we would need a reference to the input field, we should cache the getElementById call in a variable to make the code a little shorter:

1 var todoField = document.getElementById('new-todo');
2 var list = document.getElementById('todo-list');
3 var item = document.createElement("li");
4 item.appendChild(document.createTextNode(todoField.value));
5 list.appendChild(item);
6 todoField.value = "";

This caching is something you’ll see quite a bit when reading JavaScript code. Accessing the DOM is not only a verbose operation, but also a slow operation. And with slow I mean orders of magnitudes slower than normal JavaScript code. We will come back to that subject shortly.

Security is King

Pretty good so far, but don’t hit the reload button - currently our todos are only “persisted” in HTML. But before I tell you why this is usually a bad idea and how we should restructure the code to fix that, I promised you to tell you a little something about the difference between createTextNode() and innerHTML. The biggest difference is that createTextNode creates, ba-dumm-tish, text nodes, while innerHTML, as the name suggests, can also contain HTML. You can try this yourself: Try to add a todo list item like <b>foo</b> into both versions. In the innerHTML version, you’ll end up with a bold foo, while you’ll get your original input value in the createTextNode variant.

There are, obviously, upsides and downsides to both approaches. The biggest upside of the createTextNode() example? It’s super safe. You may have heard about so called cross site scripting issues: When it’s possible to insert active components (such as script tags) by user input into a web page, this enables bad people to spy on the users or tricking them into exposing credentials or other malicious deeds.

With createTextNode, it’s simply not possible to create these active components. With innerHTML though, the situation is a little more complicated. If you try to insert a script tag using innerHTML, in most (if not all) browsers, you’ll see that the script is not executed (good!) but the script tag is actually inserted into the DOM as is. That can be a problem later on. In the end, this is one of these places where the good web developer plays the “better safe than sorry” move, but of course there are situations where a text node is not enough: If you actually want people to be able to enter HTML (for example inside a comment field on a blog), you can’t simply use createTextNode, but you still have to make sure that you are not letting people input arbitrary stuff that could potentially be dangerous.

Identifying the view layer

So far, we have now stored our todo items in the DOM. Apart from the fact that this currently doesn’t survive reloads, it also has some more problems, that are, in the beginning, quite hard to grasp. As I said: Accessing the DOM is slow. Even worse: Intertwining reads and writes to the DOM is even slower. This has to do with the way browsers work internally. Browser vendors like the Google Chrome team and the Firefox team are of course aware of that and did a lot of work on this during the last few years, so the situation is not as dire as it used to be anymore, but the basic rule of JavaScript development still applies: Don’t access the DOM (unless you really, really have to).

Looking at it from a more abstract view point, this means that we should treat the DOM more like a terminal: It’s responsible for displaying stuff and handling user input, but it should not be responsible for data storage.

Okay, so let’s build a data storage. Since we’re only dealing with lists of strings here, let’s start with a basic Array that will just hold the Strings of the items. Since items might have other qualities as well (they can be marked as finished, for example), this might not be enough, but it’s the simplest thing that will work right now.

Also, let’s decouple the display from the storage and redraw the whole list every time we modify it. This might sound awkward and inefficient, but for a relatively basic view like this, that hardly matters. Also, if we want to persist stuff later on, we would need such a redraw method anyway. Also, coming back to the DOM access speed issues once again, doing lots of DOM manipulation in one place is not as costly as doing it in smaller operations every now and then.

For the moment, let’s just declare the todo list as a global (to our script, not on window) variable and then do the redraw method:

 1 var todoListItems = [];
 2 function redrawList() {
 3   var i;
 4   var list = document.getElementById('todo-list');
 5   var len = todoListItems.length;
 6   list.innerHTML = "";
 7   for(i=0; i<len; i++) {
 8     var item = document.createElement("li");
 9     item.appendChild(
10       document.createTextNode(todoListItems[i].value)
11     );
12     list.appendChild(item);
13   }
14 }

Please note that I used a simple for loop for iterating over the arrays. A more modern way that’s actually implemented in almost all browsers (unfortunately NOT in IE7/8, but it’s easy to emulate that) would be forEach().

Also, there are a million ways of writing that for loop and how to declare the len and the i variables. I’m afraid I’ll have to leave it up to you to develop your own JavaScript style.

Did you notice that I used innerHTML to empty the list: Yep, that’s actually the quickest way of deleting a complete subtree from the DOM.

Our keypress handler now is significantly smaller:

1 function newTodoKeyPressHandler(event) {
2   if (event.keyCode === 13) {
3     var todoField = document.getElementById('new-todo');
4     todoListItems.push(todoField.value);
5     redrawList();
6     todoField.value = "";
7   }
8 }

Persistence is futile

Let’s resolve the last piece of the puzzle: Storage. To keep it simple, I’ll just show you one way to do it, without really going into details. We’ll use DOM storage (specifically localStorage) to persist our data within the browser. This means that you can reload the page at any given time and you will get your todo-list back.

This does not mean that you can recall that list from other browsers, because then you’d need a server component, which is out of the scope of this book.

localStorage is just a very simple key value store, that allows you to store strings under a key. In our case, we’ll just dump our array of strings into a single key and retrieve it on a page reload. This is a good time to extract the “add item to list” functionality as well. So let’s start with that function:

1 function addToList(item) {
2   todoListItems.push(item);
3   localStorage.setItem('todo-list', JSON.stringify(todoListItems));
4 }

The newTodoKeyPressHandler needs to be adjusted as well:

1 function newTodoKeyPressHandler(event) {
2   if (event.keyCode === 13) {
3     var todoField = document.getElementById('new-todo');
4     addToList(todoField.value);
5     redrawList();
6     todoField.value = "";
7   }
8 }

So, the localStorage object has a method called setItem, and that method wants a key (in our case: just "todo-list") and a string version of your data. The easiest way to get a string version of an array is to use the JSON object. It is available at all browsers starting from IE 8, and luckily that’s the same IE version that introduced localStorage. So, calling JSON.stringify() gives you back a string containing the JSON representation of the object you stuffed in there.

So far, so simple. Now let’s build a method to restore the list in case of a reload:

1 function reloadList(item) {
2   var stored = localStorage.getItem('todo-list');
3   if (stored) {
4     todoListItems = JSON.parse(stored);
5   }
6   redrawList();
7 }

Obviously, localStorage.getItem only gives you a string version of your data (makes sense, right?), so now we need to use JSON.parse to get back our data. After that, it’s just a question of redrawing the list.

By adding reloadList() to the windowLoadHandler() function, we now have a persisting version of our todo list.

Making it beau-tee-ful

There’s tons of stuff missing but don’t you think as well that this is actually pretty good for 53 lines of HTML and JavaScript?

Before we’re going to add more functionality (we can’t delete items or mark them as done, for example), I’d like to take a step back and make the whole thing a little more beautiful. For that I’m going to shuffle the HTML around a bit and add some CSS to it. All of that is taken from the before mentioned TodoMVC project. So the HTML up to the script tag looks like this:

 1 <section id="todoapp">
 2   <header id="header">
 3     <h1>todos</h1>
 4     <input id="new-todo" placeholder="What needs to be done?" autofocus>
 5   </header>
 6   <section id="main">
 7     <ul id="todo-list"></ul>
 8   </section>
 9 </section>
10 <footer id="info">
11   <p>Template by <a href="http://github.com/sindresorhus">Sindre Sorhus</a></p>
12 </footer>

Also, I’ve added a base.css and a background.png file snatched from the TodoMVC project. I won’t reprint it here, as said in the introduction, you can find all of those files in the github.com repo. It doesn’t look perfect right now because our list items are quite a bit simpler than in the TodoMVC end result, but since this is directly connected to our next feature, we need to live with that for a minute.

The complete example can be found at code.thesinglepageapp.com.