Welcome to a tutorial on how to create a simple books list web app with pure Javascript. It’s the good old and boring school assignment. Yep, there are a ton of such tutorials all over the Internet, so here’s one that is slightly different. An offline books list progressive web app that is installable. Read on!
TABLE OF CONTENTS
JAVASCRIPT BOOKS LIST
All right, let us now get into more details on how the books list web app works.
PART 1) THE HTML
<!-- (A) BOOK LIST -->
<div id="wrap">
<div id="list"></div>
<div id="dummy" onclick="bl.toggle(true)">ADD BOOK</div>
</div>
<!-- (B) BOOK FORM -->
<div id="form"><form onsubmit="return bl.save()">
<div id="fClose" onclick="bl.toggle(false)">X</div>
<input type="hidden" id="fID">
<label>Title</label>
<input type="text" id="fTitle" required>
<label>Author</label>
<input type="text" id="fAuthor" required>
<label>ISBN (optional)</label>
<input type="text" id="fISBN">
<input type="submit" value="Save">
</form></div>
The HTML should be pretty self-explanatory, there are only 2 main sections:
<div id="list">
A container for Javascript to generate the books list into.<div id="form">
A hidden HTML form to add/edit a book entry.
PART 2) THE JAVASCRIPT
2A) INIT APP
var bl = {
// (A) INIT
data : null, // books list
hList : null, // html books list
hForm : null, // html book form
fID : null, fTitle : null, fAuthor : null, fISBN : null, // html form fields
init : () => {
// (A1) GET HTML ELEMENTS
bl.hList = document.getElementById("list");
bl.hForm = document.getElementById("form");
bl.fID = document.getElementById("fID");
bl.fTitle = document.getElementById("fTitle");
bl.fAuthor = document.getElementById("fAuthor");
bl.fISBN = document.getElementById("fISBN");
// (A2) LOAD ENTRIES
bl.data = localStorage.getItem("books");
if (bl.data==null) { bl.data = []; }
else { bl.data = JSON.parse(bl.data); }
// (A3) DRAW ENTRIES
bl.draw();
},
// ...
};
window.onload = bl.init;
On window load, bl.init()
will run. Beginners may get confused, but keep calm and look carefully.
- (A1) Get all the related HTML elements.
- (A2) We load book entries from
localStorage
intobl.data
– For those who don’t know, this is persistent storage. Data saved inlocalStorage
will not be lost when the user navigates away from the site. - (A3) Draw the book entries.
Yep, it’s just slightly long-winded. Not difficult.
2B) TOGGLE HTML FORM
// (B) TOGGLE FORM
toggle : id => {
if (id===false) {
bl.fID.value = "";
bl.fTitle.value = "";
bl.fAuthor.value = "";
bl.fISBN.value = "";
bl.hForm.classList.remove("show");
} else {
if (Number.isInteger(id)) {
bl.fID.value = id;
bl.fTitle.value = bl.data[id].t;
bl.fAuthor.value = bl.data[id].a;
bl.fISBN.value = bl.data[id].i;
}
bl.hForm.classList.add("show");
}
},
Remember the hidden add/edit book HTML form? This bl.toggle()
function is used to work with it. Take note of the id
though.
id === false
Reset the HTML form and hide it.id === true
Show the HTML form.Number.isInteger(id)
This indicates “edit mode”.bl.data
is an array of books, and this will populate the HTML form with the book selected inbl.data[id]
.
2C) DRAW BOOKS LIST HTML
// (C) DRAW BOOKS HTML
draw : () => {
let row;
bl.hList.innerHTML = "";
bl.data.forEach((book, i) => {
row = document.createElement("div");
row.className = "row";
row.innerHTML = `<div class="rDel" onclick="bl.del(${i})">X</div>
<div class="rTxt">
<div class="rTitle">${book.t}${(book.i?" ("+book.i+")":"")}</div>
<div class="rAuthor">${book.a}</div>
</div>
<div class="rEdit" onclick="bl.toggle(${i})">✎</div>`;
bl.hList.appendChild(row);
});
},
Loop through bl.data
and draw the HTML in <div id="list">
.
2D) SAVE & DELETE BOOK
// (D) SAVE BOOK
save : () => {
// (D1) GET DATA
let data = {
t : bl.fTitle.value,
a : bl.fAuthor.value,
i : bl.fISBN.value
};
// (D2) UPDATE DATA ARRAY
if (bl.fID.value=="") { bl.data.push(data); }
else { bl.data[parseInt(bl.fID.value)] = data; }
localStorage.setItem("books", JSON.stringify(bl.data));
// (D3) UPDATE HTML INTERFACE
bl.toggle(false);
bl.draw();
return false;
},
// (E) DELETE BOOK
del : id => { if (confirm("Delete book?")) {
bl.data.splice(id, 1);
localStorage.setItem("books", JSON.stringify(bl.data));
bl.draw();
}}
Lastly, remember that book entries are saved into localStorage
? These 2 functions do just that.
PART 3) PROGRESSIVE WEB APP
3A) HTML HEADERS
<!-- ANDROID + CHROME + APPLE + WINDOWS APP -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="white">
<link rel="apple-touch-icon" href="icon-512.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="Books List">
<meta name="msapplication-TileImage" content="icon-512.png">
<meta name="msapplication-TileColor" content="#ffffff">
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="3a-manifest.json">
<!-- SERVICE WORKER -->
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("3b-worker.js");
}
</script>
At this stage, we already have a fully functioning app. This step is optional, but to turn this into an “installable offline web app”, we need to add a few things to the HTML <head>
.
- HTML meta tags to define the icons and stuff. Pain in the “S” where everyone uses different meta tags and icon sizes. I figured to just use a huge 512 X 512 icon and let the platforms resize.
- Define a web manifest file.
- Register a service worker.
3B) WEB MANIFEST
{
"short_name": "Books",
"name": "Books List",
"icons": [{
"src": "favicon.png",
"sizes": "64x64",
"type": "image/png"
}, {
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png"
}],
"start_url": "1-books-list.html",
"scope": "/",
"background_color": "white",
"theme_color": "white",
"display": "standalone"
}
The web manifest file is what it is… A file that contains information on the web app – The name, icons, themes, settings, and more.
3C) SERVICE WORKER
// (A) CREATE/INSTALL CACHE
self.addEventListener("install", evt => {
self.skipWaiting();
evt.waitUntil(
caches.open("JSBooks")
.then(cache => cache.addAll([
"favicon.png",
"icon-512.png",
"1-book-list.html",
"2-book-list.js",
"2-book-list.css"
]))
.catch(err => console.error(err))
);
});
// (B) CLAIM CONTROL INSTANTLY
self.addEventListener("activate", evt => self.clients.claim());
// (B) CREATE/INSTALL CACHE
self.addEventListener("install", (evt) => {
self.skipWaiting();
evt.waitUntil(
caches.open(cName)
.then((cache) => { return cache.addAll(cFiles); })
.catch((err) => { console.error(err) })
);
});
// (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))
));
Lastly, a service worker is simply “Javascript that runs in the background”. For this worker:
- (A) We create a new browser cache and store all the books list project files.
- (C) Listen to fetch requests and “hijack” them. If the requested file is found in the cache, use it. Fall back to load from the network if not.
In other words, turning this into an offline app by loading it off the browser cache.
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 the tutorial, and here is a small section on some extras and links that may be useful to you.
COMPATIBILITY CHECKS
- Arrow Functions – CanIUse
- Local Storage – CanIUse
- Add To Home Screen – CanIUse
- Service Worker – CanIUse
This example will work on most modern “Grade A” browsers. “Installable” will only work on certain browsers and platforms at the time of writing.
LINKS & REFERENCES
- Example on CodePen – Javascript Books List
THE END
Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!