facebook youtube pinterest twitter reddit whatsapp instagram

To-Do List Application With JavaScript

In this guide, we would go over creating a to-do list app in JavaScript, where you can

    • Add a to-do list,
    • You can delete an individual item, 
    • You can search or filter an item,
  • You can clear all the items, prevents clearing multiple items by mistake
  • Prevents Adding an empty Todo item, prevents deleting an item by mistake
  • and persist the todo list even after closing the application

Prerequisite for this guide is as follows:


HTML Structure of the To-Do App

Let's get started, here is what my HTML structure looks like:

-----------------------------
-----------------------------
[[audio]]
<div id ="container" class="content-wrapper text-center">
  <div class="content-wrapper text-center">
    <div class="container-fluid">
      <div class="container">
        <div class="row justify-content-center">
          <div class="col-md-8">
            <div class="card bg-light">

              <div class="card-body">
                <h2 class="card-header">To-Do App</h2>
              </div>
              <div class="container-fluid">
                <div class="row justify-content-lg-start">

                  <!-- Todo Input-->

                  <div class="col-6 col-sm-5 col-md-4 col-lg-3 m-auto">
                    <form class="form-group">
                      <label for="task" class="w-100"></label>
                      <input type="text" class="form-control " id="input" placeholder="Type Todo"  name="email" value="" autofocus>
                      <input type="submit" value="Add Todo" id = "add-todo" class="btn mt-10">
                    </form>
                  </div>
                </div>
              </div>

              <!-- Todo Filter Input-->

              <div class="col-6 col-sm-10 mb-10 m-auto">
                <label for="filter" class="w-100"></label>
                <input type="text" class="form-control" id="filter" placeholder="Filter Todo"  name="email" value="" autofocus>
              </div>

              <div class="col-6 col-sm-5 col-md-4 col-lg-3 m-auto">
                <h4>To-do List</h4>
              </div>

              <!-- Todo Items -->
              <ul class="collection row align-items-center">
              </ul>

              <!-- Todo Clear All Button-->

              <div class="col-6 col-sm-5 col-md-4 col-lg-3 m-auto">
                <input type="submit" value="Clear All" id = "clear-all" class="btn mt-10">
              </div>

            </div>
          </div>
        </div>
      </div>
    </div>

  </div>
</div>
----------------------
----------------------

The above structure is within the body element, and besides that, I am using Halfmoon CSS Framework for the styling, here is how it looks on the page:


Todo App Structure and UI

Let's start with collecting or getting the UI elements, I have comments on the HTML structure, so, you know what to select, I'll be selecting the following elements:

// Select The UI Elements
const todoForm = document.querySelector('.form-group');
const todoInput = document.getElementById('input');
const todoItemParent = document.querySelector('.collection');
const todoClearAllBtn = document.getElementById('clear-all');
const todoFilter = document.getElementById('filter');

We don't need to define an element for the submit button, we would use event delegation to target that since we already called it's parent, in our case, the parent is form.form-group.

Add Event Function

The next thing we are going to do is to load all the event handlers, and our first event type would be of a submit type:

// Load all event listeners
loadallEvents();

// Load all event listeners
function loadallEvents() {
    // Add task event
    todoForm.addEventListener('submit', addTodo);
}

Recall, that we are storing the form elements and its child in the todoForm variable, here:

const todoForm = document.querySelector('.form-group');

Once, we load all the event listeners, it means they are all in memory, and can be triggered when called. So, the document.querySelector is used to select the form element that houses the input box, and the submit button, we then added an event listeners in our handler that listen to the submit event type and once a submit has been captured, the event listener passes the logic of how it should respond to the addTodo function, in the addTodo function, I'll do something like this:

.................
.................
// Load all event listeners
loadallEvents();

// Load all event listeners
function loadallEvents() {
    todoForm.addEventListener('submit', addTodo); // Add Todo event
}

// Add Todo
function addTodo(e) {
    if(todoInput.value.replace(/\s/g,"") === "") {
        alert('Add a task');
    } else {

        // Create li element
        const li = document.createElement('li');

        // Add class
        li.className = 'items m-auto mb-20';

        // Create text node and append to li
        li.appendChild(document.createTextNode(todoInput.value)); // what ever the user types

        // Create new link element
        const link = document.createElement('a');
        // Add class
        link.className = 'btn btn-danger ml-20 m-auto';
        // Create text node and append to link
        link.appendChild(document.createTextNode('Delete'));
        // Append the link to li
        li.appendChild(link);

        // Append li to ul
        todoItemParent.appendChild(li);
    }

    // Clear input
    todoInput.value = '';

    e.preventDefault();
}

NOTE: This ........ means its a continuation

So, let's decipher what the function is doing:

