Welcome to a tutorial on how to create a simple shopping list in vanilla Javascript. Nope, I totally did not get the idea for this tutorial from a certain Javascript challenge. It’s original content. 😆 Anyway, a shopping list is totally possible these days with modern Javascript, using the local storage API – That is what we will be using in this guide. Read on to find out!
TABLE OF CONTENTS
JAVASCRIPT SHOPPING LIST
All right, let us now get into the details of the Javascript shopping list itself.
SHOPPING LIST DEMO
PART 1) SHOPPING LIST HTML
<h1>SHOPPING LIST</h1>
<div id="shop-wrap">
<!-- (A) ADD NEW ITEM -->
<form id="shop-form">
<input type="text" id="shop-item" placeholder="Item Name" required disabled>
<input type="submit" id="shop-add" value="Add" disabled>
</form>
<!-- (B) SHOPPING LIST -->
<div id="shop-list"></div>
</div>
The HTML is very straightforward, and we only have 2 sections in the interface:
<form id="shop-form">
– Just a simple, single text field HTML form that we will use to add items to the list.<div id="shop-list">
– We will use Javascript to dynamically generate the shopping list in this empty container.
That’s all to the HTML page, the rest of the mechanics will be driven by Javascript.
PART 2) SHOPPING LIST JAVASCRIPT
2A) SHOPPING LIST INITIALIZATION
// (A) INITIALIZE SHOPPING LIST
items : [], // current shopping list
hform : null, // html add item <form>
hitem : null, // html add item <input> field
hadd : null, // html add item submit button
hlist : null, // html <div> shopping list
init : () => {
// (A1) GET HTML ELEMENTS
slist.hform = document.getElementById("shop-form");
slist.hitem = document.getElementById("shop-item");
slist.hadd = document.getElementById("shop-add");
slist.hlist = document.getElementById("shop-list");
// (A2) "ACTIVATE" HTML ADD ITEM FORM
slist.hitem.setAttribute("autocomplete", "off");
slist.hform.onsubmit = slist.add;
slist.hitem.disabled = false;
slist.hadd.disabled = false;
// (A3) RESTORE PREVIOUS SHOPPING LIST
if (localStorage.items == undefined) { localStorage.items = "[]"; }
slist.items = JSON.parse(localStorage.items);
// (A4) DRAW HTML SHOPPING LIST
slist.draw();
}
window.addEventListener("load", slist.init);
The first thing that we do on page load is to run the slist.init()
function to set up the shopping list. What this function does is very straightforward –
- (A1 & A2) Get all the related HTML elements, and initialize the “add item” form.
- (A3) Load the previously saved shopping cart from
localstorage.items
and restore it intoslist.items
. - (A4) Finally, draw the HTML shopping list.
Yep, that’s all. The only thing to take extra here is the slist.items
array – This is where we save the raw shopping list data and use it to draw the HTML.
2B) ADD ITEM & SAVING INTO LOCAL STORAGE
// (B) SAVE SHOPPING LIST INTO LOCAL STORAGE
save : () => {
if (localStorage.items == undefined) { localStorage.items = "[]"; }
localStorage.items = JSON.stringify(slist.items);
}
// (C) ADD NEW ITEM TO THE LIST
add : evt => {
// (C1) PREVENT FORM SUBMIT
evt.preventDefault();
// (C2) ADD NEW ITEM TO LIST
slist.items.push({
name : slist.hitem.value, // item name
done : false // true for "got it", false for "not yet"
});
slist.hitem.value = "";
slist.save();
// (C3) REDRAW HTML SHOPPING LIST
slist.draw();
}
Moving on, the slist.add()
function is fired whenever the user adds a new item.
- (C1) Captain Obvious, to stop the HTML form from submitting and reloading the entire page.
- (C2) Take note of how the new item is being pushed as an object into the
slist.items
array –{ name:"ITEM NAME", done:true/false }
- (B & C2) Update the
localstorage
. - (C3) Lastly, redraw the updated list.
2C) DELETE ITEM
// (D) DELETE SELECTED ITEM
delete : id => { if (confirm("Remove this item?")) {
slist.items.splice(id, 1);
slist.save();
slist.draw();
}}
Yep – This should be self-explanatory. Remove a specified item from slist.items
, update the localstorage
, then update the HTML.
2D) TOGGLE ITEM STATUS
// (E) TOGGLE ITEM BETWEEN "GOT IT" OR "NOT YET"
toggle : id => {
slist.items[id].done = !slist.items[id].done;
slist.save();
slist.draw();
}
This function toggles the “status” of the item between “got it” and “not yet” – Don’t think it needs explanation.
2E) DRAW HTML SHOPPING LIST
// (F) DRAW THE HTML SHOPPING LIST
draw : () => {
// (F1) RESET HTML LIST
slist.hlist.innerHTML = "";
// (F2) NO ITEMS
if (slist.items.length == 0) {
slist.hlist.innerHTML = "<div class='item-row item-name'>No items found.</div>";
}
// (F3) DRAW ITEMS
else {
for (let i in slist.items) {
// ITEM ROW
let row = document.createElement("div");
row.className = "item-row";
slist.hlist.appendChild(row);
// ITEM NAME
let name = document.createElement("div");
name.innerHTML = slist.items[i].name;
name.className = "item-name";
if (slist.items[i].done) { name.classList.add("item-got"); }
row.appendChild(name);
// DELETE BUTTON
let del = document.createElement("input");
del.className = "item-del";
del.type = "button";
del.value = "Delete";;
del.onclick = () => { slist.delete(i); };
row.appendChild(del);
// COMPLETED/NOT YET BUTTON
let ok = document.createElement("input");
ok.className = "item-ok";
ok.type = "button";
ok.value = slist.items[i].done ? "Not Yet" : "Got It";
ok.onclick = () => { slist.toggle(i); };
row.appendChild(ok);
}
}
}
Lastly, don’t let this slist.draw()
function intimidate you. It may look massive, but all it does is loop through slist.hlist
and create the HTML – Which we already went through in all of the above sections.
2F) GENERATED SHOPPING LIST HTML
<div class="item-row">
<div class="item-name">1 bag of sugar</div>
<input type="button" class="item-del" value="Delete">
<input type="button" class="item-ok" value="Got It">
</div>
If you are wondering – This is an example of what the above slist.draw()
generates into <div id="shop-list">
.
PART 3) PROGRESSIVE WEB APP
3A) HTML META HEADERS
<!-- WEB APP & ICONS -->
<link rel="icon" href="favicon.png" type="image/png">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="white">
<link rel="apple-touch-icon" href="favicon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="JS Shopping List">
<meta name="msapplication-TileImage" content="favicon.png">
<meta name="msapplication-TileColor" content="#ffffff">
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="3-manifest.json">
<!-- SERVICE WORKER -->
<script>if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("3-worker.js");
}</script>
We already have a fully functioning app, and this part is completely optional. But to turn this into an installable web app, all we need is:
- Define a whole load of “icons and app name” meta headers.
- Create a web manifest file.
- Register a service worker to support “offline mode”.
3B) WEB MANIFEST
{
"short_name": "JS Shopping List",
"name": "JS Shopping List",
"icons": [{
"src": "favicon.png",
"sizes": "512x512",
"type": "image/png"
}],
"start_url": "shop-list.html",
"scope": "/",
"background_color": "white",
"theme_color": "white",
"display": "standalone"
}
The manifest file is what it is. A file to contain information about your web app – The name, icons, preferred settings, etc..,
3C) SERVICE WORKER
// (A) CREATE/INSTALL CACHE
self.addEventListener("install", evt => {
self.skipWaiting();
evt.waitUntil(
caches.open("ShopList")
.then(cache => cache.addAll([
"favicon.png",
"1-shop-list.css",
"1-shop-list.html",
"2-shop-list.js",
"3-manifest.json"
]))
.catch(err => console.error(err))
);
});
// (B) CLAIM CONTROL INSTANTLY
self.addEventListener("activate", evt => self.clients.claim());
// (C) LOAD FROM CACHE FIRST, FALLBACK TO NETWORK IF NOT FOUND
self.addEventListener("fetch", evt => evt.respondWith(
caches.match(evt.request).then(res => res || fetch(evt.request))
));
For those who are new, a service worker is simply a piece of Javascript that runs in the background. In this simple worker:
- (A) We tell the browser to cache a whole list of project files.
- (C) “Hijack” the fetch requests. If the requested file is cached, use the cached copy; If not, fallback to loading from the network.
In a nutshell, enabling “offline mode”. This app will still run from the browser cache even when the user is offline.
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
EXAMPLE CODE DOWNLOAD
Click here for the source code on GitHub gist, just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
EXTRA BITS & LINKS
That’s all for this project, and here is a small section on some extras and links that may be useful to you.
COMPATIBILITY CHECKS
- Arrow Function – CanIUse
- Local Storage – CanIUse
- Add To Home Screen – CanIUse
- Service Workers – CanIUse
This shopping list will work on all modern browsers.
LINKS & REFERENCES
- Local Storage – MDN
- CSS Grid – MDN
- Example on CodePen – Simple Shopping List
THE END
Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
This was very helpful, thank you!
I noticed I am allowed to add spaces for the item and it adds a row with no item. I am trying to figure out how to add a statement that prevents a row to be added if the user enters just spaces, but I haven’t been able to figure that out. Any suggestions?
Thanks again.
https://stackoverflow.com/questions/10261986/how-to-detect-string-which-contains-only-spaces
Thanks! That helped so much!
Just customized a little bit of CSS, and done!