Skip to content

Commit

Permalink
make CadenzaClient#on type-safe
Browse files Browse the repository at this point in the history
  • Loading branch information
jkissel committed Jan 29, 2024
1 parent b216326 commit c51ba41
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 37 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased
### Added
- Documentation for the "change:extent" event
- `CadenzaClient#setFilter`
- `CadenzaClient#selectObjects`
- `CadenzaChangeExtentEvent`
- Documentation for the "JSON Representation of Cadenza Object Data"

### Changed
- Improved the documentation of events.
- Improved the documentation and types of events.

### Fixed
- The `FilterVariables` type was missing `string[]`.
Expand Down
74 changes: 57 additions & 17 deletions src/cadenza.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class CadenzaClient {
/** @readonly */
#debug;

/** @type {[ string, (event: CadenzaEvent<never, never>) => void ][]} */
/** @type {[ CadenzaEventType | string, (event: CadenzaEvent<never>) => void ][]} */
#subscriptions = [];

/**
Expand Down Expand Up @@ -202,7 +202,6 @@ export class CadenzaClient {
* @return {Promise<void>} A `Promise` for when the iframe is loaded
* @throws For invalid arguments
* @fires {@link CadenzaDrillThroughEvent}
* @fires {@link CadenzaChangeExtentEvent}
*/
show(
source,
Expand Down Expand Up @@ -264,8 +263,9 @@ export class CadenzaClient {
* @param {AbortSignal} [options.signal] - A signal to abort the iframe loading
* @return {Promise<void>} A `Promise` for when the iframe is loaded
* @throws For invalid arguments
* @fires {@link CadenzaDrillThroughEvent}
* @fires {@link CadenzaChangeExtentEvent}
* @fires
* - {@link CadenzaChangeExtentEvent}
* - {@link CadenzaDrillThroughEvent}
*/
async showMap(
mapView,
Expand Down Expand Up @@ -469,8 +469,8 @@ export class CadenzaClient {
}

unsubscribes = [
this.on('ready', () => resolve()),
this.on('error', (/** @type {CadenzaErrorEvent} */ event) => {
this.#on('ready', () => resolve()),
this.#on('error', (/** @type {CadenzaErrorEvent} */ event) => {
const { type, message } = event.detail;
reject(new CadenzaError(type, message ?? 'Loading failed'));
}),
Expand All @@ -497,8 +497,8 @@ export class CadenzaClient {
/** @type {Promise<void>} */
const promise = new Promise((resolve, reject) => {
unsubscribes = [
this.on(`${type}:success`, () => resolve()),
this.on(`${type}:error`, () => reject()),
this.#on(`${type}:success`, () => resolve()),
this.#on(`${type}:error`, () => reject()),
];
});
promise.finally(() => unsubscribes.forEach((unsubscribe) => unsubscribe()));
Expand All @@ -509,18 +509,31 @@ export class CadenzaClient {
/**
* Subscribe to a `postMessage()` event.
*
* @template {string} TYPE
* @template [DETAIL=unknown]
* @template {CadenzaEventType} TYPE
* @param {TYPE} type - The event type
* @param {(event: CadenzaEvent<TYPE, DETAIL>) => void} subscriber - The subscriber function
* @param {(event: CadenzaEvent<TYPE, CadenzaEventByType<TYPE>['detail']>) => void} subscriber - The subscriber function
* @return {() => void} An unsubscribe function
*/
on(type, subscriber) {
return this.#on(type, subscriber);
}

/**
* @template {CadenzaEventType | string} TYPE
* @template [DETAIL=unknown]
* @param {TYPE} type
* @param {(event: CadenzaEvent<TYPE, DETAIL>) => void} subscriber
* @return {() => void} An unsubscribe function
*/
#on(type, subscriber) {
const subscriptions = this.#subscriptions;
if (subscriptions.length === 0) {
window.addEventListener('message', this.#onMessage);
}
subscriptions.push([type, subscriber]);
subscriptions.push([
type,
/** @type {(event: CadenzaEvent<never>) => void} */ (subscriber),
]);

return () => {
subscriptions.forEach(([subscriptionType, subscriptionSubscriber], i) => {
Expand Down Expand Up @@ -865,13 +878,44 @@ function createParams({
});
}

// Please do not add internal event types like 'ready' here.
/**
* @template {string} TYPE
* @typedef {'change:extent'
* | 'drillThrough'
* | 'editGeometry:ok'
* | 'editGeometry:update'
* | 'editGeometry:cancel'
* | 'selectObjects:ok'
* | 'selectObjects:update'
* | 'selectObjects:cancel'
* } CadenzaEventType - An event type to subscribe to using {@link CadenzaClient#on}
*/

/**
* @template {CadenzaEventType} T
* @typedef { T extends 'change:extent' ? CadenzaChangeExtentEvent
* : T extends 'drillThrough' ? CadenzaDrillThroughEvent
* : T extends 'editGeometry:update' ? CadenzaEditGeometryUpdateEvent
* : T extends 'editGeometry:ok' ? CadenzaEditGeometryOkEvent
* : T extends 'editGeometry:cancel' ? CadenzaEditGeometryCancelEvent
* : T extends 'selectObjects:update' ? CadenzaEditGeometryUpdateEvent
* : T extends 'selectObjects:ok' ? CadenzaEditGeometryOkEvent
* : T extends 'selectObjects:cancel' ? CadenzaEditGeometryCancelEvent
* : never
* } CadenzaEventByType
*/

/**
* @template {CadenzaEventType | string} TYPE
* @template [DETAIL=unknown]
* @typedef CadenzaEvent - A Cadenza `postMessage()` event
* @property {TYPE} type - The event type
* @property {DETAIL} detail - Optional event details (depending on the event type)
*/
/**
* @typedef {CadenzaEvent<'change:extent', {extent: Extent}>} CadenzaChangeExtentEvent - When the user moved the map.
* The extent is transformed according to the `useMapSrs` option.
*/
/**
* @typedef {CadenzaEvent<'drillThrough', {values: unknown[][]}>} CadenzaDrillThroughEvent - When the user executed a POST message drill-through.
* <p>
Expand All @@ -881,10 +925,6 @@ function createParams({
* <p>
* See also: <a href="../index.html#md:json-representation-of-cadenza-object-data">JSON Representation of Cadenza Object Data</a>
*/
/**
* @typedef {CadenzaEvent<'change:extent', {extent: Extent}>} CadenzaChangeExtentEvent - When the user moved the map.
* The extent is transformed according to the `useMapSrs` option.
*/
/** @typedef {CadenzaEvent<'editGeometry:update', {geometry: Geometry}>} CadenzaEditGeometryUpdateEvent - When the user changed the geometry. */
/** @typedef {CadenzaEvent<'editGeometry:ok', {geometry: Geometry}>} CadenzaEditGeometryOkEvent - When the user submitted the geometry. */
/** @typedef {CadenzaEvent<'editGeometry:cancel'>} CadenzaEditGeometryCancelEvent - When the user cancelled the geometry editing. */
Expand Down
18 changes: 13 additions & 5 deletions src/cadenza.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { cadenza, CadenzaClient, CadenzaError, PageSource } from './cadenza.js';
import {
cadenza,
CadenzaClient,
CadenzaDrillThroughEvent,
CadenzaError,
PageSource,
} from './cadenza.js';

const BASE_URL = 'http://example.com';
const EMBEDDING_TARGET_ID = 'embedding-target';
Expand Down Expand Up @@ -217,10 +223,12 @@ describe('Given a Cadenza JS client instance', () => {
});

describe('When subscribing to a Cadenza event', () => {
const EVENT = {
type: 'whatever',
detail: 42,
} as const;
const EVENT: CadenzaDrillThroughEvent = {
type: 'drillThrough',
detail: {
values: [],
},
};

const subscriber = jest.fn();
let unsubscribe: () => void;
Expand Down
13 changes: 0 additions & 13 deletions src/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,6 @@ cadenzaClient.on('editGeometry:cancel', (event) => {
});
```
### Subscribe to an Event With Types
If you develop your application in TypeScript, you need to define the type of the `event.detail` in order to access its properties:
```typescript
cadenzaClient.on(
'editGeometry:ok',
(event: CadenzaEvent<{ geometry: Geometry }>) => {
console.log('Geometry editing was completed', event.detail.geometry);
},
);
```
### Unsubscribe From an Event
The `on()` method returns an unsubscribe function.
Expand Down

0 comments on commit c51ba41

Please sign in to comment.