Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents
include^Example.*

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.

Image Modified


Implementation

  1. Use a CLI command to generate a plug-in component with name map.

    Code Block
    languagebash
    linenumberstrue
    eo g plugin map


  2. Use a CLI command to install the maps package (takes some time).

    Code Block
    languagebash
    linenumberstrue
    npm install -P @agm/core


  3. Import AgmCoreModule module to CustomPluginsModule.

    Code Block
    languagejs
    titlecustom-plugins.module.ts
    linenumberstrue
    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 {
    }
    
    


  4. Update map.component.ts to load the current position from navigator, and modify the matchType property if necessary.

    Code Block
    languagejs
    titlemap.component.ts
    linenumberstrue
    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();
      }
    
    }
    
    


  5. Update map.component.html to include a map component with the latitude and longitude based on the current position.

    Code Block
    languagexml
    titlemap.component.html
    linenumberstrue
    <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>


  6. To make it look nice, we add some scss

    Code Block
    languagecss
    titlemap.component.scss
    linenumberstrue
    .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;
      }
    }


  7. Use a CLI command to generate labels/translations

    Code Block
    languagebash
    linenumberstrue
    eo g label eo.custom.plugin.map --en Map --de Stadtplan


    Code Block
    languagebash
    linenumberstrue
    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)"


...

  1. We start by extending the "MAP via iframe + core service"
  2. For readability, we move most of our functionality to a service

    Code Block
    languagebash
    titleTerminal
    ng g s eo-custom-client/services/location --spec false


  3. To keep the code organized, we introduce LocationType enum.

    Code Block
    languagebash
    titleTerminal
    ng g enum eo-custom-client/enum/LocationType


  4. Update locationType.ts

    Code Block
    languagejs
    titlelocationTypes.rs
    linenumberstrue
    export enum LocationType {
      COORDS = 'coords',
      ADDRESS = 'address',
      EMPTY = 'empty'
    }


  5. To keep the code organized, we introduce an Interfaces for the Locations.

    Code Block
    languagebash
    titleTerminal
    ng g interface eo-custom-client/interfaces/Location


  6. Update location.interface.ts

    Code Block
    languagejs
    titlelocation.interface.ts
    linenumberstrue
    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;
    }


  7. Final location.service.ts. Make sure the normalize function matches your scheme properties!

    Code Block
    languagejs
    titlelocation.service.ts
    linenumberstrue
    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}`);
      }
    }
    
    


  8. 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
    languagexml
    titlemap-frame.component.html
    linenumberstrue
    <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>


  9. As a last step in the TS files we need to update map-frame.component.ts

    Code Block
    languagejs
    titlemap-frame.component.ts
    linenumberstrue
    import {
      Component,
      AfterViewInit,
      ViewChild,
      ElementRef,
      Renderer2
    } from '@angular/core';
    import {DmsService, DmsObject, EventService, EnaioEvent, Event} from '@eo-sdk/core';
    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']
    })
    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));
      }
    }
    
    


  10. To finish this plug-in, add some styling.  Plus In addition, we are able to display the map - frame plugin only per specific types that contains that contain Geo Coordinates or Address Data. 

    Code Block
    languagecss
    titlemap-frame.component.scss
    linenumberstrue
    .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 containscontain 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;
      }
    }
    


...