Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit a3df794

Browse files
committed
feat(SebmGoogleMapInfoWindow): Basic support
Closes #150 Closes #238
1 parent a5b909a commit a3df794

9 files changed

+293
-5
lines changed

src/directives-const.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {SebmGoogleMap} from './directives/google-map';
22
import {SebmGoogleMapMarker} from './directives/google-map-marker';
3+
import {SebmGoogleMapInfoWindow} from './directives/google-map-info-window';
34

4-
export const ANGULAR2_GOOGLE_MAPS_DIRECTIVES: any[] = [SebmGoogleMap, SebmGoogleMapMarker];
5+
export const ANGULAR2_GOOGLE_MAPS_DIRECTIVES: any[] =
6+
[SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow];

src/directives.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export {SebmGoogleMap} from './directives/google-map';
22
export {SebmGoogleMapMarker} from './directives/google-map-marker';
3+
export {SebmGoogleMapInfoWindow} from './directives/google-map-info-window';
34
export {ANGULAR2_GOOGLE_MAPS_DIRECTIVES} from './directives-const';
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import {Component, SimpleChange, OnDestroy, OnChanges, ElementRef} from 'angular2/core';
2+
import {InfoWindowManager} from '../services/info-window-manager';
3+
import {SebmGoogleMapMarker} from './google-map-marker';
4+
5+
let infoWindowId = 0;
6+
7+
/**
8+
* SebmGoogleMapInfoWindow renders a info window inside a {@link SebmGoogleMapMarker} or standalone.
9+
*
10+
* ### Example
11+
* ```typescript
12+
* import {Component} from 'angular2/core';
13+
* import {SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow} from
14+
* 'angular2-google-maps/core';
15+
*
16+
* @Component({
17+
* selector: 'my-map-cmp',
18+
* directives: [SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow],
19+
* styles: [`
20+
* .sebm-google-map-container {
21+
* height: 300px;
22+
* }
23+
* `],
24+
* template: `
25+
* <sebm-google-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
26+
* <sebm-google-map-marker [latitude]="lat" [longitude]="lng" [label]="'M'">
27+
* <sebm-google-map-info-window [disableAutoPan]="true">
28+
* Hi, this is the content of the <strong>info window</strong>
29+
* </sebm-google-map-info-window>
30+
* </sebm-google-map-marker>
31+
* </sebm-google-map>
32+
* `
33+
* })
34+
* ```
35+
*/
36+
@Component({
37+
selector: 'sebm-google-map-info-window',
38+
inputs: ['latitude', 'longitude', 'disableAutoPan'],
39+
template: `
40+
<div class='sebm-google-map-info-window-content'>
41+
<ng-content></ng-content>
42+
</div>
43+
`
44+
})
45+
export class SebmGoogleMapInfoWindow implements OnDestroy,
46+
OnChanges {
47+
/**
48+
* The latitude position of the info window (only usefull if you use it ouside of a {@link
49+
* SebmGoogleMapMarker}).
50+
*/
51+
latitude: number;
52+
53+
/**
54+
* The longitude position of the info window (only usefull if you use it ouside of a {@link
55+
* SebmGoogleMapMarker}).
56+
*/
57+
longitude: number;
58+
59+
/**
60+
* Disable auto-pan on open. By default, the info window will pan the map so that it is fully
61+
* visible when it opens.
62+
*/
63+
disableAutoPan: boolean;
64+
65+
/**
66+
* All InfoWindows are displayed on the map in order of their zIndex, with higher values
67+
* displaying in front of InfoWindows with lower values. By default, InfoWindows are displayed
68+
* according to their latitude, with InfoWindows of lower latitudes appearing in front of
69+
* InfoWindows at higher latitudes. InfoWindows are always displayed in front of markers.
70+
*/
71+
zIndex: number;
72+
73+
/**
74+
* Maximum width of the infowindow, regardless of content's width. This value is only considered
75+
* if it is set before a call to open. To change the maximum width when changing content, call
76+
* close, update maxWidth, and then open.
77+
*/
78+
maxWidth: number;
79+
80+
/**
81+
* Holds the marker that is the host of the info window (if available)
82+
*/
83+
hostMarker: SebmGoogleMapMarker;
84+
85+
/**
86+
* Holds the native element that is used for the info window content.
87+
*/
88+
content: Node;
89+
90+
private static _infoWindowOptionsInputs: string[] = ['disableAutoPan', 'maxWidth'];
91+
private _infoWindowAddedToManager: boolean = false;
92+
private _id: string = (infoWindowId++).toString();
93+
94+
constructor(private _infoWindowManager: InfoWindowManager, private _el: ElementRef) {}
95+
96+
ngOnInit() {
97+
this.content = this._el.nativeElement.querySelector('.sebm-google-map-info-window-content');
98+
this._addToManager();
99+
}
100+
101+
/** @internal */
102+
ngOnChanges(changes: {[key: string]: SimpleChange}) {
103+
this._addToManager();
104+
if ((changes['latitude'] || changes['longitude']) && typeof this.latitude === 'number' &&
105+
typeof this.longitude === 'number') {
106+
this._infoWindowManager.setPosition(this);
107+
}
108+
if (changes['zIndex']) {
109+
this._infoWindowManager.setZIndex(this);
110+
}
111+
this._setInfoWindowOptions(changes);
112+
}
113+
114+
private _setInfoWindowOptions(changes: {[key: string]: SimpleChange}) {
115+
let options: {[propName: string]: any} = {};
116+
let optionKeys = Object.keys(changes).filter(
117+
k => SebmGoogleMapInfoWindow._infoWindowOptionsInputs.indexOf(k) !== -1);
118+
optionKeys.forEach((k) => { options[k] = changes[k].currentValue; });
119+
this._infoWindowManager.setOptions(this, options);
120+
}
121+
122+
private _addToManager() {
123+
if (!this._infoWindowAddedToManager) {
124+
this._infoWindowAddedToManager = true;
125+
this._infoWindowManager.addInfoWindow(this);
126+
}
127+
}
128+
129+
/**
130+
* Opens the info window.
131+
*/
132+
open(): Promise<void> { return this._infoWindowManager.open(this); }
133+
134+
/**
135+
* Closes the info window.
136+
*/
137+
close(): Promise<void> { return this._infoWindowManager.close(this); }
138+
139+
/** @internal */
140+
id(): string { return this._id; }
141+
142+
/** @internal */
143+
toString(): string { return 'SebmGoogleMapInfoWindow-' + this._id.toString(); }
144+
145+
/** @internal */
146+
ngOnDestroy() { this._infoWindowManager.deleteInfoWindow(this); }
147+
}

src/directives/google-map-marker.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1-
import {Directive, SimpleChange, OnDestroy, OnChanges, EventEmitter} from 'angular2/core';
1+
import {
2+
Directive,
3+
SimpleChange,
4+
OnDestroy,
5+
OnChanges,
6+
EventEmitter,
7+
ContentChild,
8+
AfterContentInit
9+
} from 'angular2/core';
210
import {MarkerManager} from '../services/marker-manager';
11+
import {SebmGoogleMapInfoWindow} from './google-map-info-window';
312
import {MouseEvent} from '../events';
413
import * as mapTypes from '../services/google-maps-types';
514

@@ -36,7 +45,7 @@ let markerId = 0;
3645
outputs: ['markerClick', 'dragEnd']
3746
})
3847
export class SebmGoogleMapMarker implements OnDestroy,
39-
OnChanges {
48+
OnChanges, AfterContentInit {
4049
/**
4150
* The latitude position of the marker.
4251
*/
@@ -77,11 +86,20 @@ export class SebmGoogleMapMarker implements OnDestroy,
7786
*/
7887
dragEnd: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
7988

89+
@ContentChild(SebmGoogleMapInfoWindow) private _infoWindow: SebmGoogleMapInfoWindow;
90+
8091
private _markerAddedToManger: boolean = false;
8192
private _id: string;
8293

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

96+
/* @internal */
97+
ngAfterContentInit() {
98+
if (this._infoWindow != null) {
99+
this._infoWindow.hostMarker = this;
100+
}
101+
}
102+
85103
/** @internal */
86104
ngOnChanges(changes: {[key: string]: SimpleChange}) {
87105
if (typeof this.latitude !== 'number' || typeof this.longitude !== 'number') {
@@ -112,6 +130,9 @@ export class SebmGoogleMapMarker implements OnDestroy,
112130

113131
private _addEventListeners() {
114132
this._markerManager.createEventObservable('click', this).subscribe(() => {
133+
if (this._infoWindow != null) {
134+
this._infoWindow.open();
135+
}
115136
this.markerClick.next(null);
116137
});
117138
this._markerManager.createEventObservable<mapTypes.MouseEvent>('dragend', this)

src/directives/google-map.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Component, ElementRef, EventEmitter, OnChanges, OnInit, SimpleChange} from 'angular2/core';
22
import {GoogleMapsAPIWrapper} from '../services/google-maps-api-wrapper';
33
import {MarkerManager} from '../services/marker-manager';
4+
import {InfoWindowManager} from '../services/info-window-manager';
45
import {LatLng, LatLngLiteral} from '../services/google-maps-types';
56
import {MouseEvent} from '../events';
67

@@ -31,7 +32,7 @@ import {MouseEvent} from '../events';
3132
*/
3233
@Component({
3334
selector: 'sebm-google-map',
34-
providers: [GoogleMapsAPIWrapper, MarkerManager],
35+
providers: [GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager],
3536
inputs: [
3637
'longitude', 'latitude', 'zoom', 'disableDoubleClickZoom', 'disableDefaultUI', 'scrollwheel',
3738
'backgroundColor', 'draggableCursor', 'draggingCursor', 'keyboardShortcuts', 'zoomControl'
@@ -43,10 +44,15 @@ import {MouseEvent} from '../events';
4344
width: inherit;
4445
height: inherit;
4546
}
47+
.sebm-google-map-content {
48+
display:none;
49+
}
4650
`],
4751
template: `
4852
<div class='sebm-google-map-container-inner'></div>
49-
<ng-content></ng-content>
53+
<div class='sebm-google-map-content'>
54+
<ng-content></ng-content>
55+
</div>
5056
`
5157
})
5258
export class SebmGoogleMap implements OnChanges,

src/services/google-maps-api-wrapper.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export class GoogleMapsAPIWrapper {
4545
});
4646
}
4747

48+
createInfoWindow(options?: mapTypes.InfoWindowOptions): Promise<mapTypes.InfoWindow> {
49+
return this._map.then(() => { return new google.maps.InfoWindow(options); });
50+
}
51+
4852
subscribeToMapEvent<E>(eventName: string): Observable<E> {
4953
return Observable.create((observer: Observer<E>) => {
5054
this._map.then((m: mapTypes.GoogleMap) => {

src/services/google-maps-types.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,35 @@ export interface MapOptions {
6464
keyboardShortcuts?: boolean;
6565
zoomControl?: boolean;
6666
}
67+
68+
export interface InfoWindow {
69+
constructor(opts?: InfoWindowOptions): void;
70+
close(): void;
71+
getContent(): string | Node;
72+
getPosition(): LatLng;
73+
getZIndex(): number;
74+
open(map?: GoogleMap, anchor?: MVCObject): void;
75+
setContent(content: string | Node): void;
76+
setOptions(options: InfoWindowOptions): void;
77+
setPosition(position: LatLng | LatLngLiteral): void;
78+
setZIndex(zIndex: number): void;
79+
}
80+
81+
export interface MVCObject { constructor(): void; }
82+
83+
export interface Size {
84+
height: number;
85+
width: number;
86+
constructor(width: number, height: number, widthUnit?: string, heightUnit?: string): void;
87+
equals(other: Size): boolean;
88+
toString(): string;
89+
}
90+
91+
export interface InfoWindowOptions {
92+
content?: string | Node;
93+
disableAutoPan?: boolean;
94+
maxWidth?: number;
95+
pixelOffset?: Size;
96+
position?: LatLng | LatLngLiteral;
97+
zIndex?: number;
98+
}

src/services/info-window-manager.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {Injectable, NgZone} from 'angular2/core';
2+
import {SebmGoogleMapInfoWindow} from '../directives/google-map-info-window';
3+
import {GoogleMapsAPIWrapper} from './google-maps-api-wrapper';
4+
import {MarkerManager} from './marker-manager';
5+
import {InfoWindow, InfoWindowOptions} from './google-maps-types';
6+
7+
@Injectable()
8+
export class InfoWindowManager {
9+
private _infoWindows: Map<SebmGoogleMapInfoWindow, Promise<InfoWindow>> =
10+
new Map<SebmGoogleMapInfoWindow, Promise<InfoWindow>>();
11+
12+
constructor(
13+
private _mapsWrapper: GoogleMapsAPIWrapper, private _zone: NgZone,
14+
private _markerManager: MarkerManager) {}
15+
16+
deleteInfoWindow(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
17+
const iWindow = this._infoWindows.get(infoWindow);
18+
if (iWindow == null) {
19+
// info window already deleted
20+
return Promise.resolve();
21+
}
22+
return iWindow.then((i: InfoWindow) => {
23+
return this._zone.run(() => {
24+
i.close();
25+
this._infoWindows.delete(infoWindow);
26+
});
27+
});
28+
}
29+
30+
setPosition(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
31+
return this._infoWindows.get(infoWindow).then((i: InfoWindow) => i.setPosition({
32+
lat: infoWindow.latitude,
33+
lng: infoWindow.longitude
34+
}));
35+
}
36+
37+
setZIndex(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
38+
return this._infoWindows.get(infoWindow)
39+
.then((i: InfoWindow) => i.setZIndex(infoWindow.zIndex));
40+
}
41+
42+
open(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
43+
return this._infoWindows.get(infoWindow).then((w) => {
44+
if (infoWindow.hostMarker != null) {
45+
return this._markerManager.getNativeMarker(infoWindow.hostMarker).then((marker) => {
46+
return this._mapsWrapper.getMap().then((map) => w.open(map, marker));
47+
});
48+
}
49+
return this._mapsWrapper.getMap().then((map) => w.open(map));
50+
});
51+
}
52+
53+
close(infoWindow: SebmGoogleMapInfoWindow): Promise<void> {
54+
return this._infoWindows.get(infoWindow).then((w) => w.close());
55+
}
56+
57+
setOptions(infoWindow: SebmGoogleMapInfoWindow, options: InfoWindowOptions) {
58+
return this._infoWindows.get(infoWindow).then((i: InfoWindow) => i.setOptions(options));
59+
}
60+
61+
addInfoWindow(infoWindow: SebmGoogleMapInfoWindow) {
62+
const options: InfoWindowOptions = {
63+
content: infoWindow.content,
64+
};
65+
if (typeof infoWindow.latitude === 'number' && typeof infoWindow.longitude === 'number') {
66+
options.position = {lat: infoWindow.latitude, lng: infoWindow.longitude};
67+
}
68+
const infoWindowPromise = this._mapsWrapper.createInfoWindow(options);
69+
this._infoWindows.set(infoWindow, infoWindowPromise);
70+
}
71+
}

src/services/marker-manager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export class MarkerManager {
5757
this._markers.set(marker, markerPromise);
5858
}
5959

60+
getNativeMarker(marker: SebmGoogleMapMarker): Promise<Marker> {
61+
return this._markers.get(marker);
62+
}
63+
6064
createEventObservable<T>(eventName: string, marker: SebmGoogleMapMarker): Observable<T> {
6165
return Observable.create((observer: Observer<T>) => {
6266
this._markers.get(marker).then((m: Marker) => {

0 commit comments

Comments
 (0)