Progressive Web Apps (PWAs) and jQuery

Progressive Web Apps (PWAs) aim to deliver a seamless, app-like experience through web technologies. By using PWAs, developers can provide functionalities like offline access, push notifications, and improved performance. This chapter details how to build a PWA using jQuery, covering everything from basic concepts to advanced features.

What is a Progressive Web App (PWA)?

PWAs use modern web technologies to emulate the feel and functionality of a native mobile application. They offer the following key characteristics:

  1. Reliable: PWAs load instantly and offer offline functionality through caching.
  2. Fast: Quick loading and smooth interactions improve user experience.
  3. Engaging: PWAs feel like native apps and can use features such as push notifications

Why Use jQuery with PWAs?

jQuery is a fast, small, and feature-rich JavaScript library that simplifies tasks like HTML document traversal, event handling, and AJAX. When building PWAs, jQuery can help manage dynamic content and enhance user interactions, making the development process easier and more efficient

Setting Up a PWA

Basic Structure

Start by setting up a basic HTML file that includes jQuery.

Example:

				
					<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My PWA</title>
  <link rel="stylesheet" href="styles.css"> <script type="litespeed/javascript" data-src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head>
<body>
  <h1>Welcome to My PWA</h1>
  <button id="notify-btn">Enable Notifications</button> <script type="litespeed/javascript" data-src="app.js"></script> <script data-no-optimize="1">!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).LazyLoad=e()}(this,function(){"use strict";function e(){return(e=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,a=arguments[e];for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(t[n]=a[n])}return t}).apply(this,arguments)}function i(t){return e({},it,t)}function o(t,e){var n,a="LazyLoad::Initialized",i=new t(e);try{n=new CustomEvent(a,{detail:{instance:i}})}catch(t){(n=document.createEvent("CustomEvent")).initCustomEvent(a,!1,!1,{instance:i})}window.dispatchEvent(n)}function l(t,e){return t.getAttribute(gt+e)}function c(t){return l(t,bt)}function s(t,e){return function(t,e,n){e=gt+e;null!==n?t.setAttribute(e,n):t.removeAttribute(e)}(t,bt,e)}function r(t){return s(t,null),0}function u(t){return null===c(t)}function d(t){return c(t)===vt}function f(t,e,n,a){t&&(void 0===a?void 0===n?t(e):t(e,n):t(e,n,a))}function _(t,e){nt?t.classList.add(e):t.className+=(t.className?" ":"")+e}function v(t,e){nt?t.classList.remove(e):t.className=t.className.replace(new RegExp("(^|\\s+)"+e+"(\\s+|$)")," ").replace(/^\s+/,"").replace(/\s+$/,"")}function g(t){return t.llTempImage}function b(t,e){!e||(e=e._observer)&&e.unobserve(t)}function p(t,e){t&&(t.loadingCount+=e)}function h(t,e){t&&(t.toLoadCount=e)}function n(t){for(var e,n=[],a=0;e=t.children[a];a+=1)"SOURCE"===e.tagName&&n.push(e);return n}function m(t,e){(t=t.parentNode)&&"PICTURE"===t.tagName&&n(t).forEach(e)}function a(t,e){n(t).forEach(e)}function E(t){return!!t[st]}function I(t){return t[st]}function y(t){return delete t[st]}function A(e,t){var n;E(e)||(n={},t.forEach(function(t){n[t]=e.getAttribute(t)}),e[st]=n)}function k(a,t){var i;E(a)&&(i=I(a),t.forEach(function(t){var e,n;e=a,(t=i[n=t])?e.setAttribute(n,t):e.removeAttribute(n)}))}function L(t,e,n){_(t,e.class_loading),s(t,ut),n&&(p(n,1),f(e.callback_loading,t,n))}function w(t,e,n){n&&t.setAttribute(e,n)}function x(t,e){w(t,ct,l(t,e.data_sizes)),w(t,rt,l(t,e.data_srcset)),w(t,ot,l(t,e.data_src))}function O(t,e,n){var a=l(t,e.data_bg_multi),i=l(t,e.data_bg_multi_hidpi);(a=at&&i?i:a)&&(t.style.backgroundImage=a,n=n,_(t=t,(e=e).class_applied),s(t,ft),n&&(e.unobserve_completed&&b(t,e),f(e.callback_applied,t,n)))}function N(t,e){!e||0<e.loadingCount||0<e.toLoadCount||f(t.callback_finish,e)}function C(t,e,n){t.addEventListener(e,n),t.llEvLisnrs[e]=n}function M(t){return!!t.llEvLisnrs}function z(t){if(M(t)){var e,n,a=t.llEvLisnrs;for(e in a){var i=a[e];n=e,i=i,t.removeEventListener(n,i)}delete t.llEvLisnrs}}function R(t,e,n){var a;delete t.llTempImage,p(n,-1),(a=n)&&--a.toLoadCount,v(t,e.class_loading),e.unobserve_completed&&b(t,n)}function T(o,r,c){var l=g(o)||o;M(l)||function(t,e,n){M(t)||(t.llEvLisnrs={});var a="VIDEO"===t.tagName?"loadeddata":"load";C(t,a,e),C(t,"error",n)}(l,function(t){var e,n,a,i;n=r,a=c,i=d(e=o),R(e,n,a),_(e,n.class_loaded),s(e,dt),f(n.callback_loaded,e,a),i||N(n,a),z(l)},function(t){var e,n,a,i;n=r,a=c,i=d(e=o),R(e,n,a),_(e,n.class_error),s(e,_t),f(n.callback_error,e,a),i||N(n,a),z(l)})}function G(t,e,n){var a,i,o,r,c;t.llTempImage=document.createElement("IMG"),T(t,e,n),E(c=t)||(c[st]={backgroundImage:c.style.backgroundImage}),o=n,r=l(a=t,(i=e).data_bg),c=l(a,i.data_bg_hidpi),(r=at&&c?c:r)&&(a.style.backgroundImage='url("'.concat(r,'")'),g(a).setAttribute(ot,r),L(a,i,o)),O(t,e,n)}function D(t,e,n){var a;T(t,e,n),a=e,e=n,(t=It[(n=t).tagName])&&(t(n,a),L(n,a,e))}function V(t,e,n){var a;a=t,(-1<yt.indexOf(a.tagName)?D:G)(t,e,n)}function F(t,e,n){var a;t.setAttribute("loading","lazy"),T(t,e,n),a=e,(e=It[(n=t).tagName])&&e(n,a),s(t,vt)}function j(t){t.removeAttribute(ot),t.removeAttribute(rt),t.removeAttribute(ct)}function P(t){m(t,function(t){k(t,Et)}),k(t,Et)}function S(t){var e;(e=At[t.tagName])?e(t):E(e=t)&&(t=I(e),e.style.backgroundImage=t.backgroundImage)}function U(t,e){var n;S(t),n=e,u(e=t)||d(e)||(v(e,n.class_entered),v(e,n.class_exited),v(e,n.class_applied),v(e,n.class_loading),v(e,n.class_loaded),v(e,n.class_error)),r(t),y(t)}function $(t,e,n,a){var i;n.cancel_on_exit&&(c(t)!==ut||"IMG"===t.tagName&&(z(t),m(i=t,function(t){j(t)}),j(i),P(t),v(t,n.class_loading),p(a,-1),r(t),f(n.callback_cancel,t,e,a)))}function q(t,e,n,a){var i,o,r=(o=t,0<=pt.indexOf(c(o)));s(t,"entered"),_(t,n.class_entered),v(t,n.class_exited),i=t,o=a,n.unobserve_entered&&b(i,o),f(n.callback_enter,t,e,a),r||V(t,n,a)}function H(t){return t.use_native&&"loading"in HTMLImageElement.prototype}function B(t,i,o){t.forEach(function(t){return(a=t).isIntersecting||0<a.intersectionRatio?q(t.target,t,i,o):(e=t.target,n=t,a=i,t=o,void(u(e)||(_(e,a.class_exited),$(e,n,a,t),f(a.callback_exit,e,n,t))));var e,n,a})}function J(e,n){var t;et&&!H(e)&&(n._observer=new IntersectionObserver(function(t){B(t,e,n)},{root:(t=e).container===document?null:t.container,rootMargin:t.thresholds||t.threshold+"px"}))}function K(t){return Array.prototype.slice.call(t)}function Q(t){return t.container.querySelectorAll(t.elements_selector)}function W(t){return c(t)===_t}function X(t,e){return e=t||Q(e),K(e).filter(u)}function Y(e,t){var n;(n=Q(e),K(n).filter(W)).forEach(function(t){v(t,e.class_error),r(t)}),t.update()}function t(t,e){var n,a,t=i(t);this._settings=t,this.loadingCount=0,J(t,this),n=t,a=this,Z&&window.addEventListener("online",function(){Y(n,a)}),this.update(e)}var Z="undefined"!=typeof window,tt=Z&&!("onscroll"in window)||"undefined"!=typeof navigator&&/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent),et=Z&&"IntersectionObserver"in window,nt=Z&&"classList"in document.createElement("p"),at=Z&&1<window.devicePixelRatio,it={elements_selector:".lazy",container:tt||Z?document:null,threshold:300,thresholds:null,data_src:"src",data_srcset:"srcset",data_sizes:"sizes",data_bg:"bg",data_bg_hidpi:"bg-hidpi",data_bg_multi:"bg-multi",data_bg_multi_hidpi:"bg-multi-hidpi",data_poster:"poster",class_applied:"applied",class_loading:"litespeed-loading",class_loaded:"litespeed-loaded",class_error:"error",class_entered:"entered",class_exited:"exited",unobserve_completed:!0,unobserve_entered:!1,cancel_on_exit:!0,callback_enter:null,callback_exit:null,callback_applied:null,callback_loading:null,callback_loaded:null,callback_error:null,callback_finish:null,callback_cancel:null,use_native:!1},ot="src",rt="srcset",ct="sizes",lt="poster",st="llOriginalAttrs",ut="loading",dt="loaded",ft="applied",_t="error",vt="native",gt="data-",bt="ll-status",pt=[ut,dt,ft,_t],ht=[ot],mt=[ot,lt],Et=[ot,rt,ct],It={IMG:function(t,e){m(t,function(t){A(t,Et),x(t,e)}),A(t,Et),x(t,e)},IFRAME:function(t,e){A(t,ht),w(t,ot,l(t,e.data_src))},VIDEO:function(t,e){a(t,function(t){A(t,ht),w(t,ot,l(t,e.data_src))}),A(t,mt),w(t,lt,l(t,e.data_poster)),w(t,ot,l(t,e.data_src)),t.load()}},yt=["IMG","IFRAME","VIDEO"],At={IMG:P,IFRAME:function(t){k(t,ht)},VIDEO:function(t){a(t,function(t){k(t,ht)}),k(t,mt),t.load()}},kt=["IMG","IFRAME","VIDEO"];return t.prototype={update:function(t){var e,n,a,i=this._settings,o=X(t,i);{if(h(this,o.length),!tt&&et)return H(i)?(e=i,n=this,o.forEach(function(t){-1!==kt.indexOf(t.tagName)&&F(t,e,n)}),void h(n,0)):(t=this._observer,i=o,t.disconnect(),a=t,void i.forEach(function(t){a.observe(t)}));this.loadAll(o)}},destroy:function(){this._observer&&this._observer.disconnect(),Q(this._settings).forEach(function(t){y(t)}),delete this._observer,delete this._settings,delete this.loadingCount,delete this.toLoadCount},loadAll:function(t){var e=this,n=this._settings;X(t,n).forEach(function(t){b(t,e),V(t,n,e)})},restoreAll:function(){var e=this._settings;Q(e).forEach(function(t){U(t,e)})}},t.load=function(t,e){e=i(e);V(t,e)},t.resetStatus=function(t){r(t)},Z&&function(t,e){if(e)if(e.length)for(var n,a=0;n=e[a];a+=1)o(t,n);else o(t,e)}(t,window.lazyLoadOptions),t});!function(e,t){"use strict";function a(){t.body.classList.add("litespeed_lazyloaded")}function n(){console.log("[LiteSpeed] Start Lazy Load Images"),d=new LazyLoad({elements_selector:"[data-lazyloaded]",callback_finish:a}),o=function(){d.update()},e.MutationObserver&&new MutationObserver(o).observe(t.documentElement,{childList:!0,subtree:!0,attributes:!0})}var d,o;e.addEventListener?e.addEventListener("load",n,!1):e.attachEvent("onload",n)}(window,document);</script><script data-no-optimize="1">var litespeed_vary=document.cookie.replace(/(?:(?:^|.*;\s*)_lscache_vary\s*\=\s*([^;]*).*$)|^.*$/,"");litespeed_vary||fetch("/wp-content/plugins/litespeed-cache/guest.vary.php",{method:"POST",cache:"no-cache",redirect:"follow"}).then(e=>e.json()).then(e=>{console.log(e),e.hasOwnProperty("reload")&&"yes"==e.reload&&(sessionStorage.setItem("litespeed_docref",document.referrer),window.location.reload(!0))});</script><script data-optimized="1" type="litespeed/javascript" data-src="https://diginode.in/wp-content/litespeed/js/96d0e6d4ba93134cbdab615e06eb2824.js?ver=a1a89"></script><script>const litespeed_ui_events=["mouseover","click","keydown","wheel","touchmove","touchstart"];var urlCreator=window.URL||window.webkitURL;function litespeed_load_delayed_js_force(){console.log("[LiteSpeed] Start Load JS Delayed"),litespeed_ui_events.forEach(e=>{window.removeEventListener(e,litespeed_load_delayed_js_force,{passive:!0})}),document.querySelectorAll("iframe[data-litespeed-src]").forEach(e=>{e.setAttribute("src",e.getAttribute("data-litespeed-src"))}),"loading"==document.readyState?window.addEventListener("DOMContentLoaded",litespeed_load_delayed_js):litespeed_load_delayed_js()}litespeed_ui_events.forEach(e=>{window.addEventListener(e,litespeed_load_delayed_js_force,{passive:!0})});async function litespeed_load_delayed_js(){let t=[];for(var d in document.querySelectorAll('script[type="litespeed/javascript"]').forEach(e=>{t.push(e)}),t)await new Promise(e=>litespeed_load_one(t[d],e));document.dispatchEvent(new Event("DOMContentLiteSpeedLoaded")),window.dispatchEvent(new Event("DOMContentLiteSpeedLoaded"))}function litespeed_load_one(t,e){console.log("[LiteSpeed] Load ",t);var d=document.createElement("script");d.addEventListener("load",e),d.addEventListener("error",e),t.getAttributeNames().forEach(e=>{"type"!=e&&d.setAttribute("data-src"==e?"src":e,t.getAttribute(e))});let a=!(d.type="text/javascript");!d.src&&t.textContent&&(d.src=litespeed_inline2src(t.textContent),a=!0),t.after(d),t.remove(),a&&e()}function litespeed_inline2src(t){try{var d=urlCreator.createObjectURL(new Blob([t.replace(/^(?:<!--)?(.*?)(?:-->)?$/gm,"$1")],{type:"text/javascript"}))}catch(e){d="data:text/javascript;base64,"+btoa(t.replace(/^(?:<!--)?(.*?)(?:-->)?$/gm,"$1"))}return d}</script></body>
