DEV Community

loading...
Cover image for JavaScript-30-Day-15

JavaScript-30-Day-15

KUMAR HARSH
The best way to learn is to teach. Programmer by Passion and Developer for Fun, and I love sharing my journey with everyone.
Updated on ・5 min read

Local Storage and Event Delegation

demo

ss

On day-15 we will learn how to make our state persistent by using local storage and learn how to use event delegation.

We have a simple list by default where we can add items and also they also have checkboxes which we can check, but the data and state of checked/unchecked boxes will stay as long as we don't refresh the page, after which every thing will be reset, and we'll work to make the this change persistent.

This is the default HTML we have:

<ul class="plates">
        <li>Loading Tapas...</li>
      </ul>
      <form class="add-items">
        <input type="text" name="item" placeholder="Item Name" required />
        <input type="submit" value="+ Add Item" />
      </form>
Enter fullscreen mode Exit fullscreen mode

We'll start by selecting the unordered list and the form element.

const addItems = document.querySelector(".add-items");
      const itemsList = document.querySelector(".plates");
      const items =[];
Enter fullscreen mode Exit fullscreen mode

We'll add a submit eventlistener to form element

addItems.addEventListener("submit", addItem);
Enter fullscreen mode Exit fullscreen mode

Why? submit, this is because one can submit by using the keyboard as well hence to cover all our bases we use a submit event listener.

Now inside the addItem function:

function addItem(e) {
        e.preventDefault();
        const text = this.querySelector("[name=item]").value;
        const item = {
          text,
          done: false,
        };
        items.push(item);
        populateList(items, itemsList);
        localStorage.setItem("items", JSON.stringify(items));
        this.reset();
      }
Enter fullscreen mode Exit fullscreen mode

First of all we use .preventDefault() method as by default the form is going to reload the page as soon as data is entered (generally to send data to server) and to prevent page from reloading we use the preventDefault() method.

Now we need to take the input user gives inside the box and put it into an object using const text = this.querySelector("[name=item]").value. The querySelector gives us the input element which has a property called .value which gives the text user has typed inside the input field.

You might be thinking why this and not document for query selector. The this here contains the form and we are searching for the one with attribute name=item. This technique is helpful when we are working with multiple forms.

After we put the text into an object using

const item = {
          text,
          done: false,
        };
Enter fullscreen mode Exit fullscreen mode

The done is for whether the item is checked and we will make it true when we click the checkbox.

Now we will push the object into items array using items.push(item)

Now we call populateList(items, itemsList) where we recreate the list every time user gives an input. The work of populateList function is to basically generate the HTML for the page.

this.reset() clears the input field after adding it to our list.

Here is the populateList() function:

function populateList(plates = [], platesList) {
        platesList.innerHTML = plates
          .map((plate, i) => {
            return `
            <li>
              <input type="checkbox" data-index=${i} id="item${i}" ${
              plate.done ? "checked" : ""
            } />
              <label for="item${i}">${plate.text}</label>
            </li>
            `;
          })
          .join("");
      }
Enter fullscreen mode Exit fullscreen mode

Here we basically get the text and create a list item according to it and pass it to the list we already have.

The plates = [] prevents the code from breaking if we forget to supply the arguments.

The id="item${i}" and for="item${i}" is same and that is how we link the label and input, when you click the label the input checks itself.

Now after all this we can add whatever we type to our list but it is not peristent and so we use local storage.

Local Storage is simply a key-value store and we can only use strings to store data in local storage

If you try to put something other than a string into Local Storage, it will convert it and store it as string.

So we use JSON.stringify to convert our objects/arrays into JSON string, and the method to set data in the storage is setItem.

localStorage.setItem("items", JSON.stringify(items));
Enter fullscreen mode Exit fullscreen mode

So we'll modify the const items =[] started with to
const items = JSON.parse(localStorage.getItem("items")) || []

Here getItem is used to take data our of local storage.

Now on page load it tries to load data from local storage and if it fails then fallback value is [] there.

Now we will work to make the toggling persistent, so that if we check an item it remains checked after refresh, but the problem is we can't listen for click events on list items because if we add new list items listeners won't work on them because input was created after we listened for them and hence they don't have eventListeners attached to them.

The whole idea of Event Delegation is rather than listening for click/change on checkbox directly , what we do is we look for something which is already going to be on the page at the time of listening.

Looking at our html the

    or unordered list with class=plates does exist on the page.

    Hence we will listen for a click on the plates and then we wil find out Did they actually mean to click on one of the input inside of it?

    If we click different items the target will be different and so we use e.target and if it is not input.

    Now we will go to items array and find out what is the status of each items whether checked/uncheced and accordingly toggle it's value and set it into local storage.

    Here is the function:

itemsList.addEventListener("click", toggleDone);

function toggleDone(e) {
        if (!e.target.matches("input")) {
          return;
          //skip this unless it's an input
        }
        console.log(e.target);
        const el = e.target;
        const index = el.dataset.index;
        items[index].done = !items[index].done;
        localStorage.setItem("items", JSON.stringify(items));
        populateList(items, itemsList);
      } 

Here we use the data-index attribute we gave each element so that we know the position of each element inside our array and we use that index to manipulate the elements.

So in short everytime we make a change, we mirror that change to local storage and then we rerender the entire list.

Here is the complete javascript:

const addItems = document.querySelector(".add-items");
      const itemsList = document.querySelector(".plates");
      const items = JSON.parse(localStorage.getItem("items")) || [];

      function addItem(e) {
        e.preventDefault();
        const text = this.querySelector("[name=item]").value;
        const item = {
          text,
          done: false,
        };
        console.log(item);
        items.push(item);
        populateList(items, itemsList);
        localStorage.setItem("items", JSON.stringify(items));
        this.reset();
      }

      function populateList(plates = [], platesList) {
        platesList.innerHTML = plates
          .map((plate, i) => {
            return `
            <li>
              <input type="checkbox" data-index=${i} id="item${i}" ${
              plate.done ? "checked" : ""
            } />
              <label for="item${i}">${plate.text}</label>
            </li>
            `;
          })
          .join("");
      }

      function toggleDone(e) {
        if (!e.target.matches("input")) {
          return;
          //skip this unless it's an input
        }
        console.log(e.target);
        const el = e.target;
        const index = el.dataset.index;
        items[index].done = !items[index].done;
        localStorage.setItem("items", JSON.stringify(items));
        populateList(items, itemsList);
      }

      addItems.addEventListener("submit", addItem);
      itemsList.addEventListener("click", toggleDone);
      populateList(items, itemsList);

and with this our project for the day was completed.

GitHub repo:

Blog on Day-14 of javascript30

Blog on Day-13 of javascript30

Blog on Day-12 of javascript30

Follow me on Twitter
Follow me on Linkedin

DEV Profile

cenacr007_harsh image

You can also do the challenge at javascript30

Thanks @wesbos , WesBos to share this with us! 😊💖

Please comment and let me know your views

Thank You!

Discussion (0)