@memberjunction/ng-map-view 6.1.4 → 6.2.0-edge.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -74,15 +74,15 @@ export declare class MapViewComponent implements OnInit, AfterViewInit, OnDestro
|
|
|
74
74
|
/**
|
|
75
75
|
* Initialize the map engine via MapCore.
|
|
76
76
|
*/
|
|
77
|
-
private
|
|
77
|
+
private initializeMap;
|
|
78
78
|
/**
|
|
79
79
|
* Render records using the current mode, update marker count.
|
|
80
80
|
*/
|
|
81
|
-
private
|
|
81
|
+
private doRender;
|
|
82
82
|
/**
|
|
83
83
|
* True if at least one loaded record carries a non-empty value in the boundary field.
|
|
84
84
|
*/
|
|
85
|
-
private
|
|
85
|
+
private recordsHaveBoundaryData;
|
|
86
86
|
/**
|
|
87
87
|
* Set rendering mode (called from template toolbar buttons).
|
|
88
88
|
*/
|
|
@@ -90,11 +90,11 @@ export declare class MapViewComponent implements OnInit, AfterViewInit, OnDestro
|
|
|
90
90
|
/**
|
|
91
91
|
* Get a field value from a record, supporting both BaseEntity (with .Get()) and plain objects.
|
|
92
92
|
*/
|
|
93
|
-
private
|
|
93
|
+
private getField;
|
|
94
94
|
/**
|
|
95
95
|
* Get a numeric field value from a record, returning null if not a valid number.
|
|
96
96
|
*/
|
|
97
|
-
private
|
|
97
|
+
private getNumericField;
|
|
98
98
|
static ɵfac: i0.ɵɵFactoryDeclaration<MapViewComponent, never>;
|
|
99
99
|
static ɵcmp: i0.ɵɵComponentDeclaration<MapViewComponent, "mj-map-view", never, { "Entity": { "alias": "Entity"; "required": false; }; "Records": { "alias": "Records"; "required": false; }; "LatitudeField": { "alias": "LatitudeField"; "required": false; }; "LongitudeField": { "alias": "LongitudeField"; "required": false; }; "RenderMode": { "alias": "RenderMode"; "required": false; }; "BoundaryField": { "alias": "BoundaryField"; "required": false; }; "DisplayState": { "alias": "DisplayState"; "required": false; }; "TotalRecordCount": { "alias": "TotalRecordCount"; "required": false; }; }, { "MarkerClick": "MarkerClick"; "RegionClick": "RegionClick"; "DisplayStateChange": "DisplayStateChange"; "RenderModeChange": "RenderModeChange"; }, never, never, false, never>;
|
|
100
100
|
}
|
|
@@ -135,13 +135,13 @@ export class MapViewComponent {
|
|
|
135
135
|
const entry = entries[0];
|
|
136
136
|
if (entry.isIntersecting && !this.engine) {
|
|
137
137
|
// First time visible — initialize the map
|
|
138
|
-
this.
|
|
138
|
+
this.initializeMap();
|
|
139
139
|
}
|
|
140
140
|
else if (entry.isIntersecting && this.engine) {
|
|
141
141
|
// Becoming visible again — fix tile rendering and re-fit
|
|
142
142
|
this.engine.invalidateSize();
|
|
143
143
|
if (this.pendingRender) {
|
|
144
|
-
this.
|
|
144
|
+
this.doRender();
|
|
145
145
|
this.pendingRender = false;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -161,7 +161,7 @@ export class MapViewComponent {
|
|
|
161
161
|
this.IsLoading = true;
|
|
162
162
|
this.cdr.detectChanges();
|
|
163
163
|
if (this.mapContainer?.nativeElement) {
|
|
164
|
-
this.
|
|
164
|
+
this.initializeMap();
|
|
165
165
|
}
|
|
166
166
|
else {
|
|
167
167
|
this.pendingRender = true;
|
|
@@ -173,7 +173,7 @@ export class MapViewComponent {
|
|
|
173
173
|
setTimeout(() => {
|
|
174
174
|
if (this.engine) {
|
|
175
175
|
this.engine.invalidateSize();
|
|
176
|
-
this.
|
|
176
|
+
this.doRender();
|
|
177
177
|
}
|
|
178
178
|
}, 100);
|
|
179
179
|
}
|
|
@@ -196,7 +196,7 @@ export class MapViewComponent {
|
|
|
196
196
|
/**
|
|
197
197
|
* Initialize the map engine via MapCore.
|
|
198
198
|
*/
|
|
199
|
-
|
|
199
|
+
initializeMap() {
|
|
200
200
|
if (!this.mapContainer?.nativeElement || this.engine)
|
|
201
201
|
return;
|
|
202
202
|
this.engine = MapCore.createEngine({
|
|
@@ -212,11 +212,11 @@ export class MapViewComponent {
|
|
|
212
212
|
geoResolver: GeoDataEngine.Instance,
|
|
213
213
|
getRecordId: (record) => {
|
|
214
214
|
const pkFields = this.Entity?.PrimaryKeys ?? [];
|
|
215
|
-
return pkFields.map(pk => this.
|
|
215
|
+
return pkFields.map(pk => this.getField(record, pk.Name)).join('||');
|
|
216
216
|
},
|
|
217
217
|
getRecordName: (record) => {
|
|
218
218
|
const nameField = this.Entity?.NameField;
|
|
219
|
-
return nameField ? String(this.
|
|
219
|
+
return nameField ? String(this.getField(record, nameField.Name) ?? '') : 'Record';
|
|
220
220
|
},
|
|
221
221
|
onMarkerClick: (event) => {
|
|
222
222
|
this.MarkerClick.emit({
|
|
@@ -237,12 +237,12 @@ export class MapViewComponent {
|
|
|
237
237
|
onPopupRecordClick: (recordId) => {
|
|
238
238
|
const record = this.Records.find(r => {
|
|
239
239
|
const pkFields = this.Entity?.PrimaryKeys ?? [];
|
|
240
|
-
const id = pkFields.map(pk => this.
|
|
240
|
+
const id = pkFields.map(pk => this.getField(r, pk.Name)).join('||');
|
|
241
241
|
return id === recordId;
|
|
242
242
|
});
|
|
243
243
|
if (record) {
|
|
244
|
-
const lat = this.
|
|
245
|
-
const lng = this.
|
|
244
|
+
const lat = this.getNumericField(record, this.LatitudeField) ?? 0;
|
|
245
|
+
const lng = this.getNumericField(record, this.LongitudeField) ?? 0;
|
|
246
246
|
this.MarkerClick.emit({
|
|
247
247
|
RecordID: recordId,
|
|
248
248
|
Latitude: lat,
|
|
@@ -266,7 +266,7 @@ export class MapViewComponent {
|
|
|
266
266
|
setTimeout(() => {
|
|
267
267
|
if (this.engine) {
|
|
268
268
|
this.engine.invalidateSize();
|
|
269
|
-
this.
|
|
269
|
+
this.doRender();
|
|
270
270
|
this.IsLoading = false;
|
|
271
271
|
this.cdr.detectChanges();
|
|
272
272
|
// Double-invalidate after tiles start loading to fix partial rendering
|
|
@@ -280,7 +280,7 @@ export class MapViewComponent {
|
|
|
280
280
|
/**
|
|
281
281
|
* Render records using the current mode, update marker count.
|
|
282
282
|
*/
|
|
283
|
-
|
|
283
|
+
doRender() {
|
|
284
284
|
if (!this.engine)
|
|
285
285
|
return;
|
|
286
286
|
this.engine.render(this.Records, this.RenderMode);
|
|
@@ -293,7 +293,7 @@ export class MapViewComponent {
|
|
|
293
293
|
&& this.RenderMode === 'point'
|
|
294
294
|
&& this.MarkerCount === 0
|
|
295
295
|
&& this.Records.length > 0
|
|
296
|
-
&& this.
|
|
296
|
+
&& this.recordsHaveBoundaryData()) {
|
|
297
297
|
this.autoModeApplied = true;
|
|
298
298
|
this.SetRenderMode('boundary');
|
|
299
299
|
}
|
|
@@ -301,10 +301,10 @@ export class MapViewComponent {
|
|
|
301
301
|
/**
|
|
302
302
|
* True if at least one loaded record carries a non-empty value in the boundary field.
|
|
303
303
|
*/
|
|
304
|
-
|
|
304
|
+
recordsHaveBoundaryData() {
|
|
305
305
|
const field = this.BoundaryField;
|
|
306
306
|
for (const record of this.Records) {
|
|
307
|
-
const val = this.
|
|
307
|
+
const val = this.getField(record, field);
|
|
308
308
|
if (val != null && val !== '')
|
|
309
309
|
return true;
|
|
310
310
|
}
|
|
@@ -316,12 +316,12 @@ export class MapViewComponent {
|
|
|
316
316
|
SetRenderMode(mode) {
|
|
317
317
|
this.RenderMode = mode;
|
|
318
318
|
this.RenderModeChange.emit(mode);
|
|
319
|
-
this.
|
|
319
|
+
this.doRender();
|
|
320
320
|
}
|
|
321
321
|
/**
|
|
322
322
|
* Get a field value from a record, supporting both BaseEntity (with .Get()) and plain objects.
|
|
323
323
|
*/
|
|
324
|
-
|
|
324
|
+
getField(record, fieldName) {
|
|
325
325
|
if ('Get' in record && typeof record['Get'] === 'function') {
|
|
326
326
|
return record.Get(fieldName);
|
|
327
327
|
}
|
|
@@ -330,8 +330,8 @@ export class MapViewComponent {
|
|
|
330
330
|
/**
|
|
331
331
|
* Get a numeric field value from a record, returning null if not a valid number.
|
|
332
332
|
*/
|
|
333
|
-
|
|
334
|
-
const val = this.
|
|
333
|
+
getNumericField(record, fieldName) {
|
|
334
|
+
const val = this.getField(record, fieldName);
|
|
335
335
|
if (val == null)
|
|
336
336
|
return null;
|
|
337
337
|
const num = Number(val);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-view.component.js","sourceRoot":"","sources":["../../src/lib/map-view.component.ts","../../src/lib/map-view.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n OnInit,\n OnDestroy,\n OnChanges,\n SimpleChanges,\n ElementRef,\n ViewChild,\n ChangeDetectorRef,\n AfterViewInit,\n inject\n} from '@angular/core';\nimport { EntityInfo, ResolveMapLatitudeField, ResolveMapLongitudeField } from '@memberjunction/core';\nimport { GeoDataEngine } from '@memberjunction/core-entities';\nimport { MapRenderMode, MapDisplayState, MapMarkerClickEvent, MapRegionClickEvent } from './map-view.types';\nimport * as MapCore from '@memberjunction/geo-maps';\n// Leaflet is loaded as a global script at runtime via angular.json scripts array.\n// The L namespace is available globally — see leaflet.d.ts for type declarations.\n\n/**\n * MapViewComponent — Leaflet-based map visualization for geo-enabled MJ entities.\n *\n * Reads `__mj_Latitude` and `__mj_Longitude` from entity records (or custom field overrides)\n * and renders them as interactive markers on an OpenStreetMap tile layer.\n *\n * Supports three rendering modes:\n * - **point** (default): Individual markers with optional clustering\n * - **choropleth**: Region shading by country/state (requires BoundaryGeoJSON)\n * - **heatmap**: Density visualization\n *\n * Delegates all rendering logic to @memberjunction/geo-maps.\n * This component is a thin Angular wrapper managing lifecycle, inputs/outputs,\n * and IntersectionObserver-based deferred initialization.\n */\n@Component({\n standalone: false,\n selector: 'mj-map-view',\n templateUrl: './map-view.component.html',\n styleUrls: ['./map-view.component.css']\n})\nexport class MapViewComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {\n private cdr = inject(ChangeDetectorRef);\n\n @ViewChild('mapContainer', { static: false })\n private mapContainer!: ElementRef<HTMLDivElement>;\n\n /** Entity metadata for the displayed records. */\n @Input() Entity!: EntityInfo;\n\n /** Array of entity records to render on the map. Accepts BaseEntity[] or plain Record objects. */\n @Input() Records: Record<string, unknown>[] = [];\n\n /**\n * Latitude field name. If left at the default, auto-resolves to the field\n * on `Entity` tagged with `ExtendedType='GeoLatitude'`.\n */\n @Input() LatitudeField: string = '__mj_Latitude';\n\n /** Longitude field name. Same auto-resolution as LatitudeField. */\n @Input() LongitudeField: string = '__mj_Longitude';\n\n /** Current render mode. */\n @Input() RenderMode: MapRenderMode = 'point';\n\n /** Field name containing GeoJSON boundary data on each record (for boundary mode). */\n @Input() BoundaryField: string = 'BoundaryGeoJSON';\n\n\n /** Persisted display state from UserView. */\n @Input() DisplayState: Partial<MapDisplayState> | null = null;\n\n /**\n * Total record count from the server (before any client-side cap).\n * When this exceeds the number of Records passed in, the map shows\n * a truncation indicator so the user knows not all data is displayed.\n */\n @Input() TotalRecordCount: number = 0;\n\n /** Emitted when a marker is clicked. */\n @Output() MarkerClick = new EventEmitter<MapMarkerClickEvent>();\n\n /** Emitted when a choropleth region is clicked. */\n @Output() RegionClick = new EventEmitter<MapRegionClickEvent>();\n\n /** Emitted when display state changes (for persistence). */\n @Output() DisplayStateChange = new EventEmitter<MapDisplayState>();\n\n /** Emitted when render mode changes (supports two-way binding: [(RenderMode)]). */\n @Output() RenderModeChange = new EventEmitter<MapRenderMode>();\n\n private engine: MapCore.MapEngine | null = null;\n private visibilityObserver: IntersectionObserver | null = null;\n private pendingRender = false;\n private autoModeApplied = false;\n IsLoading = true;\n MarkerCount = 0;\n\n /** Whether the displayed records are truncated (more exist than were loaded). */\n get IsTruncated(): boolean {\n return this.TotalRecordCount > 0 && this.Records.length < this.TotalRecordCount;\n }\n\n /** True when the entity has a per-record boundary GeoJSON field — gates the Boundary toolbar button. */\n get HasBoundaryField(): boolean {\n if (!this.Entity) return false;\n const fieldName = this.BoundaryField || 'BoundaryGeoJSON';\n return this.Entity.Fields.some(f => f.Name === fieldName);\n }\n\n /**\n * Resolve a lat/lng field name. Explicit override wins; otherwise picks the\n * field tagged with the matching `ExtendedType` on `Entity`.\n */\n private get effectiveLatitudeField(): string {\n if (!this.Entity) return this.LatitudeField || '__mj_Latitude';\n return ResolveMapLatitudeField(this.Entity, this.LatitudeField);\n }\n private get effectiveLongitudeField(): string {\n if (!this.Entity) return this.LongitudeField || '__mj_Longitude';\n return ResolveMapLongitudeField(this.Entity, this.LongitudeField);\n }\n\n ngOnInit(): void {\n // Snap saved 'boundary' → 'point' when the entity has no boundary field\n // (the button is hidden, so the user would be stuck on it).\n if (this.RenderMode === 'boundary' && !this.HasBoundaryField) {\n this.RenderMode = 'point';\n this.RenderModeChange.emit(this.RenderMode);\n }\n }\n\n ngAfterViewInit(): void {\n // Use IntersectionObserver to detect when the map container becomes visible.\n // Leaflet requires the container to have actual dimensions before initialization.\n // Since the map starts hidden (grid view is default), we defer init until visible.\n this.visibilityObserver = new IntersectionObserver((entries) => {\n const entry = entries[0];\n if (entry.isIntersecting && !this.engine) {\n // First time visible — initialize the map\n this.InitializeMap();\n } else if (entry.isIntersecting && this.engine) {\n // Becoming visible again — fix tile rendering and re-fit\n this.engine.invalidateSize();\n if (this.pendingRender) {\n this.DoRender();\n this.pendingRender = false;\n }\n }\n }, { threshold: 0.1 });\n\n if (this.mapContainer?.nativeElement) {\n this.visibilityObserver.observe(this.mapContainer.nativeElement);\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n // Entity change invalidates per-Entity config (lat/lng field names,\n // Boundary visibility) cached at engine-init. Tear down so the next\n // render rebuilds against the new Entity.\n if (changes['Entity'] && !changes['Entity'].firstChange && this.engine) {\n this.engine.destroy();\n this.engine = null;\n this.autoModeApplied = false;\n this.IsLoading = true;\n this.cdr.detectChanges();\n if (this.mapContainer?.nativeElement) {\n this.InitializeMap();\n } else {\n this.pendingRender = true;\n }\n return;\n }\n\n if (changes['Records'] || changes['RenderMode']) {\n if (this.engine) {\n setTimeout(() => {\n if (this.engine) {\n this.engine.invalidateSize();\n this.DoRender();\n }\n }, 100);\n } else {\n // Map not initialized yet — mark for render when visible\n this.pendingRender = true;\n }\n }\n }\n\n ngOnDestroy(): void {\n if (this.visibilityObserver) {\n this.visibilityObserver.disconnect();\n this.visibilityObserver = null;\n }\n if (this.engine) {\n this.engine.destroy();\n this.engine = null;\n }\n }\n\n /**\n * Initialize the map engine via MapCore.\n */\n private InitializeMap(): void {\n if (!this.mapContainer?.nativeElement || this.engine) return;\n\n this.engine = MapCore.createEngine({\n container: this.mapContainer.nativeElement,\n center: {\n lat: this.DisplayState?.CenterLat ?? 20,\n lng: this.DisplayState?.CenterLng ?? 0\n },\n zoom: this.DisplayState?.ZoomLevel ?? 2,\n latitudeField: this.effectiveLatitudeField,\n longitudeField: this.effectiveLongitudeField,\n boundaryField: this.BoundaryField,\n geoResolver: GeoDataEngine.Instance,\n getRecordId: (record: Record<string, unknown>) => {\n const pkFields = this.Entity?.PrimaryKeys ?? [];\n return pkFields.map(pk => this.GetField(record, pk.Name)).join('||');\n },\n getRecordName: (record: Record<string, unknown>) => {\n const nameField = this.Entity?.NameField;\n return nameField ? String(this.GetField(record, nameField.Name) ?? '') : 'Record';\n },\n onMarkerClick: (event: MapCore.MarkerClickEvent) => {\n this.MarkerClick.emit({\n RecordID: event.recordId,\n Latitude: event.lat,\n Longitude: event.lng,\n Record: event.record\n });\n },\n onRegionClick: (event: MapCore.RegionClickEvent) => {\n this.RegionClick.emit({\n RegionName: event.regionName,\n GroupBy: event.groupBy as 'country' | 'state_province',\n RecordCount: event.recordCount,\n Records: event.records\n });\n },\n onPopupRecordClick: (recordId: string) => {\n const record = this.Records.find(r => {\n const pkFields = this.Entity?.PrimaryKeys ?? [];\n const id = pkFields.map(pk => this.GetField(r, pk.Name)).join('||');\n return id === recordId;\n });\n if (record) {\n const lat = this.GetNumericField(record, this.LatitudeField) ?? 0;\n const lng = this.GetNumericField(record, this.LongitudeField) ?? 0;\n this.MarkerClick.emit({\n RecordID: recordId,\n Latitude: lat,\n Longitude: lng,\n Record: record\n });\n }\n },\n onMoveEnd: (state: MapCore.MoveEndEvent) => {\n this.DisplayStateChange.emit({\n ZoomLevel: state.zoom,\n CenterLat: state.centerLat,\n CenterLng: state.centerLng,\n ClusterMarkers: true\n });\n },\n maxPopupRecords: 5\n });\n\n // invalidateSize after a short delay to ensure tile rendering is correct\n // then render markers and fit bounds\n setTimeout(() => {\n if (this.engine) {\n this.engine.invalidateSize();\n this.DoRender();\n this.IsLoading = false;\n this.cdr.detectChanges();\n\n // Double-invalidate after tiles start loading to fix partial rendering\n setTimeout(() => {\n if (this.engine) this.engine.invalidateSize();\n }, 500);\n }\n }, 100);\n }\n\n /**\n * Render records using the current mode, update marker count.\n */\n private DoRender(): void {\n if (!this.engine) return;\n this.engine.render(this.Records, this.RenderMode);\n this.MarkerCount = this.engine.getStats().markerCount;\n this.cdr.detectChanges();\n\n // One-shot: if 'point' rendered nothing but records carry GeoJSON\n // (e.g. MJ: Countries), flip to 'boundary' so the map isn't blank.\n // autoModeApplied prevents second-guessing manual mode changes.\n if (!this.autoModeApplied\n && this.RenderMode === 'point'\n && this.MarkerCount === 0\n && this.Records.length > 0\n && this.RecordsHaveBoundaryData()) {\n this.autoModeApplied = true;\n this.SetRenderMode('boundary');\n }\n }\n\n /**\n * True if at least one loaded record carries a non-empty value in the boundary field.\n */\n private RecordsHaveBoundaryData(): boolean {\n const field = this.BoundaryField;\n for (const record of this.Records) {\n const val = this.GetField(record, field);\n if (val != null && val !== '') return true;\n }\n return false;\n }\n\n /**\n * Set rendering mode (called from template toolbar buttons).\n */\n SetRenderMode(mode: MapRenderMode): void {\n this.RenderMode = mode;\n this.RenderModeChange.emit(mode);\n this.DoRender();\n }\n\n /**\n * Get a field value from a record, supporting both BaseEntity (with .Get()) and plain objects.\n */\n private GetField(record: Record<string, unknown>, fieldName: string): unknown {\n if ('Get' in record && typeof record['Get'] === 'function') {\n return (record as { Get: (name: string) => unknown }).Get(fieldName);\n }\n return record[fieldName];\n }\n\n /**\n * Get a numeric field value from a record, returning null if not a valid number.\n */\n private GetNumericField(record: Record<string, unknown>, fieldName: string): number | null {\n const val = this.GetField(record, fieldName);\n if (val == null) return null;\n const num = Number(val);\n return isNaN(num) ? null : num;\n }\n}\n","<div class=\"mj-map-view-wrapper\">\n <div class=\"mj-map-toolbar\">\n <div class=\"mj-map-mode-buttons\">\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'point'\"\n (click)=\"SetRenderMode('point')\">\n <i class=\"fa-solid fa-location-dot\"></i> Points\n </button>\n @if (HasBoundaryField) {\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'boundary'\"\n (click)=\"SetRenderMode('boundary')\">\n <i class=\"fa-solid fa-draw-polygon\"></i> Boundary\n </button>\n }\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'choropleth'\"\n (click)=\"SetRenderMode('choropleth')\">\n <i class=\"fa-solid fa-map\"></i> Regions\n </button>\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'heatmap'\"\n (click)=\"SetRenderMode('heatmap')\">\n <i class=\"fa-solid fa-fire\"></i> Heat Map\n </button>\n </div>\n <div class=\"mj-map-info\">\n {{ MarkerCount }} location{{ MarkerCount !== 1 ? 's' : '' }} mapped\n @if (IsTruncated) {\n <span class=\"mj-map-truncated\" title=\"Not all records are displayed. Showing {{ Records.length | number }} of {{ TotalRecordCount | number }} total records.\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i> Showing {{ Records.length | number }} of {{ TotalRecordCount | number }}\n </span>\n }\n </div>\n </div>\n @if (IsLoading) {\n <div class=\"mj-map-loading\">\n <mj-loading text=\"Loading map...\"></mj-loading>\n </div>\n }\n <div #mapContainer class=\"mj-map-container\"></div>\n</div>\n"]}
|
|
1
|
+
{"version":3,"file":"map-view.component.js","sourceRoot":"","sources":["../../src/lib/map-view.component.ts","../../src/lib/map-view.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n OnInit,\n OnDestroy,\n OnChanges,\n SimpleChanges,\n ElementRef,\n ViewChild,\n ChangeDetectorRef,\n AfterViewInit,\n inject\n} from '@angular/core';\nimport { EntityInfo, ResolveMapLatitudeField, ResolveMapLongitudeField } from '@memberjunction/core';\nimport { GeoDataEngine } from '@memberjunction/core-entities';\nimport { MapRenderMode, MapDisplayState, MapMarkerClickEvent, MapRegionClickEvent } from './map-view.types';\nimport * as MapCore from '@memberjunction/geo-maps';\n// Leaflet is loaded as a global script at runtime via angular.json scripts array.\n// The L namespace is available globally — see leaflet.d.ts for type declarations.\n\n/**\n * MapViewComponent — Leaflet-based map visualization for geo-enabled MJ entities.\n *\n * Reads `__mj_Latitude` and `__mj_Longitude` from entity records (or custom field overrides)\n * and renders them as interactive markers on an OpenStreetMap tile layer.\n *\n * Supports three rendering modes:\n * - **point** (default): Individual markers with optional clustering\n * - **choropleth**: Region shading by country/state (requires BoundaryGeoJSON)\n * - **heatmap**: Density visualization\n *\n * Delegates all rendering logic to @memberjunction/geo-maps.\n * This component is a thin Angular wrapper managing lifecycle, inputs/outputs,\n * and IntersectionObserver-based deferred initialization.\n */\n@Component({\n standalone: false,\n selector: 'mj-map-view',\n templateUrl: './map-view.component.html',\n styleUrls: ['./map-view.component.css']\n})\nexport class MapViewComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {\n private cdr = inject(ChangeDetectorRef);\n\n @ViewChild('mapContainer', { static: false })\n private mapContainer!: ElementRef<HTMLDivElement>;\n\n /** Entity metadata for the displayed records. */\n @Input() Entity!: EntityInfo;\n\n /** Array of entity records to render on the map. Accepts BaseEntity[] or plain Record objects. */\n @Input() Records: Record<string, unknown>[] = [];\n\n /**\n * Latitude field name. If left at the default, auto-resolves to the field\n * on `Entity` tagged with `ExtendedType='GeoLatitude'`.\n */\n @Input() LatitudeField: string = '__mj_Latitude';\n\n /** Longitude field name. Same auto-resolution as LatitudeField. */\n @Input() LongitudeField: string = '__mj_Longitude';\n\n /** Current render mode. */\n @Input() RenderMode: MapRenderMode = 'point';\n\n /** Field name containing GeoJSON boundary data on each record (for boundary mode). */\n @Input() BoundaryField: string = 'BoundaryGeoJSON';\n\n\n /** Persisted display state from UserView. */\n @Input() DisplayState: Partial<MapDisplayState> | null = null;\n\n /**\n * Total record count from the server (before any client-side cap).\n * When this exceeds the number of Records passed in, the map shows\n * a truncation indicator so the user knows not all data is displayed.\n */\n @Input() TotalRecordCount: number = 0;\n\n /** Emitted when a marker is clicked. */\n @Output() MarkerClick = new EventEmitter<MapMarkerClickEvent>();\n\n /** Emitted when a choropleth region is clicked. */\n @Output() RegionClick = new EventEmitter<MapRegionClickEvent>();\n\n /** Emitted when display state changes (for persistence). */\n @Output() DisplayStateChange = new EventEmitter<MapDisplayState>();\n\n /** Emitted when render mode changes (supports two-way binding: [(RenderMode)]). */\n @Output() RenderModeChange = new EventEmitter<MapRenderMode>();\n\n private engine: MapCore.MapEngine | null = null;\n private visibilityObserver: IntersectionObserver | null = null;\n private pendingRender = false;\n private autoModeApplied = false;\n IsLoading = true;\n MarkerCount = 0;\n\n /** Whether the displayed records are truncated (more exist than were loaded). */\n get IsTruncated(): boolean {\n return this.TotalRecordCount > 0 && this.Records.length < this.TotalRecordCount;\n }\n\n /** True when the entity has a per-record boundary GeoJSON field — gates the Boundary toolbar button. */\n get HasBoundaryField(): boolean {\n if (!this.Entity) return false;\n const fieldName = this.BoundaryField || 'BoundaryGeoJSON';\n return this.Entity.Fields.some(f => f.Name === fieldName);\n }\n\n /**\n * Resolve a lat/lng field name. Explicit override wins; otherwise picks the\n * field tagged with the matching `ExtendedType` on `Entity`.\n */\n private get effectiveLatitudeField(): string {\n if (!this.Entity) return this.LatitudeField || '__mj_Latitude';\n return ResolveMapLatitudeField(this.Entity, this.LatitudeField);\n }\n private get effectiveLongitudeField(): string {\n if (!this.Entity) return this.LongitudeField || '__mj_Longitude';\n return ResolveMapLongitudeField(this.Entity, this.LongitudeField);\n }\n\n ngOnInit(): void {\n // Snap saved 'boundary' → 'point' when the entity has no boundary field\n // (the button is hidden, so the user would be stuck on it).\n if (this.RenderMode === 'boundary' && !this.HasBoundaryField) {\n this.RenderMode = 'point';\n this.RenderModeChange.emit(this.RenderMode);\n }\n }\n\n ngAfterViewInit(): void {\n // Use IntersectionObserver to detect when the map container becomes visible.\n // Leaflet requires the container to have actual dimensions before initialization.\n // Since the map starts hidden (grid view is default), we defer init until visible.\n this.visibilityObserver = new IntersectionObserver((entries) => {\n const entry = entries[0];\n if (entry.isIntersecting && !this.engine) {\n // First time visible — initialize the map\n this.initializeMap();\n } else if (entry.isIntersecting && this.engine) {\n // Becoming visible again — fix tile rendering and re-fit\n this.engine.invalidateSize();\n if (this.pendingRender) {\n this.doRender();\n this.pendingRender = false;\n }\n }\n }, { threshold: 0.1 });\n\n if (this.mapContainer?.nativeElement) {\n this.visibilityObserver.observe(this.mapContainer.nativeElement);\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n // Entity change invalidates per-Entity config (lat/lng field names,\n // Boundary visibility) cached at engine-init. Tear down so the next\n // render rebuilds against the new Entity.\n if (changes['Entity'] && !changes['Entity'].firstChange && this.engine) {\n this.engine.destroy();\n this.engine = null;\n this.autoModeApplied = false;\n this.IsLoading = true;\n this.cdr.detectChanges();\n if (this.mapContainer?.nativeElement) {\n this.initializeMap();\n } else {\n this.pendingRender = true;\n }\n return;\n }\n\n if (changes['Records'] || changes['RenderMode']) {\n if (this.engine) {\n setTimeout(() => {\n if (this.engine) {\n this.engine.invalidateSize();\n this.doRender();\n }\n }, 100);\n } else {\n // Map not initialized yet — mark for render when visible\n this.pendingRender = true;\n }\n }\n }\n\n ngOnDestroy(): void {\n if (this.visibilityObserver) {\n this.visibilityObserver.disconnect();\n this.visibilityObserver = null;\n }\n if (this.engine) {\n this.engine.destroy();\n this.engine = null;\n }\n }\n\n /**\n * Initialize the map engine via MapCore.\n */\n private initializeMap(): void {\n if (!this.mapContainer?.nativeElement || this.engine) return;\n\n this.engine = MapCore.createEngine({\n container: this.mapContainer.nativeElement,\n center: {\n lat: this.DisplayState?.CenterLat ?? 20,\n lng: this.DisplayState?.CenterLng ?? 0\n },\n zoom: this.DisplayState?.ZoomLevel ?? 2,\n latitudeField: this.effectiveLatitudeField,\n longitudeField: this.effectiveLongitudeField,\n boundaryField: this.BoundaryField,\n geoResolver: GeoDataEngine.Instance,\n getRecordId: (record: Record<string, unknown>) => {\n const pkFields = this.Entity?.PrimaryKeys ?? [];\n return pkFields.map(pk => this.getField(record, pk.Name)).join('||');\n },\n getRecordName: (record: Record<string, unknown>) => {\n const nameField = this.Entity?.NameField;\n return nameField ? String(this.getField(record, nameField.Name) ?? '') : 'Record';\n },\n onMarkerClick: (event: MapCore.MarkerClickEvent) => {\n this.MarkerClick.emit({\n RecordID: event.recordId,\n Latitude: event.lat,\n Longitude: event.lng,\n Record: event.record\n });\n },\n onRegionClick: (event: MapCore.RegionClickEvent) => {\n this.RegionClick.emit({\n RegionName: event.regionName,\n GroupBy: event.groupBy as 'country' | 'state_province',\n RecordCount: event.recordCount,\n Records: event.records\n });\n },\n onPopupRecordClick: (recordId: string) => {\n const record = this.Records.find(r => {\n const pkFields = this.Entity?.PrimaryKeys ?? [];\n const id = pkFields.map(pk => this.getField(r, pk.Name)).join('||');\n return id === recordId;\n });\n if (record) {\n const lat = this.getNumericField(record, this.LatitudeField) ?? 0;\n const lng = this.getNumericField(record, this.LongitudeField) ?? 0;\n this.MarkerClick.emit({\n RecordID: recordId,\n Latitude: lat,\n Longitude: lng,\n Record: record\n });\n }\n },\n onMoveEnd: (state: MapCore.MoveEndEvent) => {\n this.DisplayStateChange.emit({\n ZoomLevel: state.zoom,\n CenterLat: state.centerLat,\n CenterLng: state.centerLng,\n ClusterMarkers: true\n });\n },\n maxPopupRecords: 5\n });\n\n // invalidateSize after a short delay to ensure tile rendering is correct\n // then render markers and fit bounds\n setTimeout(() => {\n if (this.engine) {\n this.engine.invalidateSize();\n this.doRender();\n this.IsLoading = false;\n this.cdr.detectChanges();\n\n // Double-invalidate after tiles start loading to fix partial rendering\n setTimeout(() => {\n if (this.engine) this.engine.invalidateSize();\n }, 500);\n }\n }, 100);\n }\n\n /**\n * Render records using the current mode, update marker count.\n */\n private doRender(): void {\n if (!this.engine) return;\n this.engine.render(this.Records, this.RenderMode);\n this.MarkerCount = this.engine.getStats().markerCount;\n this.cdr.detectChanges();\n\n // One-shot: if 'point' rendered nothing but records carry GeoJSON\n // (e.g. MJ: Countries), flip to 'boundary' so the map isn't blank.\n // autoModeApplied prevents second-guessing manual mode changes.\n if (!this.autoModeApplied\n && this.RenderMode === 'point'\n && this.MarkerCount === 0\n && this.Records.length > 0\n && this.recordsHaveBoundaryData()) {\n this.autoModeApplied = true;\n this.SetRenderMode('boundary');\n }\n }\n\n /**\n * True if at least one loaded record carries a non-empty value in the boundary field.\n */\n private recordsHaveBoundaryData(): boolean {\n const field = this.BoundaryField;\n for (const record of this.Records) {\n const val = this.getField(record, field);\n if (val != null && val !== '') return true;\n }\n return false;\n }\n\n /**\n * Set rendering mode (called from template toolbar buttons).\n */\n SetRenderMode(mode: MapRenderMode): void {\n this.RenderMode = mode;\n this.RenderModeChange.emit(mode);\n this.doRender();\n }\n\n /**\n * Get a field value from a record, supporting both BaseEntity (with .Get()) and plain objects.\n */\n private getField(record: Record<string, unknown>, fieldName: string): unknown {\n if ('Get' in record && typeof record['Get'] === 'function') {\n return (record as { Get: (name: string) => unknown }).Get(fieldName);\n }\n return record[fieldName];\n }\n\n /**\n * Get a numeric field value from a record, returning null if not a valid number.\n */\n private getNumericField(record: Record<string, unknown>, fieldName: string): number | null {\n const val = this.getField(record, fieldName);\n if (val == null) return null;\n const num = Number(val);\n return isNaN(num) ? null : num;\n }\n}\n","<div class=\"mj-map-view-wrapper\">\n <div class=\"mj-map-toolbar\">\n <div class=\"mj-map-mode-buttons\">\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'point'\"\n (click)=\"SetRenderMode('point')\">\n <i class=\"fa-solid fa-location-dot\"></i> Points\n </button>\n @if (HasBoundaryField) {\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'boundary'\"\n (click)=\"SetRenderMode('boundary')\">\n <i class=\"fa-solid fa-draw-polygon\"></i> Boundary\n </button>\n }\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'choropleth'\"\n (click)=\"SetRenderMode('choropleth')\">\n <i class=\"fa-solid fa-map\"></i> Regions\n </button>\n <button class=\"mj-map-mode-btn\"\n [class.active]=\"RenderMode === 'heatmap'\"\n (click)=\"SetRenderMode('heatmap')\">\n <i class=\"fa-solid fa-fire\"></i> Heat Map\n </button>\n </div>\n <div class=\"mj-map-info\">\n {{ MarkerCount }} location{{ MarkerCount !== 1 ? 's' : '' }} mapped\n @if (IsTruncated) {\n <span class=\"mj-map-truncated\" title=\"Not all records are displayed. Showing {{ Records.length | number }} of {{ TotalRecordCount | number }} total records.\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i> Showing {{ Records.length | number }} of {{ TotalRecordCount | number }}\n </span>\n }\n </div>\n </div>\n @if (IsLoading) {\n <div class=\"mj-map-loading\">\n <mj-loading text=\"Loading map...\"></mj-loading>\n </div>\n }\n <div #mapContainer class=\"mj-map-container\"></div>\n</div>\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-map-view",
|
|
3
|
-
"version": "6.1
|
|
3
|
+
"version": "6.2.0-edge.1",
|
|
4
4
|
"description": "MemberJunction: Angular Leaflet-based map component for visualizing geo-enabled entity data",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@angular/compiler": "21.2.22",
|
|
14
14
|
"@angular/compiler-cli": "21.2.22",
|
|
15
|
-
"@memberjunction/ng-test-utils": "6.1
|
|
15
|
+
"@memberjunction/ng-test-utils": "6.2.0-edge.1",
|
|
16
16
|
"vitest": "^4.0.18"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"@angular/core": "^21.2.22"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@memberjunction/core": "6.1
|
|
24
|
-
"@memberjunction/core-entities": "6.1
|
|
25
|
-
"@memberjunction/global": "6.1
|
|
26
|
-
"@memberjunction/geo-maps": "6.1
|
|
27
|
-
"@memberjunction/ng-shared-generic": "6.1
|
|
23
|
+
"@memberjunction/core": "6.2.0-edge.1",
|
|
24
|
+
"@memberjunction/core-entities": "6.2.0-edge.1",
|
|
25
|
+
"@memberjunction/global": "6.2.0-edge.1",
|
|
26
|
+
"@memberjunction/geo-maps": "6.2.0-edge.1",
|
|
27
|
+
"@memberjunction/ng-shared-generic": "6.2.0-edge.1",
|
|
28
28
|
"leaflet": "^1.9.4",
|
|
29
29
|
"@types/leaflet": "^1.9.17",
|
|
30
30
|
"tslib": "^2.8.1",
|