/**
* ng2-gm - Angular 2 components for Google Maps
* @version v0.0.2
* @link https://github.com/williampaulo/angular2-google-maps#readme
* @license MIT
*/
import {Component, ElementRef, EventEmitter, OnChanges, OnInit, SimpleChange} from '@angular/core';
import {MouseEvent} from '../events';
import {GoogleMapsAPIWrapper} from '../services/google-maps-api-wrapper';
import {LatLng, LatLngLiteral} from '../services/google-maps-types';
import {InfoWindowManager} from '../services/info-window-manager';
import {MarkerManager} from '../services/marker-manager';
/**
* SebMGoogleMap renders a Google Map.
* **Important note**: To be able see a map in the browser, you have to define a height for the CSS
* class `sebm-google-map-container`.
*
* ### Example
* ```typescript
* import {Component} from 'angular2/core';
* import {SebmGoogleMap} from 'angular2-google-maps/core';
*
* @Component({
* selector: 'my-map-cmp',
* directives: [SebmGoogleMap],
* styles: [`
* .sebm-google-map-container {
* height: 300px;
* }
* `],
* template: `
*
*
* `
* })
* ```
*/
@Component({
selector: 'sebm-google-map',
providers: [GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager],
inputs: [
'longitude', 'latitude', 'zoom', 'disableDoubleClickZoom', 'disableDefaultUI', 'scrollwheel',
'backgroundColor', 'draggableCursor', 'draggingCursor', 'keyboardShortcuts', 'zoomControl'
],
outputs: ['mapClick', 'mapRightClick', 'mapDblClick', 'centerChange'],
host: {'[class.sebm-google-map-container]': 'true'},
styles: [`
.sebm-google-map-container-inner {
width: inherit;
height: inherit;
}
.sebm-google-map-content {
display:none;
}
`],
template: `
`
})
export class SebmGoogleMap implements OnChanges,
OnInit {
private _longitude: number = 0;
private _latitude: number = 0;
private _zoom: number = 8;
/**
* Enables/disables zoom and center on double click. Enabled by default.
*/
disableDoubleClickZoom: boolean = false;
/**
* Enables/disables all default UI of the Google map. Please note: When the map is created, this
* value cannot get updated.
*/
disableDefaultUI: boolean = false;
/**
* If false, disables scrollwheel zooming on the map. The scrollwheel is enabled by default.
*/
scrollwheel: boolean = true;
/**
* Color used for the background of the Map div. This color will be visible when tiles have not
* yet loaded as the user pans. This option can only be set when the map is initialized.
*/
backgroundColor: string;
/**
* The name or url of the cursor to display when mousing over a draggable map. This property uses
* the css * cursor attribute to change the icon. As with the css property, you must specify at
* least one fallback cursor that is not a URL. For example:
* [draggableCursor]="'url(http://www.example.com/icon.png), auto;'"
*/
draggableCursor: string;
/**
* The name or url of the cursor to display when the map is being dragged. This property uses the
* css cursor attribute to change the icon. As with the css property, you must specify at least
* one fallback cursor that is not a URL. For example:
* [draggingCursor]="'url(http://www.example.com/icon.png), auto;'"
*/
draggingCursor: string;
/**
* If false, prevents the map from being controlled by the keyboard. Keyboard shortcuts are
* enabled by default.
*/
keyboardShortcuts: boolean = true;
/**
* The enabled/disabled state of the Zoom control.
*/
zoomControl: boolean = true;
/**
* Map option attributes that can change over time
*/
private static _mapOptionsAttributes: string[] = [
'disableDoubleClickZoom', 'scrollwheel', 'draggableCursor', 'draggingCursor',
'keyboardShortcuts', 'zoomControl'
];
/**
* This event emitter gets emitted when the user clicks on the map (but not when they click on a
* marker or infoWindow).
*/
mapClick: EventEmitter = new EventEmitter();
/**
* This event emitter gets emitted when the user right-clicks on the map (but not when they click
* on a marker or infoWindow).
*/
mapRightClick: EventEmitter = new EventEmitter();
/**
* This event emitter gets emitted when the user double-clicks on the map (but not when they click
* on a marker or infoWindow).
*/
mapDblClick: EventEmitter = new EventEmitter();
/**
* This event emitter is fired when the map center changes.
*/
centerChange: EventEmitter = new EventEmitter();
constructor(private _elem: ElementRef, private _mapsWrapper: GoogleMapsAPIWrapper) {}
/** @internal */
ngOnInit() {
const container = this._elem.nativeElement.querySelector('.sebm-google-map-container-inner');
this._initMapInstance(container);
}
private _initMapInstance(el: HTMLElement) {
this._mapsWrapper.createMap(el, {
center: {lat: this._latitude, lng: this._longitude},
zoom: this._zoom,
disableDefaultUI: this.disableDefaultUI,
backgroundColor: this.backgroundColor,
draggableCursor: this.draggableCursor,
draggingCursor: this.draggingCursor,
keyboardShortcuts: this.keyboardShortcuts,
zoomControl: this.zoomControl
});
this._handleMapCenterChange();
this._handleMapZoomChange();
this._handleMapMouseEvents();
}
/* @internal */
ngOnChanges(changes: {[propName: string]: SimpleChange}) {
this._updateMapOptionsChanges(changes);
}
private _updateMapOptionsChanges(changes: {[propName: string]: SimpleChange}) {
let options: {[propName: string]: any} = {};
let optionKeys =
Object.keys(changes).filter(k => SebmGoogleMap._mapOptionsAttributes.indexOf(k) !== -1);
optionKeys.forEach((k) => { options[k] = changes[k].currentValue; });
this._mapsWrapper.setMapOptions(options);
}
/**
* Triggers a resize event on the google map instance.
* Returns a promise that gets resolved after the event was triggered.
*/
triggerResize(): Promise {
// Note: When we would trigger the resize event and show the map in the same turn (which is a
// common case for triggering a resize event), then the resize event would not
// work (to show the map), so we trigger the event in a timeout.
return new Promise((resolve) => {
setTimeout(
() => { return this._mapsWrapper.triggerMapEvent('resize').then(() => resolve()); });
});
}
/**
* Sets the zoom level of the map. The default value is `8`.
*/
set zoom(value: number|string) {
this._zoom = this._convertToDecimal(value, 8);
if (typeof this._zoom === 'number') {
this._mapsWrapper.setZoom(this._zoom);
}
}
/**
* The longitude that sets the center of the map.
*/
set longitude(value: number|string) {
this._longitude = this._convertToDecimal(value);
this._updateCenter();
}
/**
* The latitude that sets the center of the map.
*/
set latitude(value: number|string) {
this._latitude = this._convertToDecimal(value);
this._updateCenter();
}
private _convertToDecimal(value: string|number, defaultValue: number = null): number {
if (typeof value === 'string') {
return parseFloat(value);
} else if (typeof value === 'number') {
return value;
}
return defaultValue;
}
private _updateCenter() {
if (typeof this._latitude !== 'number' || typeof this._longitude !== 'number') {
return;
}
this._mapsWrapper.setCenter({
lat: this._latitude,
lng: this._longitude,
});
}
private _handleMapCenterChange() {
this._mapsWrapper.subscribeToMapEvent('center_changed').subscribe(() => {
this._mapsWrapper.getCenter().then((center: LatLng) => {
this._latitude = center.lat();
this._longitude = center.lng();
this.centerChange.emit({lat: this._latitude, lng: this._longitude});
});
});
}
private _handleMapZoomChange() {
this._mapsWrapper.subscribeToMapEvent('zoom_changed').subscribe(() => {
this._mapsWrapper.getZoom().then((z: number) => this._zoom = z);
});
}
private _handleMapMouseEvents() {
interface Emitter {
emit(value: any): void;
}
type Event = {name: string, emitter: Emitter};
const events: Event[] = [
{name: 'click', emitter: this.mapClick},
{name: 'rightclick', emitter: this.mapRightClick},
];
events.forEach((e: Event) => {
this._mapsWrapper.subscribeToMapEvent<{latLng: LatLng}>(e.name).subscribe(
(event: {latLng: LatLng}) => {
const value = {coords: {lat: event.latLng.lat(), lng: event.latLng.lng()}};
e.emitter.emit(value);
});
});
}
}