Skip to content

Commit

Permalink
feat(SebmGoogleMapInfoWindow): Basic support
Browse files Browse the repository at this point in the history
Closes #150
Closes #238
  • Loading branch information
sebholstein committed Mar 22, 2016
1 parent a5b909a commit a3df794
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 5 deletions.
4 changes: 3 additions & 1 deletion src/directives-const.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {SebmGoogleMap} from './directives/google-map';
import {SebmGoogleMapMarker} from './directives/google-map-marker';
import {SebmGoogleMapInfoWindow} from './directives/google-map-info-window';

export const ANGULAR2_GOOGLE_MAPS_DIRECTIVES: any[] = [SebmGoogleMap, SebmGoogleMapMarker];
export const ANGULAR2_GOOGLE_MAPS_DIRECTIVES: any[] =
[SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow];
1 change: 1 addition & 0 deletions src/directives.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export {SebmGoogleMap} from './directives/google-map';
export {SebmGoogleMapMarker} from './directives/google-map-marker';
export {SebmGoogleMapInfoWindow} from './directives/google-map-info-window';
export {ANGULAR2_GOOGLE_MAPS_DIRECTIVES} from './directives-const';
147 changes: 147 additions & 0 deletions src/directives/google-map-info-window.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {Component, SimpleChange, OnDestroy, OnChanges, ElementRef} from 'angular2/core';
import {InfoWindowManager} from '../services/info-window-manager';
import {SebmGoogleMapMarker} from './google-map-marker';

let infoWindowId = 0;

/**
* SebmGoogleMapInfoWindow renders a info window inside a {@link SebmGoogleMapMarker} or standalone.
*
* ### Example
* ```typescript
* import {Component} from 'angular2/core';
* import {SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow} from
* 'angular2-google-maps/core';
*
* @Component({
* selector: 'my-map-cmp',
* directives: [SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow],
* styles: [`
* .sebm-google-map-container {
* height: 300px;
* }
* `],
* template: `
* <sebm-google-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
* <sebm-google-map-marker [latitude]="lat" [longitude]="lng" [label]="'M'">
* <sebm-google-map-info-window [disableAutoPan]="true">
* Hi, this is the content of the <strong>info window</strong>
* </sebm-google-map-info-window>
* </sebm-google-map-marker>
* </sebm-google-map>
* `
* })
* ```
*/
@Component({
selector: 'sebm-google-map-info-window',
inputs: ['latitude', 'longitude', 'disableAutoPan'],
template: `
<div class='sebm-google-map-info-window-content'>
<ng-content></ng-content>
</div>
`
})
export class SebmGoogleMapInfoWindow implements OnDestroy,
OnChanges {
/**
* The latitude position of the info window (only usefull if you use it ouside of a {@link
* SebmGoogleMapMarker}).
*/
latitude: number;

/**
* The longitude position of the info window (only usefull if you use it ouside of a {@link
* SebmGoogleMapMarker}).
*/
longitude: number;

/**
* Disable auto-pan on open. By default, the info window will pan the map so that it is fully
* visible when it opens.
*/
disableAutoPan: boolean;

/**
* All InfoWindows are displayed on the map in order of their zIndex, with higher values
* displaying in front of InfoWindows with lower values. By default, InfoWindows are displayed
* according to their latitude, with InfoWindows of lower latitudes appearing in front of
* InfoWindows at higher latitudes. InfoWindows are always displayed in front of markers.
*/
zIndex: number;

/**
* Maximum width of the infowindow, regardless of content's width. This value is only considered
* if it is set before a call to open. To change the maximum width when changing content, call
* close, update maxWidth, and then open.
*/
maxWidth: number;

/**
* Holds the marker that is the host of the info window (if available)
*/
hostMarker: SebmGoogleMapMarker;

/**
* Holds the native element that is used for the info window content.
*/
content: Node;

private static _infoWindowOptionsInputs: string[] = ['disableAutoPan', 'maxWidth'];
private _infoWindowAddedToManager: boolean = false;
private _id: string = (infoWindowId++).toString();

constructor(private _infoWindowManager: InfoWindowManager, private _el: ElementRef) {}

ngOnInit() {
this.content = this._el.nativeElement.querySelector('.sebm-google-map-info-window-content');
this._addToManager();
}

/** @internal */
ngOnChanges(changes: {[key: string]: SimpleChange}) {
this._addToManager();
if ((changes['latitude'] || changes['longitude']) && typeof this.latitude === 'number' &&
typeof this.longitude === 'number') {
this._infoWindowManager.setPosition(this);
}
if (changes['zIndex']) {
this._infoWindowManager.setZIndex(this);
}
this._setInfoWindowOptions(changes);
}

private _setInfoWindowOptions(changes: {[key: string]: SimpleChange}) {
let options: {[propName: string]: any} = {};
let optionKeys = Object.keys(changes).filter(
k => SebmGoogleMapInfoWindow._infoWindowOptionsInputs.indexOf(k) !== -1);
optionKeys.forEach((k) => { options[k] = changes[k].currentValue; });
this._infoWindowManager.setOptions(this, options);
}

private _addToManager() {
if (!this._infoWindowAddedToManager) {
this._infoWindowAddedToManager = true;
this._infoWindowManager.addInfoWindow(this);
}
}

/**
* Opens the info window.
*/
open(): Promise<void> { return this._infoWindowManager.open(this); }

/**
* Closes the info window.
*/
close(): Promise<void> { return this._infoWindowManager.close(this); }

/** @internal */
id(): string { return this._id; }

/** @internal */
toString(): string { return 'SebmGoogleMapInfoWindow-' + this._id.toString(); }

/** @internal */
ngOnDestroy() { this._infoWindowManager.deleteInfoWindow(this); }
}
25 changes: 23 additions & 2 deletions src/directives/google-map-marker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import {Directive, SimpleChange, OnDestroy, OnChanges, EventEmitter} from 'angular2/core';
import {
Directive,
SimpleChange,
OnDestroy,
OnChanges,
EventEmitter,
ContentChild,
AfterContentInit
} from 'angular2/core';
import {MarkerManager} from '../services/marker-manager';
import {SebmGoogleMapInfoWindow} from './google-map-info-window';
import {MouseEvent} from '../events';
import * as mapTypes from '../services/google-maps-types';

Expand Down Expand Up @@ -36,7 +45,7 @@ let markerId = 0;
outputs: ['markerClick', 'dragEnd']
})
export class SebmGoogleMapMarker implements OnDestroy,
OnChanges {
OnChanges, AfterContentInit {
/**
* The latitude position of the marker.
*/
Expand Down Expand Up @@ -77,11 +86,20 @@ export class SebmGoogleMapMarker implements OnDestroy,
*/
dragEnd: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();

@ContentChild(SebmGoogleMapInfoWindow) private _infoWindow: SebmGoogleMapInfoWindow;

private _markerAddedToManger: boolean = false;
private _id: string;

constructor(private _markerManager: MarkerManager) { this._id = (markerId++).toString(); }

/* @internal */
ngAfterContentInit() {
if (this._infoWindow != null) {
this._infoWindow.hostMarker = this;
}
}

/** @internal */
ngOnChanges(changes: {[key: string]: SimpleChange}) {
if (typeof this.latitude !== 'number' || typeof this.longitude !== 'number') {
Expand Down Expand Up @@ -112,6 +130,9 @@ export class SebmGoogleMapMarker implements OnDestroy,

private _addEventListeners() {
this._markerManager.createEventObservable('click', this).subscribe(() => {
if (this._infoWindow != null) {
this._infoWindow.open();
}
this.markerClick.next(null);
});
this._markerManager.createEventObservable<mapTypes.MouseEvent>('dragend', this)
Expand Down
10 changes: 8 additions & 2 deletions src/directives/google-map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Component, ElementRef, EventEmitter, OnChanges, OnInit, SimpleChange} from 'angular2/core';
import {GoogleMapsAPIWrapper} from '../services/google-maps-api-wrapper';
import {MarkerManager} from '../services/marker-manager';
import {InfoWindowManager} from '../services/info-window-manager';
import {LatLng, LatLngLiteral} from '../services/google-maps-types';
import {MouseEvent} from '../events';

Expand Down Expand Up @@ -31,7 +32,7 @@ import {MouseEvent} from '../events';
*/
@Component({
selector: 'sebm-google-map',
providers: [GoogleMapsAPIWrapper, MarkerManager],
providers: [GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager],
inputs: [
'longitude', 'latitude', 'zoom', 'disableDoubleClickZoom', 'disableDefaultUI', 'scrollwheel',
'backgroundColor', 'draggableCursor', 'draggingCursor', 'keyboardShortcuts', 'zoomControl'
Expand All @@ -43,10 +44,15 @@ import {MouseEvent} from '../events';
width: inherit;
height: inherit;
}
.sebm-google-map-content {
display:none;
}
`],
template: `
<div class='sebm-google-map-container-inner'></div>
<ng-content></ng-content>
<div class='sebm-google-map-content'>
<ng-content></ng-content>
</div>
`
})
export class SebmGoogleMap implements OnChanges,
Expand Down
4 changes: 4 additions & 0 deletions src/services/google-maps-api-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export class GoogleMapsAPIWrapper {
});
}

createInfoWindow(options?: mapTypes.InfoWindowOptions): Promise<mapTypes.InfoWindow> {
return this._map.then(() => { return new google.maps.InfoWindow(options); });
}

subscribeToMapEvent<E>(eventName: string): Observable<E> {
return Observable.create((observer: Observer<E>) => {
this._map.then((m: mapTypes.GoogleMap) => {
Expand Down
32 changes: 32 additions & 0 deletions src/services/google-maps-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,35 @@ export interface MapOptions {
keyboardShortcuts?: boolean;
zoomControl?: boolean;
}

export interface InfoWindow {
constructor(opts?: InfoWindowOptions): void;
close(): void;
getContent(): string | Node;
getPosition(): LatLng;
getZIndex(): number;
open(map?: GoogleMap, anchor?: MVCObject): void;
setContent(content: string | Node): void;
setOptions(options: InfoWindowOptions): void;
setPosition(position: LatLng | LatLngLiteral): void;
setZIndex(zIndex: number): void;
}

export interface MVCObject { constructor(): void; }

export interface Size {
height: number;
width: number;
constructor(width: number, height: number, widthUnit?: string, heightUnit?: string): void;
equals(other: Size): boolean;
toString(): string;
}

export interface InfoWindowOptions {
content?: string | Node;
disableAutoPan?: boolean;
maxWidth?: number;
pixelOffset?: Size;
position?: LatLng | LatLngLiteral;
zIndex?: number;
}
71 changes: 71 additions & 0 deletions src/services/info-window-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {Injectable, NgZone} from 'angular2/core';
import {SebmGoogleMapInfoWindow} from '../directives/google-map-info-window';
import {GoogleMapsAPIWrapper} from './google-maps-api-wrapper';
import {MarkerManager} from './marker-manager';
import {InfoWindow, InfoWindowOptions} from './google-maps-types';

@Injectable()
export class InfoWindowManager {
private _infoWindows: Map<SebmGoogleMapInfoWindow, Promise<InfoWindow>> =
new Map<SebmGoogleMapInfoWindow, Promise<InfoWindow>>();

constructor(
private _mapsWrapper: GoogleMapsAPIWrapper, private _zone: NgZone,
private _markerManager: MarkerManager) {}

deleteInfoWindow(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
const iWindow = this._infoWindows.get(infoWindow);
if (iWindow == null) {
// info window already deleted
return Promise.resolve();
}
return iWindow.then((i: InfoWindow) => {
return this._zone.run(() => {
i.close();
this._infoWindows.delete(infoWindow);
});
});
}

setPosition(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
return this._infoWindows.get(infoWindow).then((i: InfoWindow) => i.setPosition({
lat: infoWindow.latitude,
lng: infoWindow.longitude
}));
}

setZIndex(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
return this._infoWindows.get(infoWindow)
.then((i: InfoWindow) => i.setZIndex(infoWindow.zIndex));
}

open(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
return this._infoWindows.get(infoWindow).then((w) => {
if (infoWindow.hostMarker != null) {
return this._markerManager.getNativeMarker(infoWindow.hostMarker).then((marker) => {
return this._mapsWrapper.getMap().then((map) => w.open(map, marker));
});
}
return this._mapsWrapper.getMap().then((map) => w.open(map));
});
}

close(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
return this._infoWindows.get(infoWindow).then((w) => w.close());
}

setOptions(infoWindow: SebmGoogleMapInfoWindow, options: InfoWindowOptions) {
return this._infoWindows.get(infoWindow).then((i: InfoWindow) => i.setOptions(options));
}

addInfoWindow(infoWindow: SebmGoogleMapInfoWindow) {
const options: InfoWindowOptions = {
content: infoWindow.content,
};
if (typeof infoWindow.latitude === 'number' && typeof infoWindow.longitude === 'number') {
options.position = {lat: infoWindow.latitude, lng: infoWindow.longitude};
}
const infoWindowPromise = this._mapsWrapper.createInfoWindow(options);
this._infoWindows.set(infoWindow, infoWindowPromise);
}
}
4 changes: 4 additions & 0 deletions src/services/marker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export class MarkerManager {
this._markers.set(marker, markerPromise);
}

getNativeMarker(marker: SebmGoogleMapMarker): Promise<Marker> {
return this._markers.get(marker);
}

createEventObservable<T>(eventName: string, marker: SebmGoogleMapMarker): Observable<T> {
return Observable.create((observer: Observer<T>) => {
this._markers.get(marker).then((m: Marker) => {
Expand Down

0 comments on commit a3df794

Please sign in to comment.