Javascript Calendar Progressive Web App (That Works Offline)

Javascript Calendar, do a quick search on the Internet, and there are a ton of these. But nope, this one is going to be a little different. This is a small experiment of mine to create a fully offline Javascript progressive web app – That’s right, this is an installable Javascript Calendar PWA that works even when offline. 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, cache storage, and indexed database.
  • 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 Calendar PWA.

 

PART 1) SINGLE PAGE APP

1A) HTML MAIN PAGE

js-calendar.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>

Yes, that’s all to the landing page.

  • <div id="cb-main"> Contain to load all the content into, using AJAX.
  • <div id="cb-info"> A simple pop-up message box that dismisses itself after 2 seconds.

 

1B) SINGLE-PAGE JAVASCRIPT ENGINE

assets/cb.js
var cb = {
  // (A) REGISTER PAGES HERE
  pages : {
    home : {file:"home.inc", load:cal.prepare},
    form : {file:"form.inc", load:cal.get}
  },
 
  // (B) LOAD PAGE
  load : {
    READ WINDOW.LOCATION.HASH
    MAP TO CB.PAGE & AJAX LOAD PAGE INTO <DIV ID="CB-MAIN">
  }
};

How this simplistic engine works:

  • Reads the hash from window.location.hash, and maps it to cb.pages.
  • For example, #form will map to cb.pages["form"].
  • It will then proceed to load cb.pages[HASH]["file"] into <div id="cb-main>.
  • Lastly, run cb.pages[HASH]["load"] if it is defined.

I can hear the “expert code trolls” saying “this is stupid, React/Angular/Vue is better”. Sure. There are only a few pages in the project. So… I don’t see a point in adding loading bloat for that.

 

 

PART 2) HTML PAGES

2A) CALENDAR PAGE

home.inc
<!-- (A) HEADER -->
<header class="cb-head">
  <!-- (A1) TITLE -->
  <h1 class="cb-head-title">My Calendar</h1>
 
  <!-- (A2) BUTTONS -->
  <div class="cb-head-btn">
    <button class="btn-ico mi" onclick="cal.show()">add_box</button>
  </div>
</header>
 
<!-- (B) CALENDAR -->
<div class="cb-body">
  <!-- (B1) MONTH YEAR SELECTORS -->
  <div id="cal-date">
    <select id="cal-mth"></select>
    <select id="cal-year"></select>
  </div>
 
  <!-- (B2) CALENDAR ITSELF -->
  <div id="cal-wrap"></div>
</div>

This page shouldn’t be too much of a mystery.

  1. Blue header bar at the top.
  2. The calendar itself is at the bottom. The month and year selectors are empty and will be generated with Javascript.

 

2B) ADD/EDIT EVENT PAGE

form.inc
<!-- (A) HEADER -->
<header class="cb-head">
  <!-- (A1) TITLE -->
  <h1 class="cb-head-title" id="cal-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="cal-form-del">delete</button>
  </div>
</header>
 
<!-- (C) CALENDAR EVENT FORM -->
<form id="cal-form" class="cb-body cb-form" onsubmit="return cal.save()">
  <label for="cal-e-start" class="form-field">Start Date</label>
  <input type="date" class="form-field" id="cal-e-start" required/>
  <label for="cal-e-end" class="form-field">End Date</label>
  <input type="date" class="form-field" id="cal-e-end" required/>
  <label for="cal-e-txt" class="form-field">Event</label>
  <textarea class="form-field" id="cal-e-txt" required></textarea>
  <label for="cal-e-color" class="form-field">Color</label>
  <input type="color" class="form-field" id="cal-e-color" required/>
  <input class="form-field btn-red" id="cal-save" type="submit" value="Save"/>
</form>

Another self-explanatory page… Just an HTML form for adding/editing events.

 

 

PART 3) APP INIT

assets/js-calendar.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, iName : "MyCalendar", // idb object & transaction
ready : 0, // number of ready components
init : () => {
  // (B1) ALL CHECKS & COMPONENTS GOOD TO GO?
  if (ready==1) {
    cal.ready++;
    if (cal.ready==2) { cb.load(); }
  }

  // (B2) REQUIREMENT CHECKS & SETUP
  else {
    CHECK BROWSER REQUIREMENTS
    INSTALL SERVICE WORKER
    PREPARE INDEXED DATABASE
  }
},
 
window.addEventListener("load", cal.init);

Not going to explain the starting sequence line-by-line, but the essentials:

  • (A) cal.err() Just a helper function to display “big red error messages” on the screen.
  • (B) cal.init() Fired on page load, B2 will run all the checks and do some setting up. B1 will proceed to start the app only when everything is properly done.

 

PART 4) APP ENGINE

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

  • (C) cal.prepare() This is called right after the calendar page is loaded. Sets up the month/year selectors.
  • (D) cal.load() Follows up from cal.prepare(), and whenever the user changes the month/year. This loads the calendar events from the indexed databases for the currently selected month/year.
  • (E) cal.draw() Follows up from cal.load() once again. When the events data is loaded, we draw the HTML calendar itself.
  • (F) cal.show() This is fired when the user clicks on “add new event” or “edit event”.
  • (G) cal.get() Follow up of cal.show(), load the selected event from the database if editing.
  • (H) cal.save() Save an event.
  • (I) cal.del() Delete an event.

 

 

PART 5) SERVICE WORKER

js-calendar-sw.js
// (A) FILES TO CACHE
const cName = "MyCalendar",
cFiles = [ LIST OF 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-calendar.html
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="js-calendar-manifest.json">

With the service worker and cached storage, 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!

4 thoughts on “Javascript Calendar Progressive Web App (That Works Offline)”

  1. i am learning about pwa and tried uploading this to a website to use it online and it works on chrome, but it does not seem to work on edge or safari. would you know why?

  2. This mini project is valuable for studies, and I would like a light on how I could make the offline push notification starting from the calendar event, will it be possible?

Leave a Comment

Your email address will not be published.