├── .gitignore ├── src ├── index.js └── GMap │ ├── marker.tmpl.js │ ├── GoogleMapsApi.js │ ├── index.js │ └── stylers.js ├── gulpfile.json ├── package.json ├── gulpfile.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Misc 2 | /tmp 3 | .DS_Store 4 | node_modules 5 | .log 6 | .remote-sync.json 7 | .sass-cache 8 | vendor 9 | dist 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as GMap from './GMap' 2 | 3 | /** 4 | * Init Map on 'js-map' 5 | * Add API Key 6 | */ 7 | if (document.querySelector('.js-map')) { 8 | GMap.GMap('.js-map', 'YOUR_GOOGLE_MAPS_API_KEY_HERE') 9 | } 10 | -------------------------------------------------------------------------------- /gulpfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GoogleMapES6", 3 | "version": "1.0.0", 4 | "description": "ES6 style google maps ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Stephen Scaff", 10 | "license": "DWTFYL" 11 | } 12 | -------------------------------------------------------------------------------- /src/GMap/marker.tmpl.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Marker Template 4 | * @param {obj} data - property data/json 5 | */ 6 | function markerTmpl(data) { 7 | 8 | const url = encodeURIComponent(data.address) 9 | 10 | return `
11 |
12 |
13 |
14 | ${data.title} 15 |
16 | ${data.address} 17 |
18 | Get Directions 19 |
20 |
21 |
22 |
`; 23 | } 24 | 25 | export default markerTmpl; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "es6googlemaps", 3 | "version": "1.0.0", 4 | "description": "Google maps api, as es6 style modules.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/stephenscaff/google-maps-es6" 12 | }, 13 | "keywords": [ 14 | "Google Maps", 15 | "google maps api", 16 | "es6 google maps", 17 | "maps" 18 | ], 19 | "author": "Stephen Scaff", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/stephenscaff/google-maps-es6#issues" 23 | }, 24 | "homepage": "https://github.com/stephenscaff/google-maps-es6#readme", 25 | "dependencies": {}, 26 | "devDependencies": { 27 | "@babel/core": "^7.8.4", 28 | "@babel/preset-env": "^7.8.4", 29 | "babelify": "^10.0.0", 30 | "browserify": "^16.5.0", 31 | "gulp": "^4.0.2", 32 | "gulp-rename": "^2.0.0", 33 | "gulp-terser": "^1.2.0", 34 | "vinyl-buffer": "^1.0.1", 35 | "vinyl-source-stream": "^2.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 2 | const gulp = require('gulp'), 3 | babelify = require('babelify'), 4 | browserify = require('browserify'), 5 | buffer = require('vinyl-buffer'), 6 | rename = require('gulp-rename'), 7 | source = require('vinyl-source-stream'), 8 | terser = require('gulp-terser'); 9 | 10 | 11 | // Error handler 12 | function handleError(err) { 13 | console.log(err.toString()) 14 | this.emit('end') 15 | } 16 | 17 | function buildJS() { 18 | const bundler = browserify('src/index.js').transform( 19 | 'babelify', 20 | { presets: ['@babel/preset-env'] } 21 | ) 22 | return bundler.bundle() 23 | .on('error', handleError) 24 | .pipe(source('src/index.js')) 25 | .pipe(buffer()) 26 | .pipe(terser({ 27 | mangle: false, 28 | compress: true 29 | })) 30 | .pipe(rename("es6-gmap.js")) 31 | .pipe(gulp.dest('dist/')) 32 | } 33 | 34 | 35 | function watch() { 36 | gulp.watch('src/**/*', buildJS) 37 | } 38 | 39 | var build = gulp.parallel( 40 | buildJS, 41 | watch 42 | ) 43 | 44 | gulp.task(build) 45 | gulp.task('default', build) 46 | -------------------------------------------------------------------------------- /src/GMap/GoogleMapsApi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GoogleMapsApi 3 | * Class to load google maps api with api key 4 | * and global Callback to init map after resolution of promise. 5 | * 6 | * @exports {GoogleMapsApi} 7 | * @example MapApi = new GoogleMapsApi(); 8 | * MapApi.load().then(() => {}); 9 | */ 10 | class GoogleMapsApi { 11 | 12 | /** 13 | * Constructor 14 | * @property {string} apiKey 15 | * @property {string} callbackName 16 | */ 17 | constructor(gApiKey) { 18 | 19 | // api key for google maps 20 | this.apiKey = gApiKey; 21 | 22 | // Set global callback 23 | if (!window._GoogleMapsApi) { 24 | this.callbackName = '_GoogleMapsApi.mapLoaded'; 25 | window._GoogleMapsApi = this; 26 | window._GoogleMapsApi.mapLoaded = this.mapLoaded.bind(this); 27 | } 28 | } 29 | 30 | /** 31 | * Load 32 | * Create script element with google maps 33 | * api url, containing api key and callback for 34 | * map init. 35 | * @return {promise} 36 | * @this {_GoogleMapsApi} 37 | */ 38 | load() { 39 | if (!this.promise) { 40 | this.promise = new Promise(resolve => { 41 | this.resolve = resolve; 42 | 43 | if (typeof window.google === 'undefined') { 44 | const script = document.createElement('script'); 45 | script.src = `//maps.googleapis.com/maps/api/js?key=${this.apiKey}&callback=${this.callbackName}`; 46 | script.async = true; 47 | document.body.append(script); 48 | 49 | } else { 50 | this.resolve(); 51 | } 52 | }); 53 | } 54 | 55 | return this.promise; 56 | } 57 | 58 | /** 59 | * mapLoaded 60 | * Global callback for loaded/resolved map instance. 61 | * @this {_GoogleMapsApi} 62 | * 63 | */ 64 | mapLoaded() { 65 | 66 | if (this.resolve) { 67 | this.resolve(); 68 | } 69 | } 70 | } 71 | 72 | export default GoogleMapsApi; 73 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Google Maps, ES6 Like 2 | 3 | Example for working with Google Maps API, all es6 like. 4 | 5 | Contains a loader class to inject the Google Maps script, with a promise that upon resolution provides a callback for a map rendering function. 6 | 7 | Also includes a separate custom marker template file and stylers object. 8 | 9 | The js structure is as follows: 10 | 11 | ``` 12 | -js 13 | |- GMap 14 | |- GoogleMapsApi.js - google maps api class 15 | |- marker.tmpl.js - custom marker template 16 | |- stylers.js - JSON styles and custom icon 17 | |- index.js - functions to render map, marker and infowindow 18 | |- index.js - import and init example 19 | ``` 20 | 21 | ## Install and Build 22 | 23 | To build this project first install 24 | 25 | ``` 26 | npm install 27 | ``` 28 | 29 | Then build it like 30 | 31 | ``` 32 | gulp 33 | ``` 34 | 35 | Or, just add `src/GMap` to your existing es6 project. 36 | 37 | 38 | ## Usage 39 | 40 | Import map components then pass your Google Map selector and Google Maps api key to `Gmap($mapEl, $apiKey)` 41 | An example init is at `src/index.js`: 42 | 43 | 44 | ``` 45 | import * as Gmap from './GMap' 46 | 47 | Gmap.GMap('.js-map', 'YOUR_GOOGLE_MAPS_API_KEY_HERE') 48 | ``` 49 | 50 | ## Markup 51 | 52 | The map div uses uses data attributes to pass values to the map instance. 53 | The required are `data-lat` and `data-lng`. For example: 54 | 55 | ``` 56 |
57 |
58 |
65 |
66 |
67 |
68 | ``` 69 | 70 | ## Styles 71 | Your map won't render unless it has a height defined. Without example above, that could look Like: 72 | 73 | ``` 74 | .map__wrap{ 75 | position: relative; 76 | width: 100%; 77 | } 78 | 79 | .map__map { 80 | width: 100%; 81 | height: 100%; 82 | min-height: 50em; 83 | } 84 | ``` 85 | 86 | Odds are you already know that. 87 | 88 | ## Info Window Template 89 | Edit the info window popup template at `src/Gmap/marker.tmpl.js` 90 | 91 | 92 | ## Google Maps Styles 93 | Edit Google Map's styles JSON at `src/GMap/stylers.js` 94 | 95 | 96 | Have fun. 97 | -------------------------------------------------------------------------------- /src/GMap/index.js: -------------------------------------------------------------------------------- 1 | import GoogleMapsApi from './GoogleMapsApi' 2 | import { stylers } from './stylers' 3 | import markerTmpl from './marker.tmpl' 4 | 5 | /** 6 | * Location Map 7 | * Main map rendering function that uses our GMaps API class 8 | * @param {string} el - Google Map selector 9 | */ 10 | export function GMap(el, apiKey) { 11 | 12 | const gApiKey = apiKey 13 | console.log(gApiKey) 14 | const gmapApi = new GoogleMapsApi(gApiKey) 15 | const mapEl = document.querySelector(el) 16 | const data = { 17 | lat: parseFloat(mapEl.dataset.lat ? mapEl.dataset.lat : 0), 18 | lng: parseFloat(mapEl.dataset.lng ? mapEl.dataset.lng : 0), 19 | address: mapEl.dataset.address, 20 | title: mapEl.dataset.title ? mapEl.dataset.title: "Map", 21 | zoom: parseFloat(mapEl.dataset.zoom ? mapEl.dataset.zoom: 12), 22 | } 23 | // Call map renderer 24 | gmapApi.load().then(() => { 25 | renderMap(mapEl, data) 26 | }) 27 | } 28 | 29 | /** 30 | * Render Map 31 | * @param {map obj} mapEl - Google Map 32 | * @param {obj} data - map data 33 | */ 34 | function renderMap(mapEl, data) { 35 | 36 | const options = { 37 | mapTypeId: google.maps.MapTypeId.ROADMAP, 38 | styles: stylers.styles, 39 | zoom: data.zoom, 40 | center: { 41 | lat: data.lat, 42 | lng: data.lng 43 | } 44 | } 45 | 46 | const map = new google.maps.Map(mapEl, options) 47 | 48 | renderMarker(map, data) 49 | } 50 | 51 | /** 52 | * Render Marker 53 | * Renders custom map marker and infowindow 54 | * @param {map element} mapEl 55 | * @param {object} data 56 | */ 57 | function renderMarker(map, data) { 58 | 59 | const icon = { 60 | url: stylers.icons.red, 61 | scaledSize: new google.maps.Size(80, 80) 62 | } 63 | 64 | const tmpl = markerTmpl(data) 65 | 66 | const marker = new google.maps.Marker({ 67 | position: new google.maps.LatLng(data.lat, data.lng), 68 | map: map, 69 | icon: icon, 70 | title: data.title, 71 | content: tmpl, 72 | animation: google.maps.Animation.DROP 73 | }) 74 | 75 | const infowindow = new google.maps.InfoWindow() 76 | 77 | handleMarkerClick(map, marker, infowindow) 78 | } 79 | 80 | /** 81 | * Handle Marker Click 82 | * 83 | * @param {map obj} mapEl 84 | * @param {marker} marker 85 | * @param {infowindow} infoWindow 86 | */ 87 | function handleMarkerClick(map, marker, infowindow) { 88 | 89 | google.maps.event.addListener(marker, 'click', function() { 90 | infowindow.setContent(marker.content) 91 | infowindow.open(map, marker) 92 | }) 93 | 94 | google.maps.event.addListener(map, 'click', function(event) { 95 | if (infowindow) { 96 | infowindow.close(map, infowindow) 97 | } 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /src/GMap/stylers.js: -------------------------------------------------------------------------------- 1 | export const stylers = { 2 | 3 | /** 4 | * Map Styler JSON 5 | */ 6 | styles: [ 7 | { 8 | "featureType": "administrative", 9 | "elementType": "labels.text.fill", 10 | "stylers": [ { 11 | "color": "#444444" 12 | } ] 13 | }, { 14 | "featureType": "landscape", 15 | "elementType": "all", 16 | "stylers": [ { 17 | "color": "#f2f2f2" 18 | } ] 19 | }, { 20 | "featureType": "landscape.natural.landcover", 21 | "elementType": "labels.icon", 22 | "stylers": [ { 23 | "visibility": "simplified" 24 | } ] 25 | }, { 26 | "featureType": "poi", 27 | "elementType": "all", 28 | "stylers": [ { 29 | "visibility": "off" 30 | } ] 31 | }, { 32 | "featureType": "road", 33 | "elementType": "all", 34 | "stylers": [ { 35 | "saturation": -100 36 | }, { 37 | "lightness": 45 38 | } ] 39 | }, { 40 | "featureType": "road.highway", 41 | "elementType": "all", 42 | "stylers": [ { 43 | "visibility": "simplified" 44 | } ] 45 | }, { 46 | "featureType": "road.highway", 47 | "elementType": "geometry.fill", 48 | "stylers": [ { 49 | "color": "#ffffff" 50 | } ] 51 | }, { 52 | "featureType": "road.arterial", 53 | "elementType": "labels.icon", 54 | "stylers": [ { 55 | "visibility": "off" 56 | } ] 57 | }, { 58 | "featureType": "transit", 59 | "elementType": "all", 60 | "stylers": [ { 61 | "visibility": "off" 62 | } ] 63 | }, { 64 | "featureType": "water", 65 | "elementType": "all", 66 | "stylers": [ { 67 | "color": "#dde6e8" 68 | }, { 69 | "visibility": "on" 70 | } ] 71 | } 72 | ], 73 | 74 | /** 75 | * Map Icon/Pin SVG 76 | * SVG is base64 encoded. 77 | */ 78 | icons: { 79 | red: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMzVweCIgaGVpZ2h0PSI0M3B4IiB2aWV3Qm94PSIwIDAgMzUgNDMiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUxLjMgKDU3NTQ0KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5QaW4gLSBPUiBDb3B5IDM8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBkPSJNNjgyLDM3MSBMNjgyLDM4MCBMNjczLDM3MSBMNjU5LDM3MSBMNjU5LDM0OCBMNjgyLDM0OCBMNjgyLDM3MSBaIiBpZD0icGF0aC0xIj48L3BhdGg+CiAgICAgICAgPGZpbHRlciB4PSItNDMuNSUiIHk9Ii0yNS4wJSIgd2lkdGg9IjE4Ny4wJSIgaGVpZ2h0PSIxNjIuNSUiIGZpbHRlclVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgaWQ9ImZpbHRlci0yIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IGR4PSIwIiBkeT0iMiIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMSI+PC9mZU9mZnNldD4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMyIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIj48L2ZlR2F1c3NpYW5CbHVyPgogICAgICAgICAgICA8ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMC4yMTE3NjQ3MDYgICAwIDAgMCAwIDAuMjA3ODQzMTM3ICAgMCAwIDAgMCAwLjIxNTY4NjI3NSAgMCAwIDAgMC4yNCAwIiB0eXBlPSJtYXRyaXgiIGluPSJzaGFkb3dCbHVyT3V0ZXIxIj48L2ZlQ29sb3JNYXRyaXg+CiAgICAgICAgPC9maWx0ZXI+CiAgICA8L2RlZnM+CiAgICA8ZyBpZD0i8J+OqC1EZXNpZ24iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJQcm9mZXNzaW9uYWwtRGV0YWlsLS0tQ3JpZGVyYS0iIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04OTAuMDAwMDAwLCAtNDE5MS4wMDAwMDApIiBmaWxsLXJ1bGU9Im5vbnplcm8iPgogICAgICAgICAgICA8ZyBpZD0iUHJvcGVydGllcy1NYXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUwLjAwMDAwMCwgMzUzMy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJMb2NhdGlvbnMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE4Ny4wMDAwMDAsIDMxNC4wMDAwMDApIj4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iUGlucyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxnIGlkPSJQaW4tLS1PUi1Db3B5LTMiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHVzZSBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIxIiBmaWx0ZXI9InVybCgjZmlsdGVyLTIpIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx1c2UgZmlsbD0iI0UwNDQwMyIgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==' 80 | } 81 | }; 82 | --------------------------------------------------------------------------------