When the user type something into the input box, and click the submit button, we want to create the following element structure, and get it appended to ul:

 <li class="items m-auto mb-20">
    User Input
    <a href="#" class="btn btn-danger ml-20 m-auto">Delete</a>
</li>

But before creating the structure we checked if the value of the todoInput has an empty string or even blank spaces, if it does, we alert the user to add a task, if otherwise, we create the li element and a link element, I appended the a tag to the li element, and lastly, I appended the li itself to the ul tag, which gives us the structure above.

Still in the function, outside of the if and else block, I cleared the input each time the user clicks the submit button, and lastly we prevented the submit default behaviour. Here is an illustration of how it works:

addTodo Function Ilustrate

Let's create a function for removing the item.

Delete Todo Function

Add a click event to the handler loader, we target the ul element:

.......................
.......................
// Load all event listeners
loadallEvents();

// Load all event listeners
function loadallEvents() {
    todoForm.addEventListener('submit', addTodo); // Add Todo event
    todoItemParent.addEventListener('click', removeTodo); // Remove Todo event
}
.......................
.......................

This just means that we are listening to any click event type in the ul element, and once a click is detected, we fire up the removeTodo function, and we would define it like so:

......................
.....................
// Remove Todo
function removeTodo(e) {
    if(e.target.classList.contains('btn-danger')) {
        if(confirm('Are You Sure?')) {
            e.target.parentElement.remove();
        }
    }
}
......................
.....................

Once you click the delete button, e.target would return: <a class="btn btn-danger ml-20 m-auto">Delete</a>we then say if e.target.classList contains a class name btn-danger, if it does, we would ask the user a confirmation about the deletion, if the user click yes, we remove the todo item.

Here is an illustration:

3. deleteTodo Function illustration

Clear All Todo Function

Add a click event to the handler loader for the clear all button:

// Load all event listeners
loadallEvents();

// Load all event listeners
function loadallEvents() {
    todoForm.addEventListener('submit', addTodo); // Add Todo event
    todoItemParent.addEventListener('click', removeTodo); // Remove Todo event
    todoClearAllBtn.addEventListener('click', clearTodos); // Clear-All Todo event
}

Once you click the cler all button, we would fire the clearTodos function, and define it like so:

// Clear All Todos
function clearTodos() {
    if (confirm('Delete All Todos?')) {
        while (todoItemParent.firstChild) {
            todoItemParent.removeChild(todoItemParent.firstChild);
        }
    }
}

This is really simple, we first ask the user if they are sure they actually want to delete all of the toDo items, if they enter yes, we use while loop to loop through the firstchild of the todoItemParent, which is li and we then remove all of the li items.

You can also do:

// Clear All Todos
function clearTodos() {
    if (confirm('Delete All Todos?')) {
        taskList.innerHTML = '';
    }
}

But the later is more faster, it's your choice either way.

Filter Todo Function

Add a keyup (this fires once the user release there keystroke) event to the handler loader for the filter input:

....................
....................
// Load all event listeners
loadallEvents();

// Load all event listeners
function loadallEvents() {
    todoForm.addEventListener('submit', addTodo); // Add Todo event
    todoItemParent.addEventListener('click', removeTodo); // Remove Todo event
    todoClearAllBtn.addEventListener('click', clearTodos); // Clear-All Todo event
    todoFilter.addEventListener('keyup', filterTodos); // Filter Todo event

}
...................
...................

If the user release their keystroke when typing, it fires the keyup event, and we build the logic of the next action in the filterTodos handler, here is how the function would be define:

