diff --git a/docs/content/api-docs/index.md b/docs/content/api-docs/index.md index 49560b9b9..20eec0158 100644 --- a/docs/content/api-docs/index.md +++ b/docs/content/api-docs/index.md @@ -6,4 +6,7 @@ title = "API Docs for Angular Google Maps" **Here you find the API docs for the @agm Packages:** -* [@agm/core - Provides components and services for the official Google Maps API v3](./agm-core/modules/AgmCoreModule.html) \ No newline at end of file +* [@agm/core](./agm-core/modules/AgmCoreModule.html) + Provides Angular integration solutions for the official Google Maps Core API v3 +* [@agm/snazzy-info-window](./agm-snazzy-info-window/modules/AgmSnazzyInfoWindowModule.html) + Styled Info Windows with [Snazzy Info Window](https://github.com/atmist/snazzy-info-window#html-structure) \ No newline at end of file diff --git a/docs/content/guides/snazzy-info-window/custom-info-windows-with-snazzy-info-window.md b/docs/content/guides/snazzy-info-window/custom-info-windows-with-snazzy-info-window.md new file mode 100644 index 000000000..0b82e41fa --- /dev/null +++ b/docs/content/guides/snazzy-info-window/custom-info-windows-with-snazzy-info-window.md @@ -0,0 +1,106 @@ ++++ +date = "2017-06-20T20:11:15+02:00" +draft = false +title = "Styled Info Windows with Snazzy Info Window & Angular Google Maps" ++++ + +Angular Google Maps provides a package that allows you to use [Snazzy Info Window](https://github.com/atmist/snazzy-info-window) together with @agm/core. 'Snazzy Info Window' allows you to create custom info window that are styleable via CSS or Angular inputs. + +Please note: The @agm/snazzy-info-window package currently supports Angular 4.x only. + +## Install the needed packages +First make sure that you install the following NPM packages: + +```bash +npm install @agm/core @agm/snazzy-info-window snazzy-info-window@^1.1.0 +``` + +Make sure you have a Google Maps API Key - [you can get one here](https://developers.google.com/maps/documentation/javascript/get-api-key?hl=de). + +## Loading the modules + +Update your root component (e.g. src/app/app.module.ts) and import the following modules: + +```typescript +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; + +// add these imports +import { AgmCoreModule } from '@agm/core'; +import { AgmSnazzyInfoWindowModule } from '@agm/snazzy-info-window'; + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + AgmCoreModule.forRoot({ + apiKey: ['YOUR_API_KEY_HERE'] + }), + AgmSnazzyInfoWindowModule + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } +``` + +## Load the CSS file + +There are some basic style that get shipped with the `snazzy-info-window` NPM package. The CSS file is located here: + +``` +node_modules/snazzy-info-window/dist/snazzy-info-window.css +``` + +If you are using Angular CLI, you can simply add this file to your `.angular-cli.json` file like this: + +```json +"styles": [ + "styles.css", + "../node_modules/snazzy-info-window/dist/snazzy-info-window.css" +] +``` + +## Using the directive with a marker (AgmMarker) + +When you import the `AgmSnazzyInfoWindowModule`, you can use the `agmSnazzyInfoWindow` directive in your template. + + +```html + + + + + My first Snazzy Info Window! + + + + +``` + +This creates a basic stylable info window that opens when the user clicks on the marker and closes when another snazzy info window opens. + +## Using the directive as a standalone info winodw + + +```html + + + + My first Snazzy Info Window! + + + +``` + +## Styling +There a two ways to style the snazzy info window: +1. Via CSS - [simply use these CSS classes shown in this HTML](https://github.com/atmist/snazzy-info-window#html-structure) +1. Via Angular inputs + +### Styling via Angular Inputs + +There a sevarel inputs that you can use for styling. [Check out the docs of the `agmSnazzyInfoWindow` directive here](/api-docs/agm-snazzy-info-window/directives/AgmSnazzyInfoWindow.html). \ No newline at end of file diff --git a/karma-test-shim.js b/karma-test-shim.js index 169dc1abb..7ad118e40 100644 --- a/karma-test-shim.js +++ b/karma-test-shim.js @@ -1,7 +1,7 @@ // Turn on full stack traces in errors to help debugging Error.stackTraceLimit=Infinity; -jasmine.DEFAULT_TIMEOUT_INTERVAL = 100; +jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; require('core-js/client/shim'); require('reflect-metadata'); diff --git a/karma.conf.js b/karma.conf.js index ada2e07c3..f5d5aa601 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -91,5 +91,4 @@ module.exports = function (config) { } config.set(_config); - }; \ No newline at end of file diff --git a/packages/snazzy-info-window/directives/snazzy-info-window.ts b/packages/snazzy-info-window/directives/snazzy-info-window.ts new file mode 100644 index 000000000..db0e2ba72 --- /dev/null +++ b/packages/snazzy-info-window/directives/snazzy-info-window.ts @@ -0,0 +1,283 @@ +import { Host, SkipSelf, AfterViewInit, EventEmitter, Input, SimpleChanges, ViewContainerRef, TemplateRef, Output, Optional, OnDestroy, ElementRef, Component, ViewChild, ContentChild } from '@angular/core'; +import { AgmMarker, GoogleMapsAPIWrapper, MarkerManager, MapsAPILoader } from '@agm/core'; + +declare var System: any; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'agm-snazzy-info-window', + template: '
' +}) +export class AgmSnazzyInfoWindow implements AfterViewInit, OnDestroy { + /** + * The latitude and longitude where the info window is anchored. + * The offset will default to 0px when using this option. Only required/used if you are not using a agm-marker. + */ + @Input() latitude: number; + + /** + * The longitude where the info window is anchored. + * The offset will default to 0px when using this option. Only required/used if you are not using a agm-marker. + */ + @Input() longitude: number; + + /** + * Changes the open status of the snazzy info window. + */ + @Input() isOpen: boolean = false; + + /** + * Emits when the open status changes. + */ + @Output() isOpenChange: EventEmitter = new EventEmitter(); + + /** + * Choose where you want the info window to be displayed, relative to the marker. + */ + @Input() placement: 'top'|'bottom'|'left'|'right' = 'top'; + + /** + * The max width in pixels of the info window. + */ + @Input() maxWidth: number|string = 200; + + /** + * The max height in pixels of the info window. + */ + @Input() maxHeight: number|string = 200; + + /** + * The color to use for the background of the info window. + */ + @Input() backgroundColor: string; + + /** + * A custom padding size around the content of the info window. + */ + @Input() padding: string; + + /** + * A custom border around the info window. Set to false to completely remove the border. + * The units used for border should be the same as pointer. + */ + @Input() border: {width: string; color: string}|boolean; + + /** + * A custom CSS border radius property to specify the rounded corners of the info window. + */ + @Input() borderRadius: string; + + /** + * The font color to use for the content inside the body of the info window. + */ + @Input() fontColor: string; + + /** + * The font size to use for the content inside the body of the info window. + */ + @Input() fontSize: string; + + /** + * The height of the pointer from the info window to the marker. + * Set to false to completely remove the pointer. + * The units used for pointer should be the same as border. + */ + @Input() pointer: string|boolean; + + /** + * The CSS properties for the shadow of the info window. + * Set to false to completely remove the shadow. + */ + @Input() shadow: boolean|{h?: string, v?: string, blur: string, spread: string, opacity: number, color: string}; + + /** + * Determines if the info window will open when the marker is clicked. + * An internal listener is added to the Google Maps click event which calls the open() method. + */ + @Input() openOnMarkerClick: boolean = true; + + /** + * Determines if the info window will close when the map is clicked. An internal listener is added to the Google Maps click event which calls the close() method. + * This will not activate on the Google Maps drag event when the user is panning the map. + */ + @Input() closeOnMapClick: boolean = true; + + /** + * An optional CSS class to assign to the wrapper container of the info window. + * Can be used for applying custom CSS to the info window. + */ + @Input() wrapperClass: string; + + /** + * Determines if the info window will close when any other Snazzy Info Window is opened. + */ + @Input() closeWhenOthersOpen: boolean = false; + + /** + * Determines if the info window will show a close button. + */ + @Input() showCloseButton: boolean = true; + + /** + * Determines if the info window will be panned into view when opened. + */ + @Input() panOnOpen: boolean = true; + + /** + * Emits before the info window opens. + */ + @Output() beforeOpen: EventEmitter = new EventEmitter(); + + /** + * Emits before the info window closes. + */ + @Output() afterClose: EventEmitter = new EventEmitter(); + + /** + * @internal + */ + @ViewChild('outerWrapper', {read: ElementRef}) _outerWrapper: ElementRef; + + /** + * @internal + */ + @ViewChild('viewContainer', {read: ViewContainerRef}) _viewContainerRef: ViewContainerRef; + + /** + * @internal + */ + @ContentChild(TemplateRef) _templateRef: TemplateRef; + + protected _nativeSnazzyInfoWindow: any; + protected _snazzyInfoWindowInitialized: Promise|null = null; + + constructor( + @Optional() @Host() @SkipSelf() private _marker: AgmMarker, + private _wrapper: GoogleMapsAPIWrapper, + private _manager: MarkerManager, + private _loader: MapsAPILoader + ) {} + + /** + * @internal + */ + ngOnChanges(changes: SimpleChanges) { + if (this._nativeSnazzyInfoWindow == null) { + return; + } + if ('isOpen' in changes && this.isOpen) { + this._openInfoWindow(); + } else if ('isOpen' in changes && !this.isOpen) { + this._closeInfoWindow(); + } + if (('latitude' in changes || 'longitude' in changes) && this._marker == null) { + this._updatePosition(); + } + } + + /** + * @internal + */ + ngAfterViewInit() { + const m = this._manager != null ? this._manager.getNativeMarker(this._marker) : null; + this._snazzyInfoWindowInitialized = this._loader.load() + .then(() => System.import('snazzy-info-window')) + .then((module: any) => Promise.all([module, m, this._wrapper.getNativeMap()])) + .then((elems) => { + const options: any = { + map: elems[2], + content: '', + placement: this.placement, + maxWidth: this.maxWidth, + maxHeight: this.maxHeight, + backgroundColor: this.backgroundColor, + padding: this.padding, + border: this.border, + borderRadius: this.borderRadius, + fontColor: this.fontColor, + pointer: this.pointer, + shadow: this.shadow, + openOnMarkerClick: this.openOnMarkerClick, + closeWhenOthersOpen: this.closeWhenOthersOpen, + showCloseButton: this.showCloseButton, + panOnOpen: this.panOnOpen, + wrapperClass: this.wrapperClass, + callbacks: { + beforeOpen: () => { + this._createViewContent(); + this.beforeOpen.emit(); + }, + afterOpen: () => { + this.isOpenChange.emit(this.openStatus()); + }, + afterClose: () => { + this.afterClose.emit(); + this.isOpenChange.emit(this.openStatus()); + } + } + }; + if (elems[1] != null) { + options.marker = elems[1]; + } else { + options.position = { + lat: this.latitude, + lng: this.longitude + }; + } + this._nativeSnazzyInfoWindow = new elems[0](options); + }); + this._snazzyInfoWindowInitialized.then(() => { + if (this.isOpen) { + this._openInfoWindow(); + } + }); + } + + protected _openInfoWindow() { + this._snazzyInfoWindowInitialized.then(() => { + this._createViewContent(); + this._nativeSnazzyInfoWindow.open(); + }); + } + + protected _closeInfoWindow() { + this._snazzyInfoWindowInitialized.then(() => { + this._nativeSnazzyInfoWindow.close(); + }); + } + + protected _createViewContent() { + if (this._viewContainerRef.length === 1) { + return; + } + const evr = this._viewContainerRef.createEmbeddedView(this._templateRef); + this._nativeSnazzyInfoWindow.setContent(this._outerWrapper.nativeElement); + // we have to run this in a separate cycle. + setTimeout(() => { + evr.detectChanges(); + }); + } + + protected _updatePosition() { + this._nativeSnazzyInfoWindow.setPosition({ + lat: this.latitude, + lng: this.longitude + }); + } + + /** + * Returns true when the Snazzy Info Window is initialized and open. + */ + openStatus(): boolean { + return this._nativeSnazzyInfoWindow && this._nativeSnazzyInfoWindow.isOpen(); + } + + /** + * @internal + */ + ngOnDestroy() { + if (this._nativeSnazzyInfoWindow) { + this._nativeSnazzyInfoWindow.destroy(); + } + } +} diff --git a/packages/snazzy-info-window/index.ts b/packages/snazzy-info-window/index.ts new file mode 100644 index 000000000..b85997a88 --- /dev/null +++ b/packages/snazzy-info-window/index.ts @@ -0,0 +1,3 @@ +// public API +export { AgmSnazzyInfoWindowModule } from './snazzy-info-window.module'; +export { AgmSnazzyInfoWindow } from './directives/snazzy-info-window'; diff --git a/packages/snazzy-info-window/package.tpl.json b/packages/snazzy-info-window/package.tpl.json new file mode 100644 index 000000000..823ad34dc --- /dev/null +++ b/packages/snazzy-info-window/package.tpl.json @@ -0,0 +1,28 @@ +{ + "name": "@agm/snazzy-info-window", + "version": "1.0.0-beta.1", + "description": "Angular Google Maps (AGM) package for Snazzy Info Window support", + "repository": { + "type": "git", + "url": "git+https://github.com/SebastianM/angular-google-maps.git" + }, + "keywords": [ + "angular-google-maps", + "angular", + "snazzy-info-window", + "info-window", + "google-maps", + "agm" + ], + "author": "Sebastian Müller", + "license": "MIT", + "bugs": { + "url": "https://github.com/SebastianM/angular-google-maps/issues" + }, + "peerDependencies": { + "@angular/core": "^4.0.0", + "@agm/core": "^1.0.0-beta.0", + "snazzy-info-window": "^1.1.0" + }, + "homepage": "https://github.com/SebastianM/angular-google-maps#readme" +} diff --git a/packages/snazzy-info-window/snazzy-info-window.module.ts b/packages/snazzy-info-window/snazzy-info-window.module.ts new file mode 100644 index 000000000..94b605f3e --- /dev/null +++ b/packages/snazzy-info-window/snazzy-info-window.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { AgmSnazzyInfoWindow } from './directives/snazzy-info-window'; + +@NgModule({ + declarations: [AgmSnazzyInfoWindow], + exports: [AgmSnazzyInfoWindow] +}) +export class AgmSnazzyInfoWindowModule {} diff --git a/rollup.snazzy-info-window.config.js b/rollup.snazzy-info-window.config.js new file mode 100644 index 000000000..1e3076854 --- /dev/null +++ b/rollup.snazzy-info-window.config.js @@ -0,0 +1,22 @@ +export default { + entry: 'dist/snazzy-info-window/index.js', + dest: 'dist/snazzy-info-window/snazzy-info-window.umd.js', + format: 'umd', + moduleName: 'ngmaps.snazzyInfoWindow', + sourceMap: true, + globals: { + '@angular/core': 'ng.core', + '@angular/common': 'ng.common', + '@angular/compiler': 'ng.compiler', + '@angular/platform-browser': 'ng.platformBrowser', + '@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic', + 'rxjs/Subject': 'Rx', + 'rxjs/observable/PromiseObservable': 'Rx', + 'rxjs/operator/toPromise': 'Rx.Observable.prototype', + 'rxjs/Observable': 'Rx', + 'rxjs/Rx': 'Rx', + '@agm/core': 'ngmaps.core' + }, + context: 'window', + external: ['rxjs', '@angular/core', 'rxjs/Observable'] +} diff --git a/scripts/create-package-json.js b/scripts/create-package-json.js index ccb1a9aed..9c12e4a86 100644 --- a/scripts/create-package-json.js +++ b/scripts/create-package-json.js @@ -4,7 +4,7 @@ const fs = require('fs'); const path = require('path'); -const pkgNames = ['core']; +const pkgNames = ['core', 'snazzy-info-window']; pkgNames.forEach(function(pkgName) { let basePkgJson; diff --git a/tslint.json b/tslint.json index 21da335dc..64baa6c09 100644 --- a/tslint.json +++ b/tslint.json @@ -77,7 +77,6 @@ "check-module", "check-type" ], - "directive-selector": [true, "element", "agm", "kebab-case"], "component-selector": [true, "element", "agm", "kebab-case"], "use-input-property-decorator": false, "use-output-property-decorator": false,