Progressive Web Application (PWA) template

PWA template
HTML5 Application

Progressive Web Application template

This article walks through the creation process of a generic Progressive Web Application (PWA) template. The goal was to build a reusable foundation packed with common features needed for modern web apps, including offline capabilities, internationalization, common UI components, and robust cloud synchronization using the Dropbox API.

The full code for this template is available on GitHub here.

HTML structure

The application uses two main HTML files: index.html for the main application interface and help/index.html for a separate help page. Both leverage Bootstrap 5 for layout and core components.

index.html sets up the primary view:

  • Offcanvas Sidebar: A collapsible sidebar (#offcanvasSidebar) used for file management (listing files, add/rename/delete buttons).
  • Main Content Area: Contains the header with controls and the primary content display area.
    • Header: Includes a button to toggle the sidebar, the current file name (#currentFileNameHeader), a language switcher dropdown, the Dropbox sync status indicator (#syncStatusIndicator), the Dropbox connection button (#dropboxAuthButton), and a link to the help page.
    • Content Placeholder: An area (#main-content-area) where application-specific UI elements can be added, pre-filled with examples of the template’s UI components (datepickers, switches, notification buttons).
  • Modals: Bootstrap modals are included for user interactions like adding files (#addFileModal), renaming files (#renameFileModal), confirming deletions (#deleteFileModalConfirm), and resolving sync conflicts (#conflictModal).

<!-- Snippet from index.html Header -->
<div class="pt-3 pb-2 mb-3 border-bottom d-flex justify-content-between align-items-center flex-wrap">
  <div class="d-flex align-items-center me-3 mb-2 mb-md-0">
    <button class="btn btn-light me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasSidebar">...</button>
    <h1 class="h2 mb-0" id="currentFileNameHeader" data-i18n="main.header.currentFile">Current File</h1>
  </div>
  <div class="d-flex align-items-center">
    <!-- Language Dropdown -->
    <div class="dropdown me-2">...</div>
    <!-- Sync Status -->
    <span id="syncStatusIndicator" class="me-2 text-muted small"></span>
    <!-- Dropbox Button -->
    <button type="button" id="dropboxAuthButton" class="btn btn-light p-1 btn-fa me-2">...</button>
    <!-- Help Link -->
    <a href="help/" class="btn btn-light btn-fa p-1">...</a>
  </div>
</div>

The structure is designed to be clean and adaptable for various PWA applications built upon this template.

CSS styling

Styling relies heavily on Bootstrap 5 for the base framework. Customizations and component-specific styles are added via:

  • TEMPLATE.css: Contains general overrides, theme colors (primary #0083B3 and complementary #FF4030), button styling, layout adjustments (like responsive button rounding), and styles for the file list sidebar and Dropbox button. It also includes a simple mechanism to prevent Flash of Unstyled Content (FOUC) during language initialization.
  • Component CSS (assets/css/ui/, assets/css/notif.min.css): Minified CSS files provide specific styling for the datepicker, switch component, and notification toasts, ensuring they integrate visually with the theme.

/* Example Theme Variables in TEMPLATE.css */
.btn-primary {
  --bs-btn-color:#FFF;
  --bs-btn-bg:#0083B3;
  --bs-btn-border-color:#0083B3;
  /* ... hover, active, disabled states ... */
}

.btn-complementary {
  --bs-btn-color: #FFF;
  --bs-btn-bg: #FF4030;
  --bs-btn-border-color: #FF4030;
  /* ... hover, active, disabled states ... */
}

/* FOUC Prevention */
body {
  opacity: 0; /* Initially hidden */
  transition: opacity 0.4s ease-in-out;
}
body.localized {
  opacity: 1; /* Visible once i18next is ready */
}

The styling aims for a clean, modern look that can be easily customized further.

JavaScript functionality

The application logic is modularized, promoting separation of concerns and maintainability. Key modules include:

Core application logic

This script acts as the main entry point for UI interactions after the document is ready.

  • Initialization: Calls initializeDropboxSync() to start the Dropbox connection process and sets up listeners for the file management buttons (Add, Rename, Delete).
  • Modal Handling: Manages the opening of Bootstrap modals for file operations, ensuring the modal instances are created and necessary data (like the current file name for renaming) is populated before showing them. It dynamically initializes modal-specific event listeners (setupAddFileModalListeners, etc.) the first time a modal is opened.

Storage system

Handles data persistence and file management logic.

  • storage.js: Provides low-level access to localStorage. It manages:
    • A list of “known files” (knownFiles key) storing {name, path} objects.
    • The currently active file path (activeFile key).
    • Content and metadata (last local modification time, last sync time) for each file, using dynamic keys based on the file path (e.g., content_/path/to/file.txt).
    • Ensures the DEFAULT_FILE_PATH (/default.txt) always exists in the known files list.
    • Dispatches a localDataChanged event when content is saved locally, used by the sync coordinator.
  • files.js: Implements higher-level file operations triggered by UI interactions (sidebar clicks, modal submissions).
    • Updates the file list UI in the sidebar (updateFileSelectionUI).
    • Handles adding new files: validates input, creates an empty file on Dropbox via the API module, adds the file to local storage, sets it as active, and updates the UI.
    • Handles renaming files: validates input, checks for conflicts, performs the rename locally (including moving associated content/metadata in localStorage), updates the UI, and then attempts to rename the file on Dropbox via the API module.
    • Handles deleting files: confirms deletion, attempts to delete the file on Dropbox via the API module, removes the file and its data from local storage, switches to the default file if the active one was deleted, and updates the UI.

Dropbox integration

This is a core feature, enabling synchronization of files stored in the app’s dedicated Dropbox folder.

  • dropbox-sync.js: The main entry point for the sync system. It initializes authentication and offline handling. It doesn’t contain the core sync logic itself but orchestrates the initialization.
  • dropbox/config.js: Stores essential configuration like the Dropbox App Key (CLIENT_ID), calculates the REDIRECT_URI dynamically, and defines localStorage keys used by the sync system. Note: The CLIENT_ID needs to be replaced with an actual App Key from Dropbox.
  • dropbox/auth.js: Manages the OAuth 2.0 authentication flow using the Dropbox.DropboxAuth SDK component. It handles obtaining, storing (localStorage), and clearing the access token, processing the redirect from Dropbox, and initializing the API client (initializeDropboxApi) upon success. It also includes logic (discoverDropboxFiles) to list files in the app’s Dropbox folder upon login and add any newly found .txt files to the local known files list.
  • dropbox/api.js: Contains functions that interact directly with the Dropbox API using the Dropbox.Dropbox SDK client (dbx). It provides wrappers for:
    • Initializing the dbx instance.
    • Getting file metadata (filesGetMetadata).
    • Downloading file content (filesDownload).
    • Uploading file content (filesUpload), ensuring overwrite mode.
    • Renaming/moving files (filesMoveV2).
    • Deleting files (filesDeleteV2).
    • Includes error handling, specifically checking for invalid access tokens and triggering a logout if detected.
  • dropbox/ui.js: Manages UI elements related to Dropbox:
    • Updates the sync status indicator (#syncStatusIndicator) with different icons/text based on the state (Idle, Syncing, Pending, Offline, Error, Not Connected).
    • Handles the display and interaction logic for the sync conflict modal (#conflictModal), using a Promise to return the user’s choice (‘local’ or ‘dropbox’).
    • Updates the appearance and behavior of the Dropbox authentication button (#dropboxAuthButton).
  • dropbox/offline.js: Handles network connectivity changes. It listens for browser online and offline events. If the app comes online and an upload was flagged as pending (because a local change happened while offline), it triggers a sync attempt. It uses localStorage to track pending uploads per file.
  • sync-coordinator.js: Orchestrates the actual synchronization logic.
    • Listens for the localDataChanged event dispatched by storage.js.
    • When local data changes for the active file, it debounces the sync check (waits a few seconds after the last change) before calling coordinateSync.
    • coordinateSync: This is the core comparison logic. It fetches local and Dropbox timestamps for the active file.
      • If one side is missing, it uploads or downloads accordingly.
      • If both exist, it compares timestamps (allowing a small buffer).
      • If Dropbox is significantly newer and local changes are pending (due to offline edits or rapid changes), it shows the conflict modal via dropbox/ui.js.
      • If Dropbox is newer and no local changes are pending, it automatically downloads the Dropbox version.
      • If local is newer, it uploads the local version.
    • It interacts with dropbox/api.js for downloads/uploads and dropbox/ui.js for status updates and conflict resolution.

UI components

These scripts initialize and manage the reusable UI components.

  • datepicker.js: Initializes the Bootstrap Datepicker component on elements with the .date-picker class, allowing theme customization via data-datepicker-color.
  • switch.js: Initializes Bootstrap switches, setting their initial text label (e.g., “ON”/“OFF”) based on state and updating the label on change. It reads custom labels from data-checked/data-unchecked attributes.
  • notif.js: Provides functions (showPrimaryNotification, showComplementaryNotification) to trigger example notifications using the showNotification function. It also contains the actual logic to initialize switches and set their labels using i18next for translation, falling back to data attributes or defaults. (Note: There seems to be duplicate switch initialization logic between switch.js and notif.js. notif.js’s version is more complete as it includes i18n).
  • notif-flash.min.js: A utility to display notifications based on URL query parameters (e.g., after a redirect). It defines the global showNotification function used by other modules to create styled Bootstrap Toasts for user feedback.

Internationalization

Implements multi-language support using i18next and its plugins.

  • Initialization: Configures i18next with the browser language detector and HTTP backend to load translation files (assets/locales/{lang}/translation.json).
  • Content Update: Selects elements with data-i18n attributes and updates their text content or specified attributes (e.g., [title]key) based on the loaded translations for the current language.
  • Language Switching: Handles clicks on language selection links, changes the i18next language, and updates the UI accordingly. Manages the display of the current language in the header dropdown.

Caching and PWA

Provides offline functionality and basic PWA features.

  • service-worker.js: (Code not shown in blob, but assumed standard) Implements caching strategies (e.g., cache-first for assets) to allow the app to load and function offline. Handles installation, activation (cache cleanup), and fetch events. Likely includes a mechanism to receive messages (like ‘refresh_cache’).
  • cache.js: Fetches a version file (/data/json/version.json) from the server, compares it to a version stored in localStorage, and if different, sends a refresh_cache message to the service worker to trigger an update of the cached assets.

Logging

A simple utility module providing logVerbose and warnVerbose functions that only output to the console if a VERBOSE_LOGGING_ENABLED flag is set to true, useful for debugging during development.

Conclusion

This PWA template provides a solid starting point for web applications requiring offline support, file management, and cloud synchronization via Dropbox. By modularizing the JavaScript and leveraging libraries like Bootstrap and i18next, it offers a flexible and feature-rich foundation. The Dropbox integration, with its attention to conflict resolution and offline handling, adds significant value for applications needing data persistence across devices.

Go to the top of the page