// Filter Todos
function filterTodos(e) {
    const filterInput = e.target.value.toLowerCase();
    getAllTodoItems = document.querySelectorAll('.items')

    getAllTodoItems.forEach(function(todo){
        const item = todo.firstChild.textContent;
        if(item.toLowerCase().indexOf(filterInput) != -1){
            todo.style.display = 'block';
        } else {
            todo.style.display = 'none';
        }
    });
}
  • filterInput would store anything the user types, and convert it to lowercase, this is so we can match the pattern correctly
  • getAllTodoItems gets all of the nodelist of the todo items, which we can then loop through
  • We then use the forEach loop to loop through the todoItems, each item get passed to the todo parameter
  • const item = todo.firstchild.textcontent is used to get all of the text in the todo items, like, if you have <li>buy pizza</li> and <li>get vacation</li>it would store only buy pizzafor the first loop run, and then get vacationfor the second loop run. In short, todo.firstchild.textcontent would grab all of the text content in the li tag as the array method loops through.
  • if(item.toLowerCase().indexOf(filterInput) != -1){ The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present. So, we are using the indexOf to locate the value in the filterInput, if the value is not present in the item variable, we set todo.style.display = 'block'; if the value is present, we set it to todo.style.display = 'none';

Here is an illustration of how it works:



Persist Todo Lists To Local Storage

As it is right now, it only takes a browser refresh for your todo items to be gone, and this is because we are not persisting it to any storage. We can persist our todo items in the browser local storage, this way, items won't get deleted even if you close the browser or refreshes the page.

The first thing we should do is to create a function that stores the todo item in the localStorage, we would add that in the addTodo function since that would mean the moment the user add an item, and we are done creating the element and stuff, we also add it to out localStorage, I'll do at the end of the else statement in the addTodo function:

// Add Todo
function addTodo(e) {
    if(todoInput.value.replace(/\s/g,"") === "") {
        alert('Add a task');
    } else {

        // Create li element
        const li = document.createElement('li');

        // Add class
        li.className = 'items m-auto mb-20';

        // Create text node and append to li
        li.appendChild(document.createTextNode(todoInput.value)); // what ever the user types

        // Create new link element
        const link = document.createElement('a');
        // Add class
        link.className = 'btn btn-danger ml-20 m-auto';
        // Create text node and append to link
        link.appendChild(document.createTextNode('Delete'));
        // Append the link to li
        li.appendChild(link);

        // Append li to ul
        todoItemParent.appendChild(li);

        // Store in LS
        storeTodoToLocalStorage(todoInput.value);
    }

    // Clear input
    todoInput.value = '';

    e.preventDefault();
}

storeTodoToLocalStorage(todoInput.value);I passed in the todoInput.value which is the users value to the storeTodoToLocalStorage, so, let's create the function, I'll do that below the addTodo function:

---------------------
---------------------
// Store Todo To localStorage
function storeTodoToLocalStorage(todo){
    let values;
    if(localStorage.getItem('todos') === null){
        values = [];
    } else {
        values = JSON.parse(localStorage.getItem('todos'));
    }
    values.push(todo);

    localStorage.setItem('todos', JSON.stringify(values));
}
------------------------
-----------------------

The function we called in the addTodo else block has this: storeTodoToLocalStorage(todoInput.value);

The todoInput.value we passed in the function when calling it is what we used in the above function when defining it. So, when defining the function we want it to accept one parameter: function storeTodoToLocalStorage(todo), which is what we have passed when calling the function. So, let me explain what the function is doing:

I started by declaring a variable "values" that would hold the input passed if there is any or an empty array if there is none.

In the if block I checked if the key (in my case, my key is called todos) is empty, and if it is empty, I set the values variable to an empty array,  the valuesvariable would be the one to store items in the localStorage.

Moving on, if the if statement is false, that is if there is truly a value in the todo key, the else block to store whatever is in the key “todos” value into the valuesvariable, here:

values = JSON.parse(localStorage.getItem('todos'));

We can’t just get the value, we need to parse it with JSON, and that is because the values or strings should be stored in a JSON format.

Once, we’ve gotten the value, we use values.push(todo) to push whatever is in the todo input (what the user types in the input box) to the values variable, it would append it to the end of what is already in the values variable.

Lastly, to store it in the localStorage we add the key, in my case I want to use the key named todoand you use JSON.stringify to converts a JavaScript object or value to a JSON string, Here:

localStorage.setItem('todos', JSON.stringify(values));

Now, if you add an item to the todo list they also get saved in the localStorage:

addTodos To localStorage

There are a couple of issues we need to fix, the first one is if the user deletes a todo item, we should also delete it from the localStorage, the second one is if the user clears all the todo items with the clear button, we should also remove everything from the localStorage, and the last one is if a page reloads, the data in the localstorage should get prepopulated back to our UI.

Let's start with the simple one, the clear all button: Goto your clearTodos function and add localStorage.clear(); in the function like so:

// Clear All Todos
function clearTodos() {
    if (confirm('Delete All Todos?')) {
        while (todoItemParent.firstChild) {
            todoItemParent.removeChild(todoItemParent.firstChild);
        }
        localStorage.clear(); // Clear All the data from localStorage
    }
}

That's all you ever need to do to clear all the todos in your localStorage. Once the user clicks the clear-all button, it would ask for confirmation, if yes is clicked, it would remove the todo items as well as the localstorage items, if otherwise, nothing would be done.

Now, whenever the page reloads, we want the user to see the data stored in the localStorage in our UI too, so, to do this, we add an event listener of DOMContentLoaded, which means it should fire once the page loads, we then add a getTodos handler that handles the logic of how the data would be prepopulated:

Add: document.addEventListener('DOMContentLoaded', getTodos);to the top of the function that loads the event handler:

------------------------
------------------------
// Load all event listeners
function loadallEvents() {
    document.addEventListener('DOMContentLoaded', getTodos); // DOM Load event
    todoForm.addEventListener('submit', addTodo); // Add Todo event
    todoItemParent.addEventListener('click', removeTodo); // Remove Todo event
    todoClearAllBtn.addEventListener('click', clearTodos); // Clear-All Todo event
    todoFilter.addEventListener('keyup', filterTodos); // Filter Todo event

}
------------------------
------------------------

add we would define the getTodos function like so:

// Get Todos from localStorage
function getTodos() {
    let values;
    if(localStorage.getItem('todos') === null){
        values = [];
    } else {
        values = JSON.parse(localStorage.getItem('todos'));
    }

    values.forEach(function(todo){
        // Create li element
        const li = document.createElement('li');

        // Add class
        li.className = 'items m-auto mb-20';

        // Create text node and append to li
        li.appendChild(document.createTextNode(todo)); // what ever the user types

        // Create new link element
        const link = document.createElement('a');
        // Add class
        link.className = 'btn btn-danger ml-20 m-auto';
        // Create text node and append to link
        link.appendChild(document.createTextNode('Delete'));
        // Append the link to li
        li.appendChild(link);

        // Append li to ul
        todoItemParent.appendChild(li);
    });
}

To get the value out from the localStorage, you can either use a forEach, which is an Array method that we can use to execute a function on each item in an array, or you can use for/of loop, you can’t use for loop or while loop because they won’t know how to get the amount of item in the list. I used a forEach loop above.

Before using the loop, we checked if there is a value in the key, if there is, we store an empty array, if otherwise, we get the values and store in the valuesvariable, and the next point of action is to loop through the items in the values array to re-build the todo items.

The forEach loop takes a callback function. The function will be executed for every single element of the array. It must take at least one parameter which represents the elements or item of the array, in our case, we are telling the function to build elements for each item in the array, and that should get back the todo item even if you close your browser and revisit.

The last thing we need to do is if a todo item is removed, we also remove it from the localStorage.

In the removeTodo function, we get the li element as that is what is holding the todoitem, which we then pass as an argument to a removeTodoFromlocalStorage function like so:

function removeTodo(e) {
    if(e.target.classList.contains('btn-danger')) {
        if (confirm('Are You Sure?')) {
            e.target.parentElement.remove();
        }
    }
    // get li element. and pass it to the removeTodoFromLocalStorage as an argument
    let getli = e.target.parentElement
    removeTodoFromLocalStorage(getli);
}

We define the function like so:

// Remove Todo From Local Storage
function removeTodoFromLocalStorage(todoItem) {
    let values;
    if(localStorage.getItem('todos') === null){
        values = [];
    } else {
        values = JSON.parse(localStorage.getItem('todos'));
    }

    values.forEach(function(todo, index){
        if(todoItem.firstChild.textContent === todo){
            values.splice(index, 1);
        }
    });

    localStorage.setItem('todos', JSON.stringify(values));
}

As usual, we checked if the key value is empty, if yes, we store an empty array, if no, we get the data and store in a variable name "values"

We then loop through the values with a forEach array method by passing two parameters, one which is todo, would be the one to store individual todo item get from the array, and the other which is index would keep track of the index count.

The if condition is checking if the textContent of the todoItem (the one passed into the function) is strictly equal to the todo (remember todo store individual todo item as loop iterates), if that is true, we use splice function to remove whatever index the item is located. Note that, this isn't actually storing anything yet, we just spliced an item identified by a certain index from the values array.

To then store the value to the localStorage, we use: localStorage.setItem('todos', JSON.stringify(values));
and that's it.

To-Do Application With JavaScript Illustration




Related Post(s)

  • Getting Started With JavaScript (Introduction)

    This is my first JavaScript guide on this blog, and in this guide, we would go over the introduction of JavaScript, but first… What is JavaScript? JavaScript is a client-side scripting language su

  • Operator in Javascript

    Operators in javascript can either be used for logical expression where you connect two or more expressions and can be a comparison expression where you compare two values. You can also use an operat

  • Exploring Data Types, and Variables in JavaScript

    In JavaScript or any programming language, a data type is an attribute of data that tells the interpreter how the programs intend to use the given data. JavaScript support a couple of data type whic

  • Creating a Loader and Remove it After a Specific Time Using JavaScript/CSS/SVG

    In this guide, we would create a loader, and remove it after a specific time using CSS and JavaScript. The icon would be in SVG format, and the rotation would be done in CSS, while the removal after

  • Object Oriented Programming in JavaScript (The ES5 Way)

    In this guide, you'll learn and understand how to create and use Objects in JavaScript (ES5). While ES6 already supports creating classes (which is the way you create objects in Java, PHP, etc), they

  • Working With The Local & Session Storage In JavaScript

    We previously take a deep dive into the Windows Object, Properties, Methods, and even the Document Object itself, in this guide, we would explore working with the local and session storage in JavaScr