</html>

				
			

Explanation:

  • HTML Boilerplate: Standard HTML5 document structure.
  • Meta Tags: Ensure the page is responsive and properly encoded.
  • jQuery Script: Includes the jQuery library from a CDN.
  • Body Content: Contains a heading and a button for enabling notifications.
  • Custom Script: Links to the app.js file for custom JavaScript code.

Service Worker Registration

A service worker is a script that runs in the background, allowing you to control network requests, cache assets, and handle push notifications. Registering a service worker is the first step to making your web app a PWA.

Example:

				
					// app.js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(function(registration) {
      console.log('Service Worker registered with scope:', registration.scope);
    })
    .catch(function(error) {
      console.log('Service Worker registration failed:', error);
    });
}

				
			

Explanation:

  • Check Support: Verify that the browser supports service workers.
  • Register: Register the service worker script located at /service-worker.js.
  • Handle Success: Log a success message with the registration scope.
  • Handle Error: Log an error message if registration fails.

Output:

  • On success: Service Worker registered with scope: /
  • On failure: Service Worker registration failed: [error message]

Service Worker Basics

1. Installation and Caching

During the installation phase, a service worker can cache essential resources. This ensures that your app can load offline or under poor network conditions.

Example:

				
					// service-worker.js
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('my-cache').then(function(cache) {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/app.js',
        '/icon.png'
      ]);
    })
  );
});

				
			

