Skip to main content Jump to list of all articles

Yet Another Simple JavaScript To-Do List

To ask a simple question; Do we need another JavaScript to-do list? I guess it depends on whom it’s for to guess the right answer. A to-do app is generally a beginner’s project in any programming language. If I chuck another one in the mix it may be useful. It is likely to be different from any other. Why is that? My father would say, “There is more than one way to skin a cat”. (Not that he would have, he loved cats). In programming, there are many ways to do things. Whether it’s a different naming convention, organisation of code. We all have our own ways to do things. Anyhow, enough of my ramblings, let’s begin.

JavaScript to-do list
Final display with 1 item marked as complete

Prerequisites

A modern browser and a text editor. I recommend VS Code and the Live Server extension. Some knowledge of HTML and JavaScript would be beneficial but not required. I am using a pre-compiled version of the Bulma CSS Framework in this vanilla JavaScript To-do list tutorial.

Download

Also available on GitHub

The Concept

If you are familiar with a to-do app you may skip to the next section. If you want to see the plan of action then look no more.

Here is our rough pseudocode:

  1. Page loads and displays items, if any.
  2. Enter text in the input.
  3. Hit enter or the save button.
  4. Item saves to local storage.
  5. The page refreshes

There is a main section of the code. There are 3 functions in total. Add and delete to-dos and a function that saves to localStorage.

Let’s Get Coding

First, we will start with the HTML. It’s a pretty basic template using the Bulma CSS framework. In a real-world scenario, it would be more beneficial to use SASS. This would compile what you need or use into 1 CSS file. I have added an input field, a save button and an empty container for the JavaScript to-do list inside the HTML file. It is possible to write them in JavaScript too.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <link href="css/bulma.min.css" rel="stylesheet" />
  <link href="css/style.css" rel="stylesheet" />
  <title>Todo App</title>
</head>

<body>
  <section class="section">
    <div class="container">
      <div id="app">
        <h1 class="title">Worldoweb Todo</h1>
        <div class="field has-addons">
          <div class="control is-expanded">
            <input class="input is-medium" id="todoInput" type="text" placeholder="What do you want TODO">
          </div>
          <div class="control">
            <button id="add-btn" class="button is-medium is-primary">Save</button>
          </div>
        </div>
        <div id="todoContainer">
          <ul class="list" id="list"></ul>
        </div>
      </div>
    </div>
  </section>
  <script src="js/global.js"></script>
</body>

</html>

Monitor Keyup Event

To start with, we will add an event listener to watch the enter key. Once the user presses enter, it will save to localStorage. There is also an event listener attached to the add button.

/**
 * Monitors for keyup event
 * @param   {object}  e  The event object
 */
const keyup = (e) => {
  let keyPressed = e.keyCode || e.which;
  if (keyPressed === 13) {
    addTodo(input.value)
  }
};
document.addEventListener('keyup', keyup)

/**
 * Attaches an event listener to the button
 */
document.getElementById("add-btn").addEventListener("click", () => {
  addTodo(input.value);
});

Here comes the 3 functions deleteTodo addTodo and saveStorage

Delete Todo

/**
 * Deletes the todo and saves into local storage
 * @param   {int}  id  the id of the todo
 */
const deleteTodo = (id) => {
  todos.splice(id, 1);
  saveStorage(todos);
};

Add Todo

/**
 * Adds the todo to local storage
 * @param   {string}  todo_text  The text from the input box
 */
const addTodo = (todo_text) => {
  if (todo_text === '') {
    window.alert("Please enter some text")
  } else {
    let arr = [],
      todo = {
        value: todo_text,
        completed: false,
      };

    if (todos === null) {
      arr.push(todo);
    } else {
      arr = Object.values(todos);
      arr.push(todo);
    }
    saveStorage(arr);
  }

};

Save Storage

/**
 * Communicates with the browsers localStorage
 * @param   {array}  todos  list of todos
 */
const saveStorage = (todos) => {
  localStorage.setItem("todos", JSON.stringify(todos));
  location.reload();
};

The start of the main section of code is the biggest so I’ll break it up into manageable chunks.

Start of the main code

Grab the input object by its ID. This is then passed to the variable input. The second part grabs the to-dos out of local storage.

/**
 * Start of main code
 */

let input = document.getElementById("todoInput");

/**
 * Grab the todo list out of storage 
 */
let todos =
  localStorage.getItem("todos") !== null ?
  JSON.parse(localStorage.getItem("todos")) :
  null;

Display the Todos

The next section of the main code will only display the to-dos if there are any in Local Storage.

/**
 * Display the todos
 */
if (todos !== null) {
  for (let i = 0; i < todos.length; i++) {
    const del_icon =
      '<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" /></svg>';

    let list = document.getElementById("list"),
      li = document.createElement("li"),
      classes = ["todo-item", "level"];
    li.classList.add(...classes);
    li.id = i;
    let comp = document.getElementsByClassName("item-value");
    li.innerHTML =
      '<div class="level-left"><div class="level-item item-value">' +
      todos[i].value +
      '</div></div><div class="level-right"><div class="level-item"><span class="icon del">' +
      del_icon +
      "</span></div></div>";
    list.appendChild(li);
    todos[i].completed === true ? comp[i].classList.toggle("completed") : comp[i].classList.toggle("completed", false);
  }
}

Attach an Event to Todo Items

The last section of the main code attaches an event listener to every to-do item. As you can see in the last if statement. On pressing the delete icon, the item deletes from LocalStorage. On clicking a to-do item, a line appears through it to show that it’s completed.

/**
 * Attaches an event listener to the individual todo items
 */
let todoElements = document.getElementsByClassName("todo-item");

for (let i = 0; i < todoElements.length; i++) {
  todoElements[i].addEventListener("click", (e) => {
    let id = todoElements[i].id,
      level_right = todoElements[i].children[1].childNodes[0].firstChild,
      level_left = todoElements[i].children[0];

    if (e.target === level_right.lastChild) {
      deleteTodo(id);
    } else if (e.target === level_left.firstChild) {
      todos[id].completed === true ? todos[id].completed = false : todos[id].completed = true
      saveStorage(todos);
    }
  });
}

CSS

Finally the additional custom CSS

body{
    background-color: rgb(235, 237, 243);
    height: 100vh;
}
.list{
    padding: 1.5rem;
}
.todo-item{
    padding: 1rem;
    background-color: #fff;
    margin: 20px 0;
}
.todo-item .completed {
    text-decoration: line-through;
}

Conclusion

As I stated this was a very simple JavaScript to-do list tutorial. Please feel free to fork the code and tweak it further. I will continue to add features to it. Please feel free to request other tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.