Lesson 5
Storing Todos Locally
Topic Overview and Actualization

Hello, and welcome to our journey about todo lists! In this lesson, we delve into the world of Data Persistence and Local Storage. We're going to figure out how to store our todo items right on our own machine, specifically in the web browser. By the end of this lesson, you'll be well-equipped to store and retrieve data even after the browser is closed and reopened. This feature gives the user freedom from the fear of losing their data accidentally. It's as if we're creating our own mini-database right in our web browser. So, let's plow ahead and get on with this exciting journey!

Introduction to Local Storage

Local storage is a type of web storage that allows JavaScript websites and apps to store and access data right in the browser with no expiration time. It's like a small database that lives in your browser. Even after closing and reopening the browser, the data persists, giving us a form of data persistence.

Can you think of examples where this could prove useful? Imagine you're filling out a lengthy form and accidentally refresh the page. Without local storage, all the information you've filled out would be lost. But if the website cleverly stored this data in local storage as you were filling it out, it could conveniently recover it, recovering your data and saving you time. Local storage could make this clever recovery possible. Now let's see how we can use it to store our todo items.

localStorage Methods

First, let's look at the local storage toolbox. There are three primary methods that are of utmost importance in using local storage: setItem, getItem, and removeItem.

At its core, local storage is like a box with many compartments, and we store data in these compartments. But to fetch the data or remove it when it's no longer needed, we need some tools, right? That's what these methods essentially are — our tools to work with local storage.

  1. setItem method: This is used to put data into local storage. It's like putting a thing into a compartment of our box. It requires two arguments: a key and a value. The key is the name of the compartment, and the value is the thing itself. Let's put something into our local storage:

    JavaScript
    1localStorage.setItem('favoriteColor', 'Blue');

    Here, favoriteColor is the key, and Blue is the value. So, we've stored Blue into a compartment named favoriteColor.

  2. getItem method: Once our data is in local storage, we need a way to retrieve it. That's what getItem does. The key is required as an argument to access the compartment, as shown below:

    JavaScript
    1const myFavoriteColor = localStorage.getItem('favoriteColor'); 2console.log(myFavoriteColor); // Displays Blue on the console

    Here, we are fetching data from the compartment named favoriteColor. So, myFavoriteColor will hold the value Blue.

  3. removeItem method: Sometimes, we no longer need the data we put in local storage, so we need to remove it. This is done using removeItem.

    JavaScript
    1localStorage.removeItem('favoriteColor');

    We can see that favoriteColor is removed from local storage. If you try to get favoriteColor now, it will return null because it's not in local storage anymore.

Saving our Todo List

