Javascript Notes Progressive Web App (That Works Offline)

“Javascript Notes App”, just do a quick search and they are all over the Internet. But no, this is not another one of those “keep notes in local storage for beginners”. I figured the world needs better examples of modern web apps, so here it is, a “Notes PWA” that runs even when offline – You read that right, an installable and offline web app. Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes How It Works Useful Bits & Links
The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES & REQUIREMENTS

  • This is not a “newbie-friendly” open-source project cum example – It involves the use of service workers, indexed database, cache storage.
  • Too much of a hassle to create an “online example”, just download the source code or grab it from Github.
  • “Grade A” browser required, alongside an https:// server. http://localhost is fine for testing too.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

LICENSE & DOWNLOAD

This project is released under the MIT License. You are free to use it for your own personal and commercial projects, modify it as you see fit. On the condition that there the software is provided “as-is”. There are no warranties provided and “no strings attached”. Code Boxx and the authors are not liable for any claims, damages, or liabilities.

Download | GitHub

 

 

HOW IT WORKS

Not going to explain everything line-by-line (will take forever), but here’s a quick walkthrough of the Javascript Notes PWA.

 

PART 1) SINGLE PAGE APP

1A) THE HTML

js-notes.html
<!-- (A) MAIN CONTENTS -->
<div id="cb-main"></div>
 
<!-- (B) INFO BOX -->
<div id="cb-info">
  <div id="cb-info-ico" class="mi"></div>
  <div id="cb-info-txt"></div>
</div>

The main page itself only has 2 components.

  • <div id="cb-main"> Where we AJAX load the pages into.
  • <div id="cb-info"> A simple “popup notification” in the middle of the page. Dismisses itself in 2 seconds.

 

1B) THE JAVASCRIPT

assets/cb.js
var cb = {
  // (A) REGISTER PAGES HERE
  pages : {
    home : {file:"home.inc", load:notes.list},
    form : {file:"form.inc", load:notes.load}
  },
 
  // (B) LOAD PAGE
  load : () => {
    READ WINDOW.LOCATION.HASH, MAP TO CB.PAGES
    THEN AJAX FETCH PAGE & PUT INTO #CB-MAIN 
  }
};
window.addEventListener("hashchange", cb.load);

The quick essentials of how single-page app is being handled here:

  • We read the hash from window.location.hash.
  • “Translate and map” it to cb.pages. For example, #home will map to cb.pages["home"].
  • Do an AJAX fetch(cb.pages[HASH]["file"]) and put the contents into <div id="cb-main">.
  • Lastly, run cb.pages[HASH]["load"] if defined.

Yes, I figured that some master expert code ninja trolls are going to say “this is so dumb, just use React/Angular/Vue/Whatever”.

P.S. There are not even 5 pages in this project. Does it make sense to add to the loading bloat?

 

 

PART 2) APP PAGES

2A) LIST PAGE

home.inc
<!-- (A) HEADER -->
<header class="cb-head">
  <!-- (A1) TITLE -->
  <h1 class="cb-head-title">My Notes</h1>
 
  <!-- (A2) BUTTONS -->
  <div class="cb-head-btn">
    <button class="btn-ico mi" onclick="notes.show()">add_box</button>
  </div>
</header>

<!-- (B) NOTES LIST -->
<div id="notes-list" class="cb-body"></div>
 
<!-- (C) NOTE ROW TEMPLATE -->
<template id="note-template"><div class="note-row">
  <div class="note-left">
    <div class="note-title"></div>
    <div class="note-text"></div>
    <div class="note-time"></div>
  </div>
  <button class="note-del btn-ico-grey mi">delete</button>
  <button class="note-edit btn-ico-grey mi">edit</button>
</div></template>

This is the main page and the list of notes itself. Shouldn’t be difficult to figure out.

  1. <header class="cb-head"> The “blue” header bar.
  2. <div id="notes-list"> The list of notes.
  3. <template id="note-template"> HTML template for the notes.

 

 

