Let's Build a PWA

This minimal project can be used as a starting point to build a progressive web application (PWA). This particular project

  • does not communicate with any server except to retrieve updated static resources, consequently it can be hosted on a static web server
  • uses vanilla JavaScript instead of third-party frameworks
  • demonstrates the use of dialogs to read and write data on the user's own computer; with this application, your information never leaves your computer
  • can be installed as a PWA for off-line use
  • uses a classless stylesheet to keep the HTML as clean as possible
  • includes a bunch of JavaScript helper routines, such as a recursive node creator for simple and safe templating
  • conforms with ECMAScript 3 (1999) so it can be run on older devices, although in that case not as a PWA

To keep this project small and avoid tooling that can be confusing for someone starting out with web apps, it forgoes a lot of nice-to-have features such as

  • automatic generation of navigation breadcrumbs
  • page routing, for example "/page/42/edit", although pages can be displayed with static arguments to provide context
  • caching of DOM elements for fast lookup by name
  • a makefile and other project build tools, for example, asset minifier, source code linter, spellchecker, cache version updater, and server synchronization script

Ledger

There is currently no data associated with this application. You have two options. If you have previously saved a data file, restore it by specifying the file in the Restore section. Otherwise, if you want to load a small sample data set, click the button in the Load Sample Data section.

Restore

Restoring a file is a two-step process. First, select the file and then click Restore Project.

Load sample data

Click the following button to initialize the program with a small sample project. You will be able to modify the data and save it for later reuse.

Form

Negative value indicates withdrawal.

Ledger

Edit
Transaction
Amount
Bank
24 May 2023
Bank · Wages
Actual 103.50
45.00
100.00

Help

This project demonstrates basic components of a progressive web app (PWA). It uses a rather opinionated approach in which each application page is put into its own div block in an amalgamated HTML page. The div blocks are siblings. When one page is shown programmatically its siblings are automatically hidden. This allows the application to function quickly since there are no network calls to retrieve content.

All programmatic logic is handled on the browser. The static web server simply delivers HTML, JavaScript, graphic resources, and the manifest file.

User data is saved and restored locally by means of system dialog boxes.

The JavaScript code is separated into two files. The utility.js file contains helper routines. This file can be used without change with other applications. It uses a singleton object so that it can be instantiated multiple times with no overhead. The index.js file contains application logic.

In general, these two files should be concatenated and minified when the application is deployed.

Development

This project can function as a normal web application without tapping into the benefits of an installed PWA. The presence of the manifest.json file, the service-worker.js file, and the call to navigator.serviceWorker.register() function are what make it installable as a PWA.

The manifest.json file and the icon that it references are typically prepared at the beginning of a project and are only infrequently modified after that. On the topic of icons, a good article is Adaptive icon support in PWAs for details about maskable icons with PWAs.

The service-worker.js file is basically boilerplate except for a couple of lines. This file implements the strategy the application will use to fetch resources from the host server, which incidentally can be a static web server. Think of a service worker as a little local web server that your app will contact to retrieve resources. If a resource is available in the local cache, the service worker will deliver that. If a resource is not available, it will contact the host server to get it. In this project, an "offline first" strategy is used. Only after the app is running will it check for updated resources on the server.

The parts of the service-worker.js file that do need to be modified regularly are the version and cacheAssets constants. In this project, the version is simply the UTC timestamp at the time of deployment. This gets updated with a small script that sets the value when the resources are synchronized with the host server. The cache assets are the files that need to be cached to function offline. This line needs to be updated only when resources are added or removed from the project, in general not too often.

In a web app like this example it is necessary to generate HTML programmatically. Most apps use some kind of templating library or HTML node generator. This app uses a different technique: certain constructions in the HTML contain the data-template attribute. At program startup, these blocks are deep cloned and saved for later reuse. When these blocks need to be rendered into HTML, a utility function named elementNew() is called that either applies text data to selected nodes in the template, skips certain nodes, or recurses into template sub-blocks. This technique simplifies page generation because the templates themselves are valid HTML and can be checked for semantic and visual correctness before their original text nodes are replaced programmatically.

HTML examples

This page demonstrates various HTML elements and how they are rendered with the classless stylesheet.

A lot of apps use stylesheet classes extensively. This project does the opposite. It is based on SPCSS that serves to make the HTML as simple as possible. When the application does need to use a custom style, try to make use of the HTML's structure in order to minimize the number of elements that need to assign the class specifically. For example, rather than apply a class (for example, class="record") to a number of repeating elements, it might be a lot simpler to instead apply a class (for example, class="record-container" to the parent of the repeating elements. In this example, the style might look like .record-container > div { ... }).

Code block

Here is a code block with a wide ruler that can be helpful to see how much horizontal and vertical space a certain amount of code consumes. It also demonstrates horizontal scrolling which in most circumstances should be avoided.

  1---5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100--105--110--115--120--125--130--135--140--145--150--155--160--165--170--175--180--185--190--195--200--205--210--215--220--225--230--235--240--245--250--255--260--265--270--275--280--285--290--295--300
  2
  3
  4
  5
  6
  7
  8

Figure

This section demonstrates an image nested within a figure element that includes a caption.

Let's Build a PWA
Let's Build a PWA

Form

This block demonstrates a simple form. On narrow displays, the email and day of week input fields are shown on separate lines while on wider displays the input fields appear on a single row.

Table

This section shows an example of an HTML table. In general, tables provide a horrible experience on mobile devices unless few narrow columns are used. It is almost always preferable to structure information so that record fields can be displayed in a single column.

Editor Creator License First Release
GNU Emacs Richard Stallman GNU GPLv3+ 20 Mar 1985
Vim Bram Moolenaar Vim License 02 Nov 1991
GNU nano Chris Allegretta GNU GPLv3 18 Nov 1999
Notepad++ Don Ho GNU GPLv2 24 Nov 2003
Atom GitHub MIT 26 Feb 2014
Visual Studio Code Microsoft MIT 29 Apr 2015
About

This example program is dedicated to Hans Valley, friend and, for too short a time, former colleague. This code may not achieve the appellation of "elegant" that we always strove for, but it does stand on its own without bulky dependencies.

MIT License

Copyright Maura and Kurt Jung

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.