Widgets for your website

Live examples of the same JavaScript widgets used by the anonymous portal overview. The overview cards require no iframe, account or API key. The purchase map requires a public Google Maps browser key.

Available in this source branch; unreleased. Deploy API and Web from the same release before using this on a public website.

Instructions and limitations

Copy an example into your own page. Use the API hostname for your tenant and the matching release environment. The module creates its own Shadow DOM and loads its stylesheet, so your website CSS does not change the cards. Labels, names and default colors come from that API. The host element controls available width: cards use a 300px minimum and wrap automatically. Use machineCount: 1, 2 or 3 for distinct random running machines. Use mountProgressWidgets for a specific unit, location or bank KID.

The module sends credential-free GET requests only. These public widget reads allow external origins; manager/session operations retain their exact-origin CORS restrictions. If your website uses Content Security Policy, allow the API origin in script-src, connect-src, img-src and style-src; dynamic progress also requires inline styles, the purchase chart requires data: in img-src, and WashDoc previews require blob: in img-src. Use HTTPS and a browser with ES modules, Shadow DOM, fetch and AbortSignal.timeout. Do not embed credentials. The data is live and may be unavailable; 503 means unavailable, never zero. Each card retries automatically and marks retained values as unavailable after a failed refresh. The ready promise reports initialization errors; set unavailableText for a localized initial failure message. Call destroy() when removing or replacing a widget group. Removing a host stops its polling automatically.

Running count refreshes every 15 seconds; purchases, reservations and machine pool every minute; active users every minute (the API caches their count for five minutes). Requests share one snapshot within the page. Countdown runs locally every second using server time, retains each selected machine and shows completion for 20 seconds. Pool bounds and public fields are unchanged; images are API-generated SVGs. Amounts remain separated by currency.

Complete overview

Active users

Purchases

Running machines

Active reservations

Machine progress

Paste the canonical KID from this tenant. No login is required. Location and bank include every public unit, including idle units. Use Load more for subsequent pages; loaded pages refresh every minute. Unknown timing is labelled, never replaced by a made-up countdown.

Progress for a unit

Progress for a location

Progress for a bank

WashDoc

A square document preview with machine/location names, date/time and a simplified graph. The default follows the most recently started currently running public machine. Choose Unit KID to follow its stored WashDocId, including the last completed document, or Doc KID to keep one document. KIDs must belong to this API tenant. Visible widgets refresh once per minute and pause while hidden. No login is needed; full tables and Excel remain protected. Allow blob: in img-src when using Content Security Policy. Available in source, unreleased. For WashDoc, ready resolves after the first visible read attempt, including when the card displays an unavailable message; it does not guarantee document data.

Purchase map widget

Mount the compact live purchase map directly, inside its own Shadow DOM. No iframe, portal shell, login or bearer token is required. The API site selects the tenant. Data refreshes every 10 seconds; failures back off for 30 seconds. Call destroy() when removing the widget. Supply googleMapsBrowserKey from the embedding website's public browser-key configuration; never use a server geocoding key. The option takes precedence over the nullable googleMapsBrowserKey from GetPublicWidgetPresentation (available only when the API already has GoogleMaps:ApiKey configured). The API does not need that setting when the embedding site supplies its own key. Without either source, ready rejects. Google HTTP-referrer restrictions must allow the embedding site; the widget does not change them. For sites with Content Security Policy, also follow Google Maps CSP guidance.

<div id="purchase-map" style="width:100%;height:400px"></div>
<script type="module">
import { mountPurchaseMap } from 'https://YOUR-API/docs/widgets.mjs';
const widget = mountPurchaseMap(document.querySelector('#purchase-map'), {
  apiBase: 'https://YOUR-API', language: 'en-GB',
  googleMapsBrowserKey: 'YOUR_PUBLIC_BROWSER_KEY'
});
await widget.ready;
// Call widget.destroy() before removing the host.
</script>

Map layers: showCoins defaults to true; showHeatmap defaults to false. For a heatmap-only card, set showCoins: false, showHeatmap: true. Available in source; unreleased.

Set zoom: 7 to use a fixed zoom level after centering on the data. Values from 0 to 22 are supported. Omit zoom to fit all locations automatically. Unreleased.

Set startPosition: { latitude: 56.2, longitude: 10.5 } to choose the initial map centre. Latitude must be from -90 to 90 and longitude from -180 to 180. Combine with zoom: 6. With startPosition, automatic data fitting is skipped; live purchase following and manual navigation remain available. Omit it to keep automatic fitting. Available in source; unreleased.

After widget.ready, widget.getView() returns the current { startPosition: { latitude, longitude }, zoom }, or null when unavailable or destroyed. It reflects manual map movements. Unreleased.

Latest photos

mountRecentPhotos(host, options) displays one public thumbnail at a time, rotating every three seconds and refreshing every minute. Set apiBase to the tenant API and provide alt, loadingText, emptyText and unavailableText. It returns ready and destroy(). Only public thumbnails are loaded; original media remains protected. New export: unreleased.

const photos = mountRecentPhotos(element, { apiBase, alt: "Photo", emptyText: "No photos", unavailableText: "Unavailable" });
await photos.ready;
// On removal:
photos.destroy();