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.
Image by codingexercises
How the app is set up
The app has a really simple setup:
- It has four functions: create, read, update, delete - commonly known as “CRUD”.
- It uses prompts to interact with the user without the need for the user to type into console
- 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:
- We need a way to store todos, and we’ll use an array for that
- Each of the CRUD pieces of functionality will be their own function
- 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:
- Interacts with the user
- Can be restarted
- 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:
- We’re not sanitizing user input; what if a user types letters instead of numbers? We didn’t handle this kind of situation
- What if a user types in the capital Y to confirm an action in the prompt? We might solve it with the
toLowerCase
function. - 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:
- If we’re removing the first element in the
todos
array, we can usetodos.shift()
. It doesn’t need a parameter. - If we’re removing the the last element in the
todos
array, we can usetodos.pop()
. It also doesn’t need a parameter. - 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)
. - 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:
- We’re passing the result of
todos.filter
, which is a brand new array, into the source, original, intact arraytodos
- essentially, we’re over-writing the original array with the filtered array - 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 theprompt
function returned the number as a string, for example: “5”, and it must be a number, thus:+todoNumber
. - Obviously, we’re using an IIFE (immediately invoked function expression), so as to be able to use
let
andconst
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.