In our todo application, we have a function called saveTodos. Its role is like a very efficient librarian who checks the todos (like books in a library), collects them all, and puts them nicely into local storage (like a library's shelves). Every time a change is made to our todos, like adding a new todo or marking a todo as completed, the librarian immediately updates the storage, ensuring the correct state of todos is maintained.

Here's the code for the saveTodos function:

JavaScript
1function saveTodos() { 2 const todos = []; 3 document.querySelectorAll('#todo-list li').forEach(li => { 4 const todoText = li.childNodes[1].nodeValue; 5 const isChecked = li.childNodes[0].checked; 6 todos.push({ text: todoText, checked: isChecked }); 7 }); 8 localStorage.setItem('todos', JSON.stringify(todos)); 9}

Let's go through it step-by-step:

  1. We define an empty array todos.
  2. We use document.querySelectorAll('#todo-list li') to get all the list items (i.e., our todos), then use the forEach method to loop through each item.
  3. For each item, we get its text content and whether its checkbox is checked. We then create an object representing our todo and add it to our todos array.
  4. Finally, we call localStorage.setItem to store the todos array in local storage. Since local storage can only store strings, we use JSON.stringify(todos) to convert the array into a string format.

To see the stored todos in your console, you can use the getItem method to fetch the 'todos' item from local storage. This will return the stored string representation of the todos' array, which you can then log to the console to inspect:

JavaScript
1console.log(localStorage.getItem('todos'));

When you run this, you'll see the stringified array of todos:

JSON
1[{"text":"Item 1","checked":false},{"text":"Item 2","checked":true},{"text":"Item 3","checked":false}]

This output shows that we have three todos. The first todo ("Item 1") is not checked, the second todo ("Item 2") is checked, and the third todo ("Item 3") is not checked. By logging the value stored in local storage, we can verify that our todos are being saved correctly.

Saving After Each Change

In certain scenarios, it's vital to save the state frequently, even after every single change — whether adding, deleting, moving a todo, or updating a checkbox. This frequent saving ensures that our local storage is always up-to-date. Imagine if the browser closes unexpectedly; without saving each change, those unsaved changes would be lost. By constantly updating local storage, we guarantee that our todo list will always return to its correct state, even after the browser is closed and reopened. Think of it as continually updating a diary to avoid losing track of your tasks.

Loading our Todo List

Our loadTodos function is like the opposite of saveTodos. It's our trusted librarian who, when the library opens, gets all the books (i.e., todos) from the shelves (i.e., local storage) and arranges them for viewing. The function code is as follows:

JavaScript
1function loadTodos() { 2 const todos = JSON.parse(localStorage.getItem('todos')); 3 if (todos) { 4 todos.forEach(todo => { 5 const li = document.createElement('li'); 6 const checkbox = document.createElement('input'); 7 checkbox.type = 'checkbox'; 8 checkbox.checked = todo.checked; 9 checkbox.addEventListener('change', function() { 10 if (checkbox.checked) { 11 li.style.textDecoration = 'line-through'; 12 } else { 13 li.style.textDecoration = 'none'; 14 } 15 saveTodos(); 16 }); 17 18 li.appendChild(checkbox); 19 li.appendChild(document.createTextNode(todo.text)); 20 21 document.getElementById('todo-list').appendChild(li); 22 23 if (todo.checked) { 24 li.style.textDecoration = 'line-through'; 25 } 26 }); 27 } 28}

Here, we are fetching our todos from local storage using localStorage.getItem. But why are we surrounding it with JSON.parse? Well, remember that we stored our todos as a string with JSON.stringify. Now, we need to turn it back into an array of objects we can use in JavaScript, and JSON.parse does exactly that. The rest of the code within the function body creates li elements just like before, with all the event listeners, and adds them to the todo list in the same arrangement they were stored in.

Autoloading our Todos

Loading todos wouldn't be very useful if it only worked once — what about when the page is reloaded, or when we open our webpage the next day?

JavaScript
1document.addEventListener('DOMContentLoaded', loadTodos);

This line of code ensures that our todos are loaded every time our webpage fully loads, bringing our todos back to life. Without DOMContentLoaded, our todo list would be empty every time we reloaded the page, which wouldn't be very useful, would it?

Putting it all Together

Now let's take all the pieces we've discussed and put them together into a cohesive whole. We'll create a simple todo list application that can add, display, and save todos, allowing us to see exactly how local storage can be implemented in a real-world scenario.

  • Adding a New Todo: We'll use an input field and a button to add new todos to the list.
  • Saving Todos: Each new todo will be saved in local storage so that it persists across browser sessions.
  • Loading Todos: When the page is loaded, todos from local storage will be displayed.
  • Toggling Complete Status: Each todo will have a checkbox to mark it as complete, and its status will be stored in local storage.

Here's the full code to implement this functionality:

JavaScript
1document.getElementById('add-btn').addEventListener('click', function() { 2 const newTodo = document.getElementById('new-todo').value; 3 if (newTodo.trim() === '') return; 4 5 const li = document.createElement('li'); 6 const checkbox = document.createElement('input'); 7 checkbox.type = 'checkbox'; 8 checkbox.addEventListener('change', function() { 9 if (checkbox.checked) { 10 li.style.textDecoration = 'line-through'; 11 } else { 12 li.style.textDecoration = 'none'; 13 } 14 saveTodos(); 15 }); 16 17 li.appendChild(checkbox); 18 li.appendChild(document.createTextNode(newTodo)); 19 20 document.getElementById('todo-list').appendChild(li); 21 document.getElementById('new-todo').value = ''; 22 23 saveTodos(); 24}); 25 26function saveTodos() { 27 const todos = []; 28 document.querySelectorAll('#todo-list li').forEach(li => { 29 const todoText = li.childNodes[1].nodeValue; 30 const isChecked = li.childNodes[0].checked; 31 todos.push({ text: todoText, checked: isChecked }); 32 }); 33 localStorage.setItem('todos', JSON.stringify(todos)); 34} 35 36function loadTodos() { 37 const todos = JSON.parse(localStorage.getItem('todos')); 38 if (todos) { 39 todos.forEach(todo => { 40 const li = document.createElement('li'); 41 const checkbox = document.createElement('input'); 42 checkbox.type = 'checkbox'; 43 checkbox.checked = todo.checked; 44 checkbox.addEventListener('change', function() { 45 if (checkbox.checked) { 46 li.style.textDecoration = 'line-through'; 47 } else { 48 li.style.textDecoration = 'none'; 49 } 50 saveTodos(); 51 }); 52 53 li.appendChild(checkbox); 54 li.appendChild(document.createTextNode(todo.text)); 55 56 document.getElementById('todo-list').appendChild(li); 57 58 if (todo.checked) { 59 li.style.textDecoration = 'line-through'; 60 } 61 }); 62 } 63} 64 65document.addEventListener('DOMContentLoaded', loadTodos);

This example provides a comprehensive demonstration of how local storage can be used to enhance a todo list application by preserving data between sessions.

Local Storage Capacity Limits

While local storage is very useful, keep in mind that it has a storage limit (usually around 5-10 MB per domain). Exceeding this limit could result in data not being saved. It's important to monitor the data size stored to avoid hitting this limit, especially when dealing with large applications.

Lesson Summary

Congratulations! You've unlocked another exciting aspect of JavaScript — Local Storage! Now, you're equipped with the tools to store and retrieve data, ensuring that it persists even if the browser is closed and re-opened.

We've discussed the local storage concept and its methods, setItem, getItem, and removeItem. We also delved into saveTodos() and loadTodos(), our methods for saving todos to local storage and loading them from there. Importantly, we learned why it’s crucial to save todos after each change and touched upon the local storage capacity limits.

Next up, you'll get a chance to apply your newfound knowledge in practice exercises! Remember, exercises are an integral part of learning programming. You’ve got this! Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.