2B) ADD/EDIT NOTE PAGE

form.inc
<!-- (A) HEADER -->
<header class="cb-head">
  <!-- (A1) TITLE -->
  <h1 class="cb-head-title" id="note-form-title"></h1>
 
  <!-- (A2) BUTTONS -->
  <div class="cb-head-btn">
    <a class="btn-ico mi" href="#home">reply</a>
    <button class="btn-ico mi" id="note-form-del">delete</button>
  </div>
</header>
 
<!-- (B) NOTES FORM -->
<form id="notes-form" class="cb-body cb-form" onsubmit="return notes.save()">
  <label for="note-title" class="form-field">Title</label>
  <input id="note-title" class="form-field" type="text" autocomplete="off" required/>
  <label for="note-text" class="form-field">Text</label>
  <textarea id="note-text" class="form-field" autocomplete="off" required></textarea>
  <input class="form-field btn-red" id="note-save" type="submit" value="Save"/>
</form>

Another easy one. Just the header and an HTML form to add/edit the notes.

 

 

PART 3) APP INIT

assets/js-notes.js
// (A) HELPER FUNCTION TO GENERATE ERROR MESSAGE
err : (msg) => {
  let row = document.createElement("div");
  row.className = "error";
  row.innerHTML = msg;
  document.getElementById("cb-main").appendChild(row);
},
 
// (B) INIT APP
iDB : null, iTX : null, // idb object & transaction
ready : 0, // number of ready components
init : () => {
  // (B1) ALL CHECKS & COMPONENTS GOOD TO GO?
  if (ready==1) {
    notes.ready++;
    if (notes.ready==2) { cb.load(); }
  }
 
  // (B2) REQUIREMENT CHECKS & SETUP
  else {
    CHECK BROWSER REQUIREMENTS
    INSTALL SERVICE WORKER
    PREPARE INDEXED DATABASE
  }
},
 
window.addEventListener("DOMContentLoaded", notes.init);

The app initialization is kind of confusing, but the quick basics:

  • (A) notes.err() Nothing but a helper function to display “big red error messages” on the screen.
  • (B) notes.init() Runs on page load, B2 will start first, doing all the browser checks and setup. Then, B1 will only proceed to start the app when everything is in place.

 

PART 4) NOTES ENGINE

The rest of assets/js-notes.js is the “notes engine” itself:

  • (C) notes.list() Draw the notes in “nice HTML”.
  • (D) notes.show() Show the selected note in the add/edit form.
  • (E) notes.load() A follow up of notes.show(), loads the given note if editing.
  • (F) notes.save() Add or update a note.
  • (G) notes.del() Self-explanatory. Delete a note.

 

PART 5) SERVICE WORKER

js-notes-sw.js
// (A) FILES TO CACHE
const cName = "MyNotes",
cFiles = [FILES TO CACHE];
 
// (B) CREATE/INSTALL CACHE
self.addEventListener("install", (evt) => {
  evt.waitUntil(
    caches.open(cName)
    .then((cache) => { return cache.addAll(cFiles); })
    .catch((err) => { console.error(err) })
  );
});
 
// (C) LOAD FROM CACHE, FALLBACK TO NETWORK IF NOT FOUND
self.addEventListener("fetch", (evt) => {
  evt.respondWith(
    caches.match(evt.request)
    .then((res) => { return res || fetch(evt.request); })
  );
});

The service worker is a simple one – It caches the entire app itself so that it can run even when offline.

 

 

PART 6) INSTALLABLE PWA

js-notes.html
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="js-notes-manifest.json">

With the service worker and indexed database, the app itself should already work offline. This is just one last bit – Add a manifest to make it installable.

 

USEFUL 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

Most of the required features are already well-supported on modern “Grade A” browsers.

 

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!

Leave a Comment

Your email address will not be published. Required fields are marked *