Write a simple todo app in JavaScript

A very simple, yet fully functional, todo app in JS

By: Ajdin Imsirovic 10 November 2019

In this article, we’ll write a simple todo app in JavaScript.

Helpful tricks to learn JavaScript better Image by codingexercises

How the app is set up

The app has a really simple setup:

  1. It has four functions: create, read, update, delete - commonly known as “CRUD”.
  2. It uses prompts to interact with the user without the need for the user to type into console
  3. It is written to be as simple as possible, yet relatively easy to use

Writing the first version of our todo app

There are several things to consider:

  1. We need a way to store todos, and we’ll use an array for that
  2. Each of the CRUD pieces of functionality will be their own function
  3. We need a way to restart the app based on user choice (a ‘yes’ or ‘no’ answer in a prompt window)

Now that we’ve got everything planned out, let’s start building the app.

Putting the entire app in a single function

Let’s begin with our app’s function. The code is written in ES5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var start = function() {
    var todos, createTodo, readTodo, updateTodo, deleteTodo;
    
    todos = ['laundry', 'shopping'];

    alert(todos);

    if(prompt("Show todos?")=="y") {
        start();
    } else {
        alert("exiting the app");
    }
}
start()

On line 1, we define the start function.

On line 2, we prepare all the variables we’ll be using.

On line 4, we set a data structure to store our todos - a simple array, and we give it two todos.

On line 6, we alert the existing todos.

On line 8, we ask the user if they’d like to see todos; if the answer is a ‘y’, we run the start function. Otherwise, we stop the app and notify the user of it.

Great, on only 14 lines of code we’ve written an app that:

  1. Interacts with the user
  2. Can be restarted
  3. Can be exited out of

This is pretty impressive for only 14 lines of code. But I shouldn’t take credit for it, it’s just that JavaScript is such a great language!

Next, let’s add the createTodo, readTodo, updateTodo, and deleteTodo functions:

Here’s the updated version:

var start = function() {
    var todos, createTodo, readTodo, updateTodo, deleteTodo;
    
    todos = ['first', 'second'];

    createTodo = function() {
        var anotherTodo = prompt("Type a todo to add")
        todos.push(anotherTodo);
        readTodo();
    }

    readTodo = function() {
        alert(todos);
		createTodo();
    }

    updateTodo = function() {
        // to be added
    }

    deleteTodo = function() {
        // to be added
    }

    if(prompt("Show todos?")=="y") {
        readTodo();
    } else {
        alert("exiting the app");
    }
}
start()

Now are app has more logic, but it’s also broken: It will keep switching between the createTodo and readTodo functions.

Thus, although it’s working properly, it gets tiring quickly, as the app keeps asking us to add yet another todo (after we’ve already added one). This means we need an exit condition in the readTodo function.

Adding an exit condition in the readTodo function

To add an exit condition, all we need to do is add another prompt:

readTodo = function() {
    alert(todos);
    if (prompt("Add another todo?")=="y"){
        createTodo();
    } else {
        alert("exiting the app");
    }
}

In the update above, we’ve added a check: we’re asking the user if they’d like to add another todo. If they do, great: we run the createTodo function. If they don’t, then we have an issue: do we really want to exit the app just because the user doesn’t want to add another todo?

Instead, let’s ask them if they’d like to see all todos.

The code is already there; this time, we’ll just wrap it inside a function and call it when we need to:

var wannaSeeAllTodos = function() {
    if(prompt("Show todos?)=="y"){
        readTodo();
    } else {
        alert("exiting the app");
    }
}

Here’s the updated code for the entire app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var start = function() {
    var todos, createTodo, readTodo, updateTodo, deleteTodo;
    
    todos = ['first', 'second'];

    createTodo = function() {
        var anotherTodo = prompt("Type a todo to add")
        todos.push(anotherTodo);
        readTodo();
    }

    readTodo = function() {
        alert(todos);
        if (prompt("Add another todo?")=="y"){
            createTodo();
        } else {
            wannaSeeAllTodos();
        }
    }

    updateTodo = function() {
        // to be added
    }

    deleteTodo = function() {
        // to be added
    }

    var wannaSeeAllTodos = function() {
        if(prompt("Show todos?")=="y"){
            readTodo();
        } else {
            alert("exiting the app");
        }
    }
}
start()

Adding the updateTodo function’s code

We need to ask the user which todo they’d like to update; we’ll let them type in a number.

updateTodo = function() {
    var todoNumber = prompt('Which todo you want to change?');
    var updatedText = prompt('Enter updated text');
    todo[todoNumber] = updatedText;
    alert(todos);
}

Here’s the full updated app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var start = function() {
    var todos, createTodo, readTodo, updateTodo, deleteTodo;
    
    todos = ['first', 'second'];

    createTodo = function() {
        var anotherTodo = prompt("Type a todo to add")
        todos.push(anotherTodo);
        readTodo();
    }

    readTodo = function() {
        alert(todos);
        if (prompt("Add another todo?")=="y"){
            createTodo();
        } else {
            updateTodo();
        }
    }

    updateTodo = function() {
        var todoNumber = prompt('Which todo you want to change?');
        var updatedText = prompt('Enter updated text');
        todos[todoNumber] = updatedText;
        alert(todos);
        wannaSeeAllTodos();
    }

    deleteTodo = function() {
        // to be added
    }

    var wannaSeeAllTodos = function() {
        if(prompt("Show todos?")=="y"){
            readTodo();
        } else {
            alert("exiting the app");
        }
    }
	
	wannaSeeAllTodos()
}
start()

Deleting a todo

Now we can update the deleteTodo function.

Again, we’re checking the number of todo to delete, and we’re storing it in a variable named todoNumber. Then we simply remove the specified todo using the splice method:

deleteTodo = function() {
        var todoNumber = prompt('Which todo you want to change?');
        todos.splice(todoNumber, 1); // this line deletes the specified todo
        alert(todos);
        wannaSeeAllTodos();
}

The splice method on arrays is very versatile. Here, we’re running the splice method on the todos array.

We’re passing it the position of the todoNumber we are removing - the first parameter.

We’re also passing it the number of elements to be removed - the second parameter.

Since we’re only removing a single todo, the second parameter is one.

Arrays are zero-indexed

Since arrays are zero-indexed, meaning, they’re counted like this: 0, 1, 2…, we might want to lower the todoNumber by 1, like this:

deleteTodo = function() {
        var todoNumber = prompt('Which todo you want to change?');
        todos.splice(todoNumber - 1, 1); // this line deletes the specified todo
        alert(todos);
        wannaSeeAllTodos();
}

Alternatively, we might want to remind the user that arrays in JS are zero-indexed, like this:

deleteTodo = function() {
        var todoNumber = prompt('Which todo you want to change? If it\'s the first one, type 0; if it\'s the second one, type 1, etc. Remember: arrays are zero-indexed!');
        todos.splice(todoNumber, 1); // this line deletes the specified todo
        alert(todos);
        wannaSeeAllTodos();
}

Regardless of how we do it, the end result is the same.

Here’s our full todo app, with the reminder of zero-indexed arrays:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var start = function() {
    var todos, createTodo, readTodo, updateTodo, deleteTodo;
    
    todos = ['first', 'second'];

    createTodo = function() {
        var anotherTodo = prompt("Type a todo to add")
        todos.push(anotherTodo);
        readTodo();
    }

    readTodo = function() {
        alert(todos);
        if (prompt("Add another todo?")=="y"){
            createTodo();
        } else {
            updateTodo();
        }
    }

    updateTodo = function() {
        var todoNumber = prompt('Which todo you want to change?');
        var updatedText = prompt('Enter updated text');
        todos[todoNumber] = updatedText;
        alert(todos);
        wannaSeeAllTodos();
    }

    deleteTodo = function() {
            var todoNumber = prompt(`
                Which todo you want to change? 
                If it\'s the first one, type 0; 
                if it\'s the second one, type 1, etc. 
                Remember: arrays are zero-indexed!`);
            todos.splice(todoNumber, 1); // this line deletes the specified todo
            alert(todos);
            wannaSeeAllTodos();
    }

    var wannaSeeAllTodos = function() {
        if(prompt("Show todos?")=="y"){
            readTodo();
        } else {
            alert("exiting the app");
        }
    }
	
	wannaSeeAllTodos()
}
start()

In the code above, inside the deleteTodo function, instead of regular single or double quotes around the string we passed to the prompt function, we’ve used backticks, an ES6 feature, which allows us to write multi-line strings easily.

Our todo app is complete, but…

We could say that our todo app is complete, but there are many potential points of failure:

  1. We’re not sanitizing user input; what if a user types letters instead of numbers? We didn’t handle this kind of situation
  2. What if a user types in the capital Y to confirm an action in the prompt? We might solve it with the toLowerCase function.
  3. Is there a way to get rid of the prompts and make the app easier to use? We could use HTML elements, canvas, or something else entirely.

Obviously, there’s lots of place for improvement.

However, rather than making the app itself more complex, let’s use the opportunity to look at alternative ways to delete items from an array.

Some addtional ways to remove items from an array in JavaScript

Let’s assume that we’re still working with the todos array.

Here are additional ways to do it:

  1. If we’re removing the first element in the todos array, we can use todos.shift(). It doesn’t need a parameter.
  2. If we’re removing the the last element in the todos array, we can use todos.pop(). It also doesn’t need a parameter.
  3. With splice, we can remove more than one element. For example, to remove 5 elements, starting from position 0, we’d run: todos.splice(0, 5).
  4. We could remove todos using the array.filter() method, combined with array destructuring. We’ll look at this example, because it might feel complex until you understand it; then it looks really elegant.

Removing todos using the array.filter() method

This is how we can implement a filter on our todos array:

todos.filter( todo => todo != todos[todoNumber] );

Here’s the updated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
(function() {
    let todos, createTodo, readTodo, updateTodo, deleteTodo;
    
    todos = ['first', 'second'];

    createTodo = function() {
        let anotherTodo = prompt("Type a todo to add")
        todos.push(anotherTodo);
        readTodo();
    }

    readTodo = function() {
        alert(todos);
        if (prompt("Add another todo?")=="y"){
            createTodo();
        } else {
            updateTodo();
        }
    }

    updateTodo = function() {
        let todoNumber = prompt('Which todo you want to change?');
        let updatedText = prompt('Enter updated text');
        todos[todoNumber] = updatedText;
        alert(todos);
        wannaSeeAllTodos();
    }

    deleteTodo = function() {
            let todoNumber = prompt(`
                Which todo you want to delete? 
                If it\'s the first one, type 0; 
                if it\'s the second one, type 1, etc. 
                Remember: arrays are zero-indexed!`);
            // the next line deletes the specified todo
            todos = todos.filter( todo => todo != todos[+todoNumber] );
            alert(todos);
            wannaSeeAllTodos();
    }

    var wannaSeeAllTodos = function() {
        if(prompt("Show todos?")=="y"){
            readTodo();
        } else if(prompt("Delete a todo?")=="y") {
            deleteTodo();
        } else {
            alert("exiting the app");
        }
    }
	
	wannaSeeAllTodos()
})();

Note this line:

todos = todos.filter( todo => todo != todos[+todoNumber] );

There’s a few things happening here:

  1. We’re passing the result of todos.filter, which is a brand new array, into the source, original, intact array todos - essentially, we’re over-writing the original array with the filtered array
  2. We’re converting the todoNumber from a string into a number, using the plus operator in front of variable name trick: +todoNumber. We must do this because the earlier call to the prompt function returned the number as a string, for example: “5”, and it must be a number, thus: +todoNumber.
  3. Obviously, we’re using an IIFE (immediately invoked function expression), so as to be able to use let and const without the browser’s console complaining they’ve already been declared

We’ve already explained the rest of the stuff earlier in this article.

Like mentioned earlier in this article, we could have improved many things in this app, most notably the business logic - there’s still some unespected behavior in the app. However, for a quick exercise in building a todo app, this is probably good enough.

Feel free to check out my work here: