Skip to content

Commit

Permalink
Introduction of 4 distinct callbacks for cgpv. To better distinguish …
Browse files Browse the repository at this point in the history
…when it's possible to start listening to events (#2546)

Minor ESLint fixes elsewhere
  • Loading branch information
Alex-NRCan authored Oct 15, 2024
1 parent d07aff8 commit 98c719d
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 102 deletions.
183 changes: 123 additions & 60 deletions packages/geoview-core/public/templates/demos/demo-function-event.html

Large diffs are not rendered by default.

110 changes: 88 additions & 22 deletions packages/geoview-core/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export const api = new API();

const reactRoot: Record<string, Root> = {};

let cgpvCallbackMapInit: (mapId: string) => void | undefined;
let cgpvCallbackMapReady: (mapId: string) => void | undefined;
let cgpvCallbackLayersProcessed: (mapId: string) => void | undefined;
let cgpvCallbackLayersLoaded: (mapId: string) => void | undefined;

/**
* Function to unmount a map element
*
Expand Down Expand Up @@ -204,11 +209,10 @@ export async function initMapDivFromFunctionCall(mapDiv: HTMLElement, mapConfig:
}

/**
* Initialize the cgpv and render it to root element
* Initializes the cgpv and render it to root element
*
* @param {(mapId: string) => void} callbackMapInit optional callback function to run once the map rendering is ready
* @param {(mapId: string) => void} callbackMapLayersLoaded optional callback function to run once layers are loaded on the map
* @returns {Promise<void>}
*/
function init(callbackMapInit?: (mapId: string) => void, callbackMapLayersLoaded?: (mapId: string) => void): void {
const mapElements = document.getElementsByClassName('geoview-map');
Expand All @@ -220,31 +224,53 @@ function init(callbackMapInit?: (mapId: string) => void, callbackMapLayersLoaded
// Render the map
const promiseMapInit = renderMap(mapElement);

// The callback for the map init when the promiseMapInit will resolve
const theCallbackMapInit = cgpvCallbackMapInit;

// The callback for the map ready when the promiseMapInit will resolve
const theCallbackMapReady = cgpvCallbackMapReady;

// The callback for the layers processed when the promiseMapInit will resolve
const theCallbackLayersProcessed = cgpvCallbackLayersProcessed;

// The callback for the layers loaded when the promiseMapInit will resolve
const theCallbackLayersLoaded = cgpvCallbackLayersLoaded;

// When the map init is done
promiseMapInit
.then(() => {
// Log
logger.logInfo('Map initialized', mapElement.getAttribute('id')!);

// TODO: Fix this timeout issue when geoCore layer are use in config: https://github.com/Canadian-Geospatial-Platform/geoview/issues/2380
// Set a timeout value if one of thelaeyrs is a geoCore type
const mapId = mapElement.getAttribute('id')!;
const layerTypes = api.maps[mapId].mapFeaturesConfig.map.listOfGeoviewLayerConfig?.map((item) => item.geoviewLayerType) || [];
const geoCoreTimeout = layerTypes.includes('geoCore') ? 500 : 0;

// TODO: Fix this timeout issue when geoCore layer are use in config: https://github.com/Canadian-Geospatial-Platform/geoview/issues/2380
setTimeout(() => {
// Callback about it
callbackMapInit?.(mapId);

// Register when the map viewer will have loaded layers
api.maps[mapId].onMapLayersLoaded((mapViewerLoaded) => {
logger.logInfo('Map layers loaded', mapViewerLoaded.mapId);

// Callback for that particular map
callbackMapLayersLoaded?.(mapViewerLoaded.mapId);
});
}, geoCoreTimeout);
logger.logInfo('Map initialized', mapId);

// Callback about it
theCallbackMapInit?.(mapId);
callbackMapInit?.(mapId); // TODO: Obsolete call, remove it eventually

// Register when the map viewer will have a map ready
api.maps[mapId].onMapReady((mapViewer) => {
logger.logInfo('Map ready / layers registered', mapViewer.mapId);

// Callback for that particular map
theCallbackMapReady?.(mapViewer.mapId);
});

// Register when the map viewer will have loaded layers
api.maps[mapId].onMapLayersProcessed((mapViewer) => {
logger.logInfo('Map layers processed', mapViewer.mapId);

// Callback for that particular map
theCallbackLayersProcessed?.(mapViewer.mapId);
});

// Register when the map viewer will have loaded layers
api.maps[mapId].onMapLayersLoaded((mapViewer) => {
logger.logInfo('Map layers loaded', mapViewer.mapId);

// Callback for that particular map
theCallbackLayersLoaded?.(mapViewer.mapId);
callbackMapLayersLoaded?.(mapViewer.mapId); // TODO: Obsolete call, remove it eventually
});
})
.catch((error) => {
// Log
Expand All @@ -254,9 +280,49 @@ function init(callbackMapInit?: (mapId: string) => void, callbackMapLayersLoaded
}
}

/**
* Registers a callback when the map has been initialized
* @param {(mapId: string) => void} callback - The callback to be called
*/
export function onMapInit(callback: (mapId: string) => void): void {
// Keep the callback
cgpvCallbackMapInit = callback;
}

/**
* Registers a callback when the map has turned ready / layers were registered
* @param {(mapId: string) => void} callback - The callback to be called
*/
export function onMapReady(callback: (mapId: string) => void): void {
// Keep the callback
cgpvCallbackMapReady = callback;
}

/**
* Registers a callback when the layers have been processed
* @param {(mapId: string) => void} callback - The callback to be called
*/
export function onLayersProcessed(callback: (mapId: string) => void): void {
// Keep the callback
cgpvCallbackLayersProcessed = callback;
}

/**
* Registers a callback when the layers have been loaded
* @param {(mapId: string) => void} callback - The callback to be called
*/
export function onLayersLoaded(callback: (mapId: string) => void): void {
// Keep the callback
cgpvCallbackLayersLoaded = callback;
}

// cgpv object to be exported with the api for outside use
export const cgpv: TypeCGPV = {
init,
onMapInit,
onMapReady,
onLayersProcessed,
onLayersLoaded,
api,
react: React,
createRoot,
Expand Down
5 changes: 5 additions & 0 deletions packages/geoview-core/src/core/types/global-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export interface TypeWindow extends Window {
*/
export type TypeCGPV = {
init: CGPVInitCallback;
onMapInit: CGPVCallback;
onMapReady: CGPVCallback;
onLayersProcessed: CGPVCallback;
onLayersLoaded: CGPVCallback;
api: API;
react: typeof React;
createRoot: typeof createRoot;
Expand All @@ -64,6 +68,7 @@ export type TypeCGPV = {
* Type used for a callback function.
*/
export type CGPVInitCallback = (callbackMapsInit?: (mapId: string) => void, callbackMapsLayersLoaded?: (mapId: string) => void) => void;
export type CGPVCallback = (callback: (mapId: string) => void) => void;

/** ******************************************************************************************************************************
* Type used for exporting UI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,15 @@ export class GVEsriDynamic extends AbstractGVRaster {
if (i === 0) {
if (styleSettings.classBreakStyleInfo[0].visible !== false && styleSettings.defaultVisible === false)
filterArray.push(
`${styleSettings.field} >= ${this.#formatFieldValue(
`${styleSettings.field} >= ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[0].minValue!,
layerConfig.source.featureInfo!
)}`
);
else if (styleSettings.classBreakStyleInfo[0].visible === false && styleSettings.defaultVisible !== false) {
filterArray.push(
`${styleSettings.field} < ${this.#formatFieldValue(
`${styleSettings.field} < ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[0].minValue!,
layerConfig.source.featureInfo!
Expand All @@ -426,23 +426,23 @@ export class GVEsriDynamic extends AbstractGVRaster {
}
} else if (styleSettings.classBreakStyleInfo[i].visible !== false && styleSettings.defaultVisible === false) {
filterArray.push(
`${styleSettings.field} > ${this.#formatFieldValue(
`${styleSettings.field} > ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[i].minValue!,
layerConfig.source.featureInfo!
)}`
);
if (i + 1 === styleSettings.classBreakStyleInfo.length)
filterArray.push(
`${styleSettings.field} <= ${this.#formatFieldValue(
`${styleSettings.field} <= ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[i].maxValue!,
layerConfig.source.featureInfo!
)}`
);
} else if (styleSettings.classBreakStyleInfo[i].visible === false && styleSettings.defaultVisible !== false) {
filterArray.push(
`${styleSettings.field} <= ${this.#formatFieldValue(
`${styleSettings.field} <= ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[i].minValue!,
layerConfig.source.featureInfo!
Expand All @@ -453,15 +453,15 @@ export class GVEsriDynamic extends AbstractGVRaster {
} else if (styleSettings.defaultVisible === false) {
if (styleSettings.classBreakStyleInfo[i].visible === false) {
filterArray.push(
`${styleSettings.field} <= ${this.#formatFieldValue(
`${styleSettings.field} <= ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[i - 1].maxValue!,
layerConfig.source.featureInfo!
)}`
);
} else if (i + 1 === styleSettings.classBreakStyleInfo.length) {
filterArray.push(
`${styleSettings.field} <= ${this.#formatFieldValue(
`${styleSettings.field} <= ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[i].maxValue!,
layerConfig.source.featureInfo!
Expand All @@ -470,7 +470,7 @@ export class GVEsriDynamic extends AbstractGVRaster {
}
} else if (styleSettings.classBreakStyleInfo[i].visible !== false) {
filterArray.push(
`${styleSettings.field} > ${this.#formatFieldValue(
`${styleSettings.field} > ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[i - 1].maxValue!,
layerConfig.source.featureInfo!
Expand All @@ -483,7 +483,7 @@ export class GVEsriDynamic extends AbstractGVRaster {
}
if (visibleWhenGreatherThisIndex !== -1)
filterArray.push(
`${styleSettings.field} > ${this.#formatFieldValue(
`${styleSettings.field} > ${GVEsriDynamic.#formatFieldValue(
styleSettings.field,
styleSettings.classBreakStyleInfo[visibleWhenGreatherThisIndex].maxValue!,
layerConfig.source.featureInfo!
Expand Down Expand Up @@ -613,7 +613,7 @@ export class GVEsriDynamic extends AbstractGVRaster {
): string {
let queryString = styleSettings.defaultVisible !== false && !level ? 'not (' : '(';
for (let i = 0; i < queryTree.length; i++) {
const value = this.#formatFieldValue(styleSettings.fields[fieldOrder[level]], queryTree[i].fieldValue, sourceFeatureInfo);
const value = GVEsriDynamic.#formatFieldValue(styleSettings.fields[fieldOrder[level]], queryTree[i].fieldValue, sourceFeatureInfo);
// The nextField array is not empty, then it is is not the last field
if (queryTree[i].nextField.length) {
// If i > 0 (true) then we add a OR clause
Expand Down Expand Up @@ -644,7 +644,7 @@ export class GVEsriDynamic extends AbstractGVRaster {
* @returns {string} The resulting field value.
* @private
*/
#formatFieldValue(fieldName: string, rawValue: string | number | Date, sourceFeatureInfo: TypeFeatureInfoLayerConfig): string {
static #formatFieldValue(fieldName: string, rawValue: string | number | Date, sourceFeatureInfo: TypeFeatureInfoLayerConfig): string {
const fieldEntry = sourceFeatureInfo.outfields?.find((outfield) => outfield.name === fieldName);
const fieldType = fieldEntry?.type;
switch (fieldType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class GVWMS extends AbstractGVRaster {
}
} else featureMember = { plain_text: { '#text': response.data } };
if (featureMember) {
const featureInfoResult = this.#formatWmsFeatureInfoResult(featureMember, clickCoordinate);
const featureInfoResult = GVWMS.#formatWmsFeatureInfoResult(featureMember, clickCoordinate);
return featureInfoResult;
}
}
Expand Down Expand Up @@ -401,7 +401,7 @@ export class GVWMS extends AbstractGVRaster {
* @returns {TypeFeatureInfoEntry[]} The feature info table.
* @private
*/
#formatWmsFeatureInfoResult(featureMember: TypeJsonObject, clickCoordinate: Coordinate): TypeFeatureInfoEntry[] {
static #formatWmsFeatureInfoResult(featureMember: TypeJsonObject, clickCoordinate: Coordinate): TypeFeatureInfoEntry[] {
const queryResult: TypeFeatureInfoEntry[] = [];

let featureKeyCounter = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export abstract class AbstractLayerSet {
// Keep all callback delegates references
#onLayerSetUpdatedHandlers: LayerSetUpdatedDelegate[] = [];

// Keep all callback delegates references
#onLayerStatusUpdatedHandlers: LayerStatusUpdatedDelegate[] = [];

// Keep a bounded reference to the handle layer status changed
#boundHandleLayerStatusChanged: (config: ConfigBaseClass, layerStatusEvent: LayerStatusChangedEvent) => void;

Expand Down Expand Up @@ -140,13 +143,16 @@ export abstract class AbstractLayerSet {

// If the layer could be found
if (layer) {
// Register the layer automatically in the layer set
// Register the layer itself (not the layer config) (recall the hybrid mode) automatically in the layer set
this.registerLayer(layer, layerConfig.layerPath).catch((error) => {
// Log
logger.logPromiseFailed('in registerLayer in registerLayerConfig', error);
});
}
}

// Emit that the layerConfig got their status changed
this.#emitLayerStatusUpdated({ layer: layerConfig });
} catch (error) {
// Error happened when trying to register the layer coming from the layer config
logger.logError('Error trying to register the layer coming from the layer config', error);
Expand Down Expand Up @@ -517,6 +523,34 @@ export abstract class AbstractLayerSet {
// Unregister the layersetupdated event callback
EventHelper.offEvent(this.#onLayerSetUpdatedHandlers, callback);
}

/**
* Emits an event to all registered handlers.
* @param {LayerStatusUpdatedEvent} event - The event to emit
* @private
*/
#emitLayerStatusUpdated(event: LayerStatusUpdatedEvent): void {
// Emit the layersetupdated event
EventHelper.emitEvent(this, this.#onLayerStatusUpdatedHandlers, event);
}

/**
* Registers a callback to be executed whenever the layer status is updated.
* @param {LayerStatusUpdatedDelegate} callback - The callback function
*/
onLayerStatusUpdated(callback: LayerStatusUpdatedDelegate): void {
// Register the layersetupdated event callback
EventHelper.onEvent(this.#onLayerStatusUpdatedHandlers, callback);
}

/**
* Unregisters a callback from being called whenever the layer status is updated.
* @param {LayerStatusUpdatedDelegate} callback - The callback function to unregister
*/
offLayerStatusUpdated(callback: LayerStatusUpdatedDelegate): void {
// Unregister the layersetupdated event callback
EventHelper.offEvent(this.#onLayerStatusUpdatedHandlers, callback);
}
}

// TODO: Rename this type to something like 'store-container-type' as it is now mostly used to indicate in which store to propagate the result set
Expand All @@ -542,3 +576,15 @@ export type LayerSetUpdatedEvent = {
layerPath: string;
resultSet: TypeResultSet;
};

/**
* Define a delegate for the event handler function signature
*/
type LayerStatusUpdatedDelegate = EventDelegateBase<AbstractLayerSet, LayerStatusUpdatedEvent, void>;

/**
* Define an event for the delegate
*/
export type LayerStatusUpdatedEvent = {
layer: ConfigBaseClass;
};
10 changes: 4 additions & 6 deletions packages/geoview-core/src/geo/map/map-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,10 @@ export class MapViewer {
this.#mapInit = true;
this.#emitMapInit();

MapEventProcessor.resetBasemap(this.mapId)
.then()
.catch((error) => {
// Log
logger.logPromiseFailed(' MapEventProcessor.resetBasemap in map-viewer', error);
});
MapEventProcessor.resetBasemap(this.mapId).catch((error) => {
// Log
logger.logPromiseFailed(' MapEventProcessor.resetBasemap in map-viewer', error);
});

// Start checking for when the map will be ready
this.#checkMapReady();
Expand Down

0 comments on commit 98c719d

Please sign in to comment.