Javascript Events Calendar PWA (Free Code Download)

Javascript Calendar, there sure are a ton of these all over the Internet. So I figured to do something a little different. This is a small experiment of mine to create a Javascript Events Calendar progressive web app – That’s right, a serverless, installable web app that works even when the user is offline. Read on!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

GitHub | SourceForge

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.

 

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

 

QUICK NOTES

  • This is not a “newbie-friendly” open-source project and example. It involves the use of service workers, cache storage, and SQLite through web assembly.
  • “Grade A” browser required, alongside a https:// server. http://localhost is fine for testing too.

 

 

 

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 HTML

js-calendar.html
<!-- (A) PERIOD SELECTOR -->
<div id="calHead">
  <div id="calPeriod">
    <button id="calToday" type="button" class="icon-pushpin"></button>
    <button id="calBack" type="button" class="icon-circle-left"></button>
    <select id="calMonth"></select>
    <input id="calYear" type="number">
    <button id="calNext" type="button" class="icon-circle-right"></button>
  </div>
  <button id="calAdd" type="button" class="icon-plus"></button>
</div>
 
<!-- (B) CALENDAR WRAPPER -->
<div id="calWrap">
  <div id="calDays"></div>
  <div id="calBody"></div>
</div>
 
<!-- (C) EVENT FORM -->
<dialog id="calForm"><form method="dialog">
  <div id="evtCX" class="mi">clear</div>
  <h2 class="evt100">CALENDAR EVENT</h2>
  <div class="evt50">
    <label>Start</label>
    <input id="evtStart" type="datetime-local" required>
  </div>
  <div class="evt50">
    <label>End</label>
    <input id="evtEnd" type="datetime-local" required>
  </div>
  <div class="evt50">
    <label>Text Color</label>
    <input id="evtColor" type="color" value="#000000" required>
  </div>
  <div class="evt50">
    <label>Background Color</label>
    <input id="evtBG" type="color" value="#ffdbdb" required>
  </div>
  <div class="evt100">
    <label>Event</label>
    <input id="evtTxt" type="text" required>
  </div>
  <div class="evt100">
    <input type="hidden" id="evtID">
    <button type="button" id="evtDel" class="icon-bin2"></button>
    <button type="submit" id="evtSave" class="icon-checkmark"></button>
  </div>
</form></dialog>

Look no further, this is the only HTML page in the entire project.

  1. <div id="calHead"> The header bar.
    • <div id="calPeriod"> Month and year selectors.
    • <input id="calAdd"> Add new event button.
  2. <div id="calWrap"> The calendar will be generated here with Javascript.
  3. <dialog id="calForm"> Popup calendar event form.

 

 

PART 2) JAVASCRIPT

2A) SQLITE DATABASE

assets/js-calendar-db.js
var calDB = {
  // (A) PROPERTIES
  db : null, // database object
  cache : null, // storage cache object
  cname : "SQLDB", // cache storage name
  dbname : "/calendar.sqlite", // database storage name
 
  // (B) INITIALIZE
  init : async () => {
    // (B1) STORAGE CACHE
    calDB.cache = await caches.open(calDB.cname);
 
    // (B2) ATTEMPT TO LOAD DATABASE FROM STORAGE CACHE
    calDB.cache.match(calDB.dbname).then(async r => {
      // (B2-1) SQLJS
      const SQL = await initSqlJs({
        locateFile: filename => `assets/${filename}`
      });
 
      // (B2-2) NOPE - CREATE A NEW DATABASE
      if (r==undefined) {
        calDB.db = new SQL.Database();
        calDB.db.run(`CREATE TABLE events (
          evt_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
          evt_start TEXT NOT NULL,
          evt_end TEXT NOT NULL,
          evt_text TEXT NOT NULL,
          evt_color TEXT NOT NULL,
          evt_bg TEXT NOT NULL
        )`);
        calDB.db.run("CREATE INDEX evt_start ON events (evt_start)");
        calDB.db.run("CREATE INDEX evt_end ON events (evt_end)");
        await calDB.export();
        cal.initB();
      }
 
      // (B2-3) LOAD EXISTING DATABASE
      else {
        const buf = await r.arrayBuffer();
        calDB.db = new SQL.Database(new Uint8Array(buf));
        cal.initB();
      }
    });
  },
 
  // (C) EXPORT TO CACHE STORAGE
  export : async () => await calDB.cache.put(
    calDB.dbname, new Response(calDB.db.export())
  ),
 
  // (D) SAVE EVENT
  // data is an array!
  // data[0] = start date
  // data[1] = end date
  // data[2] = event text
  // data[3] = text color
  // data[4] = background color
  // data[5] = optional event id (is an update if specified)
  save : async (data) => {
    const sql = data.length==6
      ? "UPDATE events SET evt_start=?, evt_end=?, evt_text=?, evt_color=?, evt_bg=? WHERE evt_id=?"
      : "INSERT INTO events (evt_start, evt_end, evt_text, evt_color, evt_bg) VALUES (?,?,?,?,?)" ;
    calDB.db.run(sql, data);
    await calDB.export();
  },
 
  // (E) DELETE EVENT
  del : async (id) => {
    calDB.db.run("DELETE FROM events WHERE evt_id=?", [id]);
    await calDB.export();
  },

  // (F) GET EVENT
  get : id => 
    (calDB.db.prepare("SELECT * FROM events WHERE evt_id=$eid"))
    .getAsObject({$eid:id}),
 
  // (G) GET EVENTS FOR GIVEN PERIOD
  getPeriod : (start, end) => {
    // (G1) SQL QUERY
    const stmt = calDB.db.prepare(`SELECT * FROM events WHERE (
      (evt_start BETWEEN $start AND $end)
      OR (evt_end BETWEEN $start AND $end)
      OR (evt_start <= $start AND evt_end >= $end)
    )`);
    stmt.bind({$start:start, $end:end});

    // (G2) DATA YOGA
    // s & e : start & end date
    // c & b : text & background color
    // t : event text
    let events = {};
    while (stmt.step()) {
      const r = stmt.getAsObject();
      events[r["evt_id"]] = {
        "s" : r["evt_start"],
        "e" : r["evt_end"],
        "t" : r["evt_text"],
        "c" : r["evt_color"],
        "b" : r["evt_bg"]
      };
    }
    return events;
  }
};

Yes, you see that right – That’s SQL in Javascript, made possible with the SQLJS library. Don’t need to panic, here’s a quick summary:

  • (B) calDB.init() We will run this on page load. Pretty much create a database with only one table – events.
  • (C) calDB.export() Unfortunately, Javascript does not have direct access to the file system. But we can store the database file in a cache storage.
  • (D to G) CRUD.
    • cal.save() Add or update an event.
    • cal.del() Delete an event.
    • cal.get() Get specified event.
    • cal.getPeriod() Get all events within a given period.

 

2B) PROPERTIES & HELPERS

assets/js-calendar.js
var cal = {
  // (A) PROPERTIES
  // (A1) FLAGS & DATA
  mon : false,   // monday first
  events : null, // events data for current month/year
  sMth : 0,      // selected month
  sYear : 0,     // selected year
  sDIM : 0,      // number of days in selected month
  sF : 0,        // first date of the selected month (yyyy-mm-dd hh:mm:ss)
  sL : 0,        // last date of the selected month (yyyy-mm-dd hh:mm:ss)
  sFD : 0,       // first day of the selected month (mon-sun)
  sLD : 0,       // last day of the selected month (mon-sun)
 
  // (A2) HTML ELEMENTS
  hMth : null, hYear : null,      // month & year
  hCD : null, hCB : null,         // calendar days & body
  hFormWrap : null, hForm : null, // event form
  hfID : null, hfStart : null,    // event form fields
  hfEnd : null, hfTxt : null,
  hfColor : null, hfBG : null,
  hfDel : null,
 
  // (A3) HELPER FUNCTION - TRANSITION
  transit : swap => {
    if (document.startViewTransition) { document.startViewTransition(swap); }
    else { swap(); }
  },
  //...
};

All the calendar mechanics are contained within var cal = {}. There are a lot of settings, flags, and data… Not going to go through all of them one by one, just take note of mon : false – Change this if you want the week to start on Monday.

 

2C) INIT

assets/js-calendar.js
// (B) INIT PART 1 - REQUIREMENTS CHECK + WORKER + DATABASE INIT
initA : async () => {
  // (B1) REQUIREMENTS CHECK - WASM
  if (typeof WebAssembly == "undefined") { 
    alert("Your browser does not support Web Assembly.");
    return;
  }

  // (B2) REQUIREMENTS CHECK - STORAGE CACHE
  if (!"caches" in window) {
    alert("Your browser does not support cache storage.");
    return;
  }
 
  // (B3) REGISTER SERVICE WORKER
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("CB-worker.js");
  }

  // (B4) DATABASE INIT
  calDB.init();
},
 
 // (C) INIT PART 2 - SETUP & ENABLE HTML
initB : () => {
  // (C1) GET HTML ELEMENTS
  cal.hMth = document.getElementById("calMonth");
  cal.hYear = document.getElementById("calYear");
  cal.hCD = document.getElementById("calDays");
  cal.hCB = document.getElementById("calBody");
  cal.hFormWrap = document.getElementById("calForm");
  cal.hForm = cal.hFormWrap.querySelector("form");
  cal.hfID = document.getElementById("evtID");
  cal.hfStart = document.getElementById("evtStart");
  cal.hfEnd = document.getElementById("evtEnd");
  cal.hfTxt = document.getElementById("evtTxt");
  cal.hfColor = document.getElementById("evtColor");
  cal.hfBG = document.getElementById("evtBG");
  cal.hfDel = document.getElementById("evtDel");
 
  // (C2) MONTH & YEAR SELECTOR
  let now = new Date(), nowMth = now.getMonth() + 1;
  for (let [i,n] of Object.entries({
    1 : "January", 2 : "Febuary", 3 : "March", 4 : "April",
    5 : "May", 6 : "June", 7 : "July", 8 : "August",
    9 : "September", 10 : "October", 11 : "November", 12 : "December"
  })) {
    let opt = document.createElement("option");
    opt.value = i;
    opt.innerHTML = n;
    if (i==nowMth) { opt.selected = true; }
    cal.hMth.appendChild(opt);
  }
  cal.hYear.value = parseInt(now.getFullYear());
 
  // (C3) ATTACH CONTROLS
  cal.hMth.onchange = cal.load;
  cal.hYear.onchange = cal.load;
  document.getElementById("calToday").onclick = () => cal.today();
  document.getElementById("calBack").onclick = () => cal.pshift();
  document.getElementById("calNext").onclick = () => cal.pshift(1);
  document.getElementById("calAdd").onclick = () => cal.show();
  cal.hForm.onsubmit = () => cal.save();
  document.getElementById("evtCX").onclick = () => cal.transit(() => cal.hFormWrap.close());
  cal.hfDel.onclick = cal.del;
 
  // (C4) DRAW DAY NAMES
  let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  if (cal.mon) { days.push("Sun"); } else { days.unshift("Sun"); }
  for (let d of days) {
    let cell = document.createElement("div");
    cell.className = "calCell";
    cell.innerHTML = d;
    cal.hCD.appendChild(cell);
  }
 
  // (C5) LOAD & DRAW CALENDAR
  cal.load();
},
// ...
window.onload = cal.initA;

The initialization is split into two phases –

  • On page load, cal.initA() will run. Does requirement checks, registers a service worker, and sets the database up.
  • The second phase cal.initB() will deal with the HTML interface – Draw the year, months, days, and enable the interface.

 

 

2D) PERIOD SHIFT

assets/js-calendar.js
// (D) SHIFT CURRENT PERIOD BY 1 MONTH
pshift : forward => {
  cal.sMth = parseInt(cal.hMth.value);
  cal.sYear = parseInt(cal.hYear.value);
  if (forward) { cal.sMth++; } else { cal.sMth--; }
  if (cal.sMth > 12) { cal.sMth = 1; cal.sYear++; }
  if (cal.sMth < 1) { cal.sMth = 12; cal.sYear--; }
  cal.hMth.value = cal.sMth;
  cal.hYear.value = cal.sYear;
  cal.load();
},

// (E) JUMP TO TODAY
today : () => {
  let now = new Date(), ny = now.getFullYear(), nm = now.getMonth()+1;
  if (ny!=cal.sYear || (ny==cal.sYear && nm!=cal.sMth)) {
    cal.hMth.value = nm;
    cal.hYear.value = ny;
    cal.load();
  }
},
  • cal.pshift() Shifts the current period by one month.
  • cal.today() Jumps back to the current month and year.

 

2E) LOAD EVENTS

assets/js-calendar.js
// (F) LOAD EVENTS DATA FOR MONTH/YEAR
load : () => {
  // (F1) SET SELECTED PERIOD
  cal.sMth = parseInt(cal.hMth.value);
  cal.sYear = parseInt(cal.hYear.value);
  cal.sDIM = new Date(cal.sYear, cal.sMth, 0).getDate();
  cal.sFD = new Date(cal.sYear, cal.sMth-1, 1).getDay();
  cal.sLD = new Date(cal.sYear, cal.sMth-1, cal.sDIM).getDay();
  let m = cal.sMth;
  if (m < 10) { m = "0" + m; }
  cal.sF = `${cal.sYear}-${m}-01 00:00:00`;
  cal.sL = `${cal.sYear}-${m}-${cal.sDIM} 23:59:59`;
  
  // (F2) FETCH & DRAW
  cal.events = calDB.getPeriod(cal.sF, cal.sL);
  cal.hCB.innerHTML = "";
  cal.draw();
},

cal.load() pretty much loads the events data from the database into cal.events.

 

 

2F) DRAW CALENDAR

assets/js-calendar.js
// (G) DRAW CALENDAR
draw : () => {
  // (G1) CALCULATE DAY MONTH YEAR
  // note - jan is 0 & dec is 11 in js
  // note - sun is 0 & sat is 6 in js
  let now = new Date(), // current date
      nowMth = now.getMonth()+1, // current month
      nowYear = parseInt(now.getFullYear()), // current year
      nowDay = cal.sMth==nowMth && cal.sYear==nowYear ? now.getDate() : null ;

  // (G2) DRAW CALENDAR ROWS & CELLS
  // (G2-1) INIT
  let rowA, rowB, rowC, rowMap = {}, rowNum = 1, cell, cellNum = 1,
 
  // (G2-2) HELPER - DRAW A NEW ROW
  rower = () => {
    rowA = document.createElement("div");
    rowB = document.createElement("div");
    rowC = document.createElement("div");
    rowA.className = "calRow";
    rowA.id = "calRow" + rowNum;
    rowB.className = "calRowHead";
    rowC.className = "calRowBack";
    cal.hCB.appendChild(rowA);
    rowA.appendChild(rowB);
    rowA.appendChild(rowC);
  },
 
  // (G2-3) HELPER - DRAW A NEW CELL
  celler = day => {
    cell = document.createElement("div");
    cell.className = "calCell";
    if (day) {
      cell.innerHTML = day;
      cell.classList.add("calCellDay");
      cell.onclick = () => {
        cal.show();
        let d = +day, m = +cal.hMth.value,
            s = `${cal.hYear.value}-${String(m<10 ? "0"+m : m)}-${String(d<10 ? "0"+d : d)}T00:00:00`;
        cal.hfStart.value = s;
        cal.hfEnd.value = s;
      };
    }
    rowB.appendChild(cell);
    cell = document.createElement("div");
    cell.className = "calCell";
    if (day===undefined) { cell.classList.add("calBlank"); }
    if (day!==undefined && day==nowDay) { cell.classList.add("calToday"); }
    rowC.appendChild(cell);
  };
 
  // (G2-4) RESET CALENDAR
  cal.hCB.innerHTML = ""; rower();

  // (G2-5) BLANK CELLS BEFORE START OF MONTH
  if (cal.mon && cal.sFD != 1) {
    let blanks = cal.sFD==0 ? 7 : cal.sFD ;
    for (let i=1; i<blanks; i++) { celler(); cellNum++; }
  }
  if (!cal.mon && cal.sFD != 0) {
    for (let i=0; i<cal.sFD; i++) { celler(); cellNum++; }
  }

  // (G2-6) DAYS OF THE MONTH
  for (let i=1; i<=cal.sDIM; i++) {
    rowMap[i] = { r : rowNum, c : cellNum };
    celler(i);
    if (cellNum%7==0 && i!=cal.sDIM) { rowNum++; rower(); }
    cellNum++;
  }

  // (G2-7) BLANK CELLS AFTER END OF MONTH
  if (cal.mon && cal.sLD != 0) {
    let blanks = cal.sLD==6 ? 1 : 7-cal.sLD;
    for (let i=0; i<blanks; i++) { celler(); cellNum++; }
  }
  if (!cal.mon && cal.sLD != 6) {
    let blanks = cal.sLD==0 ? 6 : 6-cal.sLD;
    for (let i=0; i<blanks; i++) { celler(); cellNum++; }
  }
 
  // (G3) DRAW EVENTS
  if (Object.keys(cal.events).length > 0) { for (let [id,evt] of Object.entries(cal.events)) {
    // (G3-1) EVENT START & END DAY
    let sd = new Date(cal.toISODate(evt.s)),
        ed = new Date(cal.toISODate(evt.e));
    if (sd.getFullYear() < cal.sYear) { sd = 1; }
    else { sd = sd.getMonth()+1 < cal.sMth ? 1 : sd.getDate(); }
    if (ed.getFullYear() > cal.sYear) { ed = cal.sDIM; }
    else { ed = ed.getMonth()+1 > cal.sMth ? cal.sDIM : ed.getDate(); }

    // (G3-2) "MAP" ONTO HTML CALENDAR
    cell = {}; rowNum = 0;
    for (let i=sd; i<=ed; i++) {
      if (rowNum!=rowMap[i]["r"]) {
        cell[rowMap[i]["r"]] = { s:rowMap[i]["c"], e:0 };
        rowNum = rowMap[i]["r"];
      }
      if (cell[rowNum]) { cell[rowNum]["e"] = rowMap[i]["c"]; }
    }
 
    // (G3-3) DRAW HTML EVENT ROW
    for (let [r,c] of Object.entries(cell)) {
      let o = c.s - 1 - ((r-1) * 7), // event cell offset
          w = c.e - c.s + 1; // event cell width
      rowA = document.getElementById("calRow"+r);
      rowB = document.createElement("div");
      rowB.className = "calRowEvt";
      rowB.innerHTML = cal.events[id]["t"];
      rowB.style.color = cal.events[id]["c"];
      rowB.style.backgroundColor = cal.events[id]["b"];
      rowB.classList.add("w"+w);
      if (o!=0) { rowB.classList.add("o"+o); }
      rowB.onclick = () => cal.show(id);
      rowA.appendChild(rowB);
    }
  }}
},

Right, cal.draw() is massive. There is quite a bit of calculation, but it essentially draws the HTML “day cells”, then “maps” cal.events on.

 

2G) SHOW EVENT FORM

assets/js-calendar.js
// (H) SHOW EVENT FORM
show : id => {
  if (id) {
    cal.hfID.value = id;
    cal.hfStart.value = cal.toISODate(cal.events[id]["s"]);
    cal.hfEnd.value = cal.toISODate(cal.events[id]["e"]);
    cal.hfTxt.value = cal.events[id]["t"];
    cal.hfColor.value = cal.events[id]["c"];
    cal.hfBG.value = cal.events[id]["b"];
    cal.hfDel.style.display = "inline-block";
  } else {
    cal.hForm.reset();
    cal.hfID.value = "";
    cal.hfDel.style.display = "none";
  }
  cal.transit(() => cal.hFormWrap.show());
},

cal.show() To display the hidden popup event form.

  • id===NUMBER When the user clicks on an event to edit it. Load the event from indexDB and populate the form fields.
  • id===ANYTHING When the user clicks on “add”. Just show an empty event form.

 

 

2H) SAVE & DELETE EVENTS

assets/js-calendar.js
// (I) SAVE EVENT
save : () => {
  // (I1) COLLECT DATA
  // data[0] = start date
  // data[1] = end date
  // data[2] = event text
  // data[3] = text color
  // data[4] = background color
  var data = [
    cal.hfStart.value,
    cal.hfEnd.value,
    cal.hfTxt.value,
    cal.hfColor.value,
    cal.hfBG.value
  ];
  if (cal.hfID.value != "") { data.push(+cal.hfID.value); }

  // (I2) DATE CHECK
  if (new Date(data[0]) > new Date(data[1])) {
    alert("Start date cannot be later than end date!");
    return false;
  }

  // (I3) SAVE
  await calDB.save(data);
  cal.transit(() => cal.hFormWrap.close());
  cal.load();
  return false;
},
 
// (J) DELETE EVENT
del : () => { if (confirm("Delete Event?")) {
  await calDB.del(cal.hfID.value);
  cal.transit(() => cal.hFormWrap.close());
  cal.load();
}}

Pretty self-explanatory.

  • cal.save() gets the data from the HTML form and saves the event into the database.
  • cal.del() removes the specified event from the database.

 

PART 3) PROGRESSIVE WEB APP

3A) HTML META HEADERS

js-calendar.html
<!-- WEB APP & ICONS -->
<link rel="icon" href="assets/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="assets/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="JS Calendar">
<meta name="msapplication-TileImage" content="assets/icon-512.png">
<meta name="msapplication-TileColor" content="#ffffff">
 
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="CB-manifest.json">

The calendar is pretty much already complete. But to create a PWA, there are 3 things to address:

  • Add the above HTML meta headers to specify the icons/app name.
  • Register a web manifest.
  • Register a service worker.

 

3B) WEB MANIFEST

CB-manifest.json
{
  "short_name": "JS Calendar",
  "name": "JS Calendar",
  "icons": [{
    "src": "assets/favicon.png",
    "sizes": "128x128",
    "type": "image/png"
  }, {
    "src": "assets/ico-512.png",
    "sizes": "512x512",
    "type": "image/png"
  }],
  "start_url": "js-calendar.html",
  "scope": "/",
  "background_color": "white",
  "theme_color": "white",
  "display": "standalone"
}

The manifest file is what it is… A file to indicate the app icons, name, settings, and more.

 

3C) SERVICE WORKER

CB-worker.js
// (A) CREATE/INSTALL CACHE
self.addEventListener("install", evt => {
  self.skipWaiting();
  evt.waitUntil(
    caches.open("JSCalendar")
    .then(cache => cache.addAll([
      "assets/favicon.png",
      "assets/head-pwa-calendar.webp",
      "assets/ico-512.png",
      "assets/icomoon.woff2",
      "assets/js-calendar.css",
      "assets/js-calendar.js",
      "assets/js-calendar-db.js",
      "assets/sql-wasm.js",
      "assets/sql-wasm.wasm",
      "CB-manifest.json",
      "js-calendar.html"
    ]))
    .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 just a piece of Javascript that runs in the background. For this worker:

  • (A) Create a new storage cache and save the project files.
  • (C) “Hijack” the fetch requests. If the requested file is in the cache, use it. If not, load from the network.

In short, this worker will enable the web app to work in “offline mode”.

 

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

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!

10 thoughts on “Javascript Events Calendar PWA (Free Code Download)”

  1. Hello!
    How i set in europe date form, when the monday is first day of the week?
    Tanks for the answer.

  2. Hi,
    Thank you for this nice project.
    Unfortunately on iPhone and iPad (with Safari, Firefox or Brave), any 1 day events are displayed as running from the first to the last day of the month.
    Any suggestion to correct that behaviour ?
    Jean

    1. Sorry, I don’t have half-eaten apple devices to verify that… Works as intended on Chrome, Edge, FF, and Opera though – Start 4/4/2023 12:00 AM, end 4/4/2023 11:59 PM shows exactly as one day.

  3. I would like to pass a department ID into the calendar class to view events by department, but not having much luck. I have a department drop down which passes a link to the calendar through the url ( ?dept=23 ). I would like to grab that and use it in the SQL in the calendar class but I’m struggling getting that value into the class. Any help you could give me would be greatly appreciated

    1. 1) Create multiple object stores for different departments.
      2) On changing dept dropdown – Fetch data from the selected object store and redraw the HTML. The end.

      Throw the ideas of “server-side” GET, POST, SQL database out of the window. They don’t exist here, this is a serverless offline app.

  4. 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?

  5. 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?

Comments are closed.