Explanation:

  • Install Event: Triggered when the service worker is installed.
  • Cache API: Open a cache named ‘my-cache’ and add specified resources to it.
  • waitUntil: Ensures the install event doesn’t complete until the caching is done.

Output:

  • Caches the specified resources, making them available offline.

Fetching and Responding with Cache

Service workers can intercept network requests and serve cached responses. This enhances performance and enables offline functionality.

Example:

				
					// service-worker.js
self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

				
			

Explanation:

  • Fetch Event: Intercepts network requests.
  • respondWith: Responds with the cached resource if available, otherwise fetches it from the network.

Output:

  • Serves cached resources for matched requests, improving performance and enabling offline access.

Activation and Cleanup

The activation phase is used to clean up old caches. This is crucial for keeping your app updated and ensuring users get the latest version.

Example:

				
					// service-worker.js
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('my-cache').then(function(cache) {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/app.js',
        '/icon.png'
      ]);
    })
  );
});

				
			

Explanation:

  • Activate Event: Triggered when the service worker activates.
  • Cache Management: Delete old caches not in the whitelist.
  • waitUntil: Ensures the activate event doesn’t complete until old caches are deleted.

Output:

  • Removes outdated caches, ensuring users get the latest version of your app.

Enhancing PWAs with jQuery

