Info |
---|
The CLI component is only working for versions before 9.10. It must be upgraded to Angular 15 as well and will come later. |
Table of Contents | ||
---|---|---|
|
Example 1: MAP + Angular library
Description
In this example we will create a new tab in the object area, which will show a map with our current location.
Implementation
Use a CLI command to generate a plug-in component with name map.
Code Block language bash linenumbers true eo g plugin map
Use a CLI command to install the maps package (takes some time).
Code Block language bash linenumbers true npm install -P @agm/core
Import AgmCoreModule module to CustomPluginsModule.
Code Block language js title custom-plugins.module.ts linenumbers true import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {EoFrameworkModule} from '@eo-sdk/client'; import {PluginsModule} from '@eo-sdk/client'; import {EoPlugin} from '@eo-sdk/client'; import {links} from '../custom-states/custom-states.module'; import {MapComponent} from './map/map.component'; import {AgmCoreModule} from '@agm/core'; export const entryComponents: EoPlugin[] = [ MapComponent, ]; @NgModule({ imports: [ CommonModule, EoFrameworkModule, AgmCoreModule.forRoot(), PluginsModule.forRoot(entryComponents, links) ], declarations: [MapComponent], exports: [PluginsModule] }) export class CustomPluginsModule { }
Update map.component.ts to load the current position from navigator, and modify the matchType property if necessary.
Code Block language js title map.component.ts linenumbers true import { Component, OnInit } from '@angular/core'; @Component({ selector: 'eo-map', templateUrl: './map.component.html', styleUrls: ['./map.component.scss'] }) export class MapComponent implements OnInit { static id = 'eo.custom.plugin.map'; static matchType = new RegExp ('object-details-tab.*'); currentPosition; secureOriginIssue = false; constructor() { } private getCurrentPosition(): void { navigator.geolocation.getCurrentPosition((position) => { this.currentPosition = position; }, failure => { if (failure.message.indexOf('Only secure origins are allowed') === 0) { this.secureOriginIssue = true; } }); } ngOnInit() { this.getCurrentPosition(); } }
Update map.component.html to include a map component with the latitude and longitude based on the current position.
Code Block language xml title map.component.html linenumbers true <agm-map *ngIf="currentPosition && !secureOriginIssue" [latitude]="currentPosition.coords.latitude" [longitude]="currentPosition.coords.longitude" [style.height.%]="90"> <agm-marker [latitude]="currentPosition.coords.latitude" [longitude]="currentPosition.coords.longitude"></agm-marker> </agm-map> <section *ngIf="!currentPosition && secureOriginIssue" class="secure-origin__issue"> <eo-icon class="no-file" [iconSrc]="'assets/_default/svg/ic_not_listed_location.svg'"></eo-icon> <span translate>eo.custom.plugin.map.secure.origin</span> </section>
To make it look nice, we add some scss
Code Block language css title map.component.scss linenumbers true .secure-origin__issue { height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; text-transform: capitalize; border: 0; .no-file { width: 128px; height: 128px; opacity: .06; } }
Use a CLI command to generate labels/translations
Code Block language bash linenumbers true eo g label eo.custom.plugin.map --en Map --de Stadtplan
Code Block language bash linenumbers true eo g label eo.custom.plugin.map.secure.origin --en "Please, change to a secure connection (https)" --de "Bitte wechseln sie zu einer sicheren verbindung (https)"
...
Use a CLI command to generate a plug-in component.
Code Block language bash linenumbers true eo g plugin map-frame
Update map-frame.component.ts to load the current address from the dms object, and modify matchType property if necessary. Make sure that the normalize function matches your scheme properties!
Code Block language js title map-frame.component.ts linenumbers true import { Component, AfterViewInit, ViewChild, ElementRef, Renderer2 } from '@angular/core'; import {DmsService, DmsObject, EventService, EnaioEvent, Event} from '@eo-sdk/core'; // In the following line ', UnsubscribeOnDestroy' has to be removed when updating to version 9.10 or younger: import {SelectionService, UnsubscribeOnDestroy} from '@eo-sdk/client'; import {takeUntil} from 'rxjs/operators'; @Component({ selector: 'eo-map-frame', templateUrl: './map-frame.component.html', styleUrls: ['./map-frame.component.scss'] })'] }) // In the following line 'extends UnsubscribeOnDestroy' has to be removed when updating to version 9.10 or younger: export class MapFrameComponent extends UnsubscribeOnDestroy implements AfterViewInit { static id = 'eo.custom.plugin.map-frame'; static matchType = new RegExp('object-details-tab.*'); context; @ViewChild('mapFrame') mapFrame: ElementRef; constructor(private selectionService: SelectionService, private dmsService: DmsService, private renderer: Renderer2, private eventService: EventService) { super(); } /** * normalize Address data - map your data based on scheme properties * @param data * @returns */ private normalize(data: any = {}): any { return { streethw: data.strassehw, townhw: data.orthw, countryhw: data.landhw, ...data }; } /** * Process dmsObject to get the url for map frame * @param dmsObj */ setupMap(dmsObj: DmsObject) { if (dmsObj) { const {streethw, townhw, countryhw} = this.normalize(dmsObj.data); const url = `https://www.google.com/maps/embed/v1/place?key=AIzaSyDX8znfh-d4u3spGhC1GvCjq6EA1pjPovQ&q=${streethw}+${townhw}+${countryhw}`; this.renderer.setAttribute(this.mapFrame.nativeElement, 'src', url); } } /** * Load & update current context/dmsObject * @param event */ eventHandler(event: Event) { if (event.type === EnaioEvent.DMS_OBJECT_LOADED || (this.context && this.context.id === event.data.id)) { this.context = event.data; this.setupMap(event.data); } } ngAfterViewInit() { this.eventService .on(EnaioEvent.DMS_OBJECT_LOADED, EnaioEvent.DMS_OBJECT_UPDATED) .pipe( takeUntil(this.componentDestroyed$) ) .subscribe(e => this.eventHandler(e)); } }
Update map-frame.component.html to include a Google maps iframe based on the address of the selected dms object.
Code Block language xml title map-frame.component.html linenumbers true <iframe #mapFrame width="100%" height="90%" frameborder="0" style="border:0" allowfullscreen> </iframe>
Use a CLI command to generate labels/translations
Code Block language bash linenumbers true eo g label eo.custom.plugin.map-frame --en GoogleMaps --de GoogleMaps
...
- We start by extending the "MAP via iframe + core service"
For readability, we move most of our functionality to a service
Code Block language bash title Terminal ng g s eo-custom-client/services/location --spec false
To keep the code organized, we introduce LocationType enum.
Code Block language bash title Terminal ng g enum eo-custom-client/enum/LocationType
Update locationType.ts
Code Block language js title locationTypes.rs linenumbers true export enum LocationType { COORDS = 'coords', ADDRESS = 'address', EMPTY = 'empty' }
To keep the code organized, we introduce an Interfaces for the Locations.
Code Block language bash title Terminal ng g interface eo-custom-client/interfaces/Location
Update location.interface.ts
Code Block language js title location.interface.ts linenumbers true export interface LocationByAddress { type: string; streethw: string; townhw: string; countryhw: string } export interface LocationByCoords { type: string; photogpsla: number; photogpslo: number; } export interface NoLocation { type: string; }
Final location.service.ts. Make sure the normalize function matches your scheme properties!
Code Block language js title location.service.ts linenumbers true import {Injectable} from '@angular/core'; import {LocationType} from '../enum/location-type.enum'; import {Observable, of, throwError} from 'rxjs'; import {LocationByAddress, LocationByCoords, NoLocation} from '../interfaces/location'; @Injectable({ providedIn: 'root' }) export class LocationService { private readonly apiKey = `AIzaSyDX8znfh-d4u3spGhC1GvCjq6EA1pjPovQ`; private readonly mapUrl = `https://www.google.com/maps/embed/v1/place`; /** * normalize Address data - map your data based on scheme properties * @param data * @returns */ private normalize(data: any = {}): any { return { streethw: data.strassehw, townhw: data.orthw, countryhw: data.landhw, photogpsla: data.imagegpsla, photogpslo: data.imagegpslo, ...data }; } /** * retrieve Address from data object * @param data * @returns */ private locationbDataWithAddress(data): LocationByAddress | NoLocation { const {streethw, townhw, countryhw} = data; return (townhw && countryhw) ? {type: LocationType.ADDRESS, streethw, townhw, countryhw} : {type: LocationType.EMPTY}; } /** * retrieve Geo Coordinates from data object * @param data * @returns */ private locationbDataWithCoords(data): LocationByCoords | NoLocation { const {photogpsla, photogpslo} = data; return (photogpsla && photogpslo) ? {type: LocationType.COORDS, photogpsla, photogpslo} : {type: LocationType.EMPTY}; } /** * Geo Coordinates are assumed to be only present in photo Objects. * We check the data to either retrieve Geo Coordinates or Address Data. * If neither is available we return empty type. * * @param typeName * @param data * @returns */ locationbData(typeName: string, data: Object): LocationByAddress | LocationByCoords | NoLocation { const normalizedData = this.normalize(data); return normalizedData.photogpsla ? this.locationbDataWithCoords(normalizedData) : this.locationbDataWithAddress(normalizedData); } /** * Depending on the Location type we build a different URL. * If we have no Location type we return an error. * * @param location * @returns */ mapsUrl(location): Observable<string> { let params: string; if (location.type === LocationType.ADDRESS) { const {streethw, townhw, countryhw} = location; params = `${streethw || ''}+${townhw}+${countryhw}`; } else if (location.type === LocationType.COORDS) { const {photogpsla, photogpslo} = location; params = `${photogpsla},${photogpslo}`; } else { return throwError(true) } return of(`${this.mapUrl}?key=${this.apiKey}&q=${params}`); } }
Since we want to hide the map when we don't have any location information, we need to update the map.component.html.
Code Block language xml title map-frame.component.html linenumbers true <iframe class="map" [hidden]="mapAvailable" #mapFrame frameborder="0" allowfullscreen> </iframe> <section class="map__not-available" [hidden]="!mapAvailable"> <eo-icon class="no-file" [iconSrc]="'assets/_default/svg/ic_no-file.svg'"></eo-icon> </section>
As a last step in the TS files we need to update map-frame.component.ts
Code Block language js title map-frame.component.ts linenumbers true import { Component, AfterViewInit, ViewChild, ElementRef, Renderer2 } from '@angular/core'; import {DmsService, DmsObject, EventService, EnaioEvent, Event} from '@eo-sdk/core'; // In the following line ', UnsubscribeOnDestroy' has to be removed when updating to version 9.10 or younger: import {SelectionService, UnsubscribeOnDestroy} from '@eo-sdk/client'; import {takeUntil} from 'rxjs/operators'; import {LocationService} from '../../services/location.service'; @Component({ selector: 'eo-map-frame', templateUrl: './map-frame.component.html', styleUrls: ['./map-frame.component.scss'] }) // In the following line 'extends UnsubscribeOnDestroy' has to be removed when updating to version 9.10 or younger: export class MapFrameComponent extends UnsubscribeOnDestroy implements AfterViewInit { static id = 'eo.custom.plugin.map-frame'; static matchType = new RegExp('object-details-tab.*'); context; mapAvailable; @ViewChild('mapFrame') mapFrame: ElementRef; constructor(private selectionService: SelectionService, private dmsService: DmsService, private renderer: Renderer2, private eventService: EventService, private locationService: LocationService) { super(); } /** * Process dmsObject to get the url for map frame * @param dmsObj */ setupMap(dmsObj: DmsObject) { if (dmsObj) { const {typeName, data} = dmsObj; const location = this.locationService.locationbData(typeName, data); this.locationService .mapsUrl(location) .pipe( takeUntil(this.componentDestroyed$) ) .subscribe(url => { this.mapAvailable = false; this.renderer.setAttribute(this.mapFrame.nativeElement, 'src', url); }, error => this.mapAvailable = error); } } /** * Load & update current context/dmsObject * @param event */ eventHandler(event: Event) { if (event.type === EnaioEvent.DMS_OBJECT_LOADED || (this.context && this.context.id === event.data.id)) { this.context = event.data; this.setupMap(event.data); } } ngAfterViewInit() { this.eventService .on(EnaioEvent.DMS_OBJECT_LOADED, EnaioEvent.DMS_OBJECT_UPDATED) .pipe( takeUntil(this.componentDestroyed$) ) .subscribe(e => this.eventHandler(e)); } }
To finish this plug-in, add some styling. In addition, we are able to display the map frame plugin only per specific types that contain Geo Coordinates or Address Data.
Code Block language css title map-frame.component.scss linenumbers true .map{ width: 100%; height: 90%; &__not-available{ height: 100%; display: flex; justify-content: center; align-items: center; text-transform: capitalize; border: 0; .no-file{ width: 128px; height: 128px; opacity: .06; } } } // Display map frame plugin only per specific types that contain Geo Coordinates or Address Data ::ng-deep { // by default hide map-frame plugin (tab content & tab label) eo-object-details [id="eo.custom.plugin.map-frame"], eo-object-details [id="eo.custom.plugin.map-frame-label"] { display: none; } // display map frame only per specific types eo-object-details[data-type=personalakte] [id="eo.custom.plugin.map-frame"], eo-object-details[data-type=personalakte] [id="eo.custom.plugin.map-frame-label"], eo-object-details[data-type=photobasic] [id="eo.custom.plugin.map-frame"], eo-object-details[data-type=photobasic] [id="eo.custom.plugin.map-frame-label"], eo-object-details[data-type=foto] [id="eo.custom.plugin.map-frame"], eo-object-details[data-type=foto] [id="eo.custom.plugin.map-frame-label"], eo-object-details[data-type=albumphoto] [id="eo.custom.plugin.map-frame"], eo-object-details[data-type=albumphoto] [id="eo.custom.plugin.map-frame-label"] { display: block; } }
...