Local Storage and Event Delegation
demo
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>
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 =[];
We'll add a submit
eventlistener to form element
addItems.addEventListener("submit", addItem);
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();
}
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,
};
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("");
}
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}"
andfor="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));
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
You can also do the challenge at javascript30
Thanks @wesbos , WesBos to share this with us! 😊💖
Please comment and let me know your views
Top comments (0)