AJAX Requests

AJAX allows you to update parts of a web page without reloading the whole page. Using jQuery’s AJAX methods, you can fetch data dynamically, enhancing the user experience.

Example:

				
					// app.js
$(document).ready(function() {
  $.ajax({
    url: 'https://api.example.com/data',
    method: 'GET',
    success: function(data) {
      console.log(data);
    },
    error: function(error) {
      console.log('Error:', error);
    }
  });
});

				
			

Explanation:

  • $.ajax: Performs an asynchronous HTTP request.
  • URL and Method: Specifies the request URL and method.
  • Success Handler: Logs the response data on success.
  • Error Handler: Logs an error message on failure.

Output:

  • On success: Logs the data retrieved from the API.
  • On error: Logs an error message.

Dynamic Content Loading

jQuery can load content into a page dynamically, which is useful for creating a more interactive user experience without page reloads.

Example:

				
					// app.js
$(document).ready(function() {
  $('#load-content-btn').click(function() {
    $('#content').load('/content.html');
  });
});

				
			

Explanation:

  • Document Ready: Ensures the DOM is fully loaded before executing the script.
  • Click Event: Attaches a click event handler to the button with ID load-content-btn.
  • $.load: Loads content from /content.html into the element with ID content.

Output:

  • Dynamically loads the content of content.html into the specified element when the button is clicked.

Push Notifications

Requesting Permission

To send push notifications, you first need the user’s permission. This can be requested using the Notification API.

Example:

				
					// app.js
$('#notify-btn').click(function() {
  Notification.requestPermission().then(function(result) {
    if (result === 'granted') {
      console.log('Notification permission granted.');
    }
  });
});

				
			

Explanation:

  • Click Event: Attaches a click event handler to the button with ID notify-btn.
  • Notification.requestPermission: Prompts the user for permission to send notifications.
  • Promise Handling: Logs a message if permission is granted.

Output:

  • On permission granted: Notification permission granted.

Sending Notifications

The service worker can handle push notifications, ensuring they are displayed even when the web app is not active.

Example:

				
					// service-worker.js
self.addEventListener('push', function(event) {
  var options = {
    body: 'This is a push notification.',
    icon: '/icon.png',
    badge: '/badge.png'
  };
  event.waitUntil(
    self.registration.showNotification('Notification Title', options)
  );
});

				
			

Explanation:

  • Push Event: Triggered when a push message is received.
  • Notification Options: Defines the notification content, including body, icon, and badge.
  • showNotification: Displays the notification with the specified options.

Output:

  • Displays a push notification with the defined content.

Advanced PWA Features

Background Sync

Background sync allows you to defer actions until the user has a stable internet connection, ensuring data is synced when possible.

Example:

				
					// service-worker.js
self.addEventListener('sync', function(event) {
  if (event.tag === 'sync-tag') {
    event.waitUntil(syncData());
  }
});

function syncData() {
  // Perform data synchronization here
}

				
			

Explanation:

  • Sync Event: Triggered when the service worker regains connectivity.
  • Tag Check: Ensures the sync event is for the correct tag.
  • waitUntil: Ensures the sync event doesn’t complete until syncData is finished.

Output:

  • Synchronizes data when the service worker regains connectivity.

Web App Manifest

A web app manifest provides metadata about your PWA, allowing it to be installed on a user’s home screen with an app-like experience.

Example:

				
					{
  "name": "My PWA",
  "short_name": "PWA",
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

				
			

Include the manifest in your HTML:

				
					<link rel="manifest" href="/manifest.json">

				
			

Explanation:

  • Manifest Properties: Define the app’s name, icons, start URL, display mode, background color, and theme color.
  • Link Manifest: Include a link to the manifest file in your HTML.

Output:

  • Provides metadata for your PWA, enabling it to be installed on the user’s home screen.

Building a Progressive Web App with jQuery leverages the simplicity of jQuery and the advanced capabilities of modern web technologies. By integrating service workers, push notifications, background sync, and a web app manifest, you can create a reliable, fast, and engaging user experience. This chapter has provided a comprehensive guide from basic setup to advanced features, ensuring you have all the information needed to build a PWA with jQuery. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India