diff --git a/src/directives-const.ts b/src/directives-const.ts
index df1220acb..6d7543a36 100644
--- a/src/directives-const.ts
+++ b/src/directives-const.ts
@@ -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];
diff --git a/src/directives.ts b/src/directives.ts
index ce0a8e158..0cd668fde 100644
--- a/src/directives.ts
+++ b/src/directives.ts
@@ -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';
diff --git a/src/directives/google-map-info-window.ts b/src/directives/google-map-info-window.ts
new file mode 100644
index 000000000..2e91abfbf
--- /dev/null
+++ b/src/directives/google-map-info-window.ts
@@ -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: `
+ *
+ *
+ *
+ * Hi, this is the content of the info window
+ *
+ *
+ *
+ * `
+ * })
+ * ```
+ */
+@Component({
+ selector: 'sebm-google-map-info-window',
+ inputs: ['latitude', 'longitude', 'disableAutoPan'],
+ template: `
+
+
+
+ `
+})
+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 { return this._infoWindowManager.open(this); }
+
+ /**
+ * Closes the info window.
+ */
+ close(): Promise { 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); }
+}
diff --git a/src/directives/google-map-marker.ts b/src/directives/google-map-marker.ts
index d67eb4f74..2214ed72b 100644
--- a/src/directives/google-map-marker.ts
+++ b/src/directives/google-map-marker.ts
@@ -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';
@@ -36,7 +45,7 @@ let markerId = 0;
outputs: ['markerClick', 'dragEnd']
})
export class SebmGoogleMapMarker implements OnDestroy,
- OnChanges {
+ OnChanges, AfterContentInit {
/**
* The latitude position of the marker.
*/
@@ -77,11 +86,20 @@ export class SebmGoogleMapMarker implements OnDestroy,
*/
dragEnd: EventEmitter = new EventEmitter();
+ @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') {
@@ -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('dragend', this)
diff --git a/src/directives/google-map.ts b/src/directives/google-map.ts
index 7bd489315..10309cfd8 100644
--- a/src/directives/google-map.ts
+++ b/src/directives/google-map.ts
@@ -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';
@@ -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'
@@ -43,10 +44,15 @@ import {MouseEvent} from '../events';
width: inherit;
height: inherit;
}
+ .sebm-google-map-content {
+ display:none;
+ }
`],
template: `
-
+
+
+
`
})
export class SebmGoogleMap implements OnChanges,
diff --git a/src/services/google-maps-api-wrapper.ts b/src/services/google-maps-api-wrapper.ts
index cdaee1d5a..6e54b4d6d 100644
--- a/src/services/google-maps-api-wrapper.ts
+++ b/src/services/google-maps-api-wrapper.ts
@@ -45,6 +45,10 @@ export class GoogleMapsAPIWrapper {
});
}
+ createInfoWindow(options?: mapTypes.InfoWindowOptions): Promise {
+ return this._map.then(() => { return new google.maps.InfoWindow(options); });
+ }
+
subscribeToMapEvent(eventName: string): Observable {
return Observable.create((observer: Observer) => {
this._map.then((m: mapTypes.GoogleMap) => {
diff --git a/src/services/google-maps-types.ts b/src/services/google-maps-types.ts
index cdaba284a..d7bcc4574 100644
--- a/src/services/google-maps-types.ts
+++ b/src/services/google-maps-types.ts
@@ -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;
+}
diff --git a/src/services/info-window-manager.ts b/src/services/info-window-manager.ts
new file mode 100644
index 000000000..3f6438ae8
--- /dev/null
+++ b/src/services/info-window-manager.ts
@@ -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> =
+ new Map>();
+
+ constructor(
+ private _mapsWrapper: GoogleMapsAPIWrapper, private _zone: NgZone,
+ private _markerManager: MarkerManager) {}
+
+ deleteInfoWindow(infoWindow: SebmGoogleMapInfoWindow): Promise {
+ 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 {
+ return this._infoWindows.get(infoWindow).then((i: InfoWindow) => i.setPosition({
+ lat: infoWindow.latitude,
+ lng: infoWindow.longitude
+ }));
+ }
+
+ setZIndex(infoWindow: SebmGoogleMapInfoWindow): Promise {
+ return this._infoWindows.get(infoWindow)
+ .then((i: InfoWindow) => i.setZIndex(infoWindow.zIndex));
+ }
+
+ open(infoWindow: SebmGoogleMapInfoWindow): Promise {
+ 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 {
+ 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);
+ }
+}
diff --git a/src/services/marker-manager.ts b/src/services/marker-manager.ts
index 5e46ebec5..6a087e3c0 100644
--- a/src/services/marker-manager.ts
+++ b/src/services/marker-manager.ts
@@ -57,6 +57,10 @@ export class MarkerManager {
this._markers.set(marker, markerPromise);
}
+ getNativeMarker(marker: SebmGoogleMapMarker): Promise {
+ return this._markers.get(marker);
+ }
+
createEventObservable(eventName: string, marker: SebmGoogleMapMarker): Observable {
return Observable.create((observer: Observer) => {
this._markers.get(marker).then((m: Marker) => {