Created
November 20, 2019 00:27
-
-
Save Jones-S/79d13ef11a72bffa46e80394f0670e17 to your computer and use it in GitHub Desktop.
Map Example Component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="StoreFinderMap" aria-hidden="true"> | |
<!-- Using v-show because the DOM element has to exist when initiating the map --> | |
<div v-show="map" ref="map" class="StoreFinderMap__map-wrapper" /> | |
<div class="StoreFinderMap__control-wrapper"> | |
<StoreFinderZoomButton magnify @magnify="magnify">+</StoreFinderZoomButton> | |
<StoreFinderZoomButton demagnify @demagnify="demagnify">-</StoreFinderZoomButton> | |
</div> | |
</div> | |
</template> | |
<script> | |
import { mapGetters } from 'vuex'; | |
import MarkerClusterer from '@google/markerclustererplus'; | |
import StoreFinderZoomButton from '../StoreFinderZoomButton/StoreFinderZoomButton.vue'; | |
import calculateDistance from '../../mixins/calculateDistance'; | |
export default { | |
components: { | |
StoreFinderZoomButton, | |
}, | |
mixins: [calculateDistance], | |
props: { | |
mapOptions: { | |
type: Object, | |
required: true, | |
default: () => {}, | |
}, | |
markerClusterStyle: { | |
type: Array, | |
required: true, | |
default: () => [], | |
}, | |
google: { | |
type: Object, | |
required: true, | |
default: () => {}, | |
}, | |
}, | |
data: () => ({ | |
map: null, | |
geocoder: null, | |
markers: null, | |
resultMarker: null, // current user locatin or search result marker | |
markerPosition: { x: 0, y: 58 }, | |
}), | |
computed: { | |
...mapGetters(['mapLocations', 'searchCoordinates', 'mapCoordinates', 'storeFilter', 'activeLocation']), | |
markerAnchor() { | |
return new this.google.maps.Point(this.markerPosition.x, this.markerPosition.y); | |
}, | |
}, | |
watch: { | |
searchCoordinates: function (newPosition) { // eslint-disable-line object-shorthand, func-names | |
this.setMapBounds({ position: newPosition, showClosest: true }); | |
this.setResultMarker(newPosition); // set user-location marker / search-marker | |
}, | |
mapCoordinates: function (newPosition) { // eslint-disable-line object-shorthand, func-names | |
this.setMapBounds({ position: newPosition, zoom: this.mapOptions.detailZoom }); | |
}, | |
activeLocation: function (newActiveLocation) { // eslint-disable-line object-shorthand, func-names | |
this.removeMarkersFromMap(); | |
this.addNewMarkersToMap({ activeLocation: newActiveLocation }); | |
this.setMarkerClusterer(); | |
// if active location has been reset (=null), we don't want to hide the result marker | |
if (newActiveLocation) { this.resetResultMarker(); } | |
}, | |
storeFilter: function (newFilterState) { // eslint-disable-line object-shorthand, func-names | |
this.removeMarkersFromMap(); | |
this.addNewMarkersToMap({ filterState: newFilterState }); | |
this.setMarkerClusterer(newFilterState); | |
}, | |
}, | |
mounted() { | |
try { | |
this.initMap(); | |
this.initMarkers(); | |
this.subscribeToMapScroll(); | |
} catch (error) { | |
/* eslint-disable-next-line no-console */ | |
console.error(error); | |
} | |
}, | |
methods: { | |
initMap() { | |
this.geocoder = new this.google.maps.Geocoder(); | |
this.map = new this.google.maps.Map(this.$refs.map, { ...this.mapOptions, mapTypeId: this.google.maps.MapTypeId.ROADMAP }); | |
this.geocoder.geocode({ address: this.mapOptions.centerOnLoad }, (results, status) => { | |
if (status !== 'OK' || !results[0]) { | |
throw new Error(status); | |
} | |
// check for initial coordinates to center | |
if (this.mapCoordinates) { | |
this.setMapBounds({ position: this.mapCoordinates, zoom: this.mapOptions.detailZoom }); | |
} else if (this.searchCoordinates) { | |
this.setMapBounds({ position: this.searchCoordinates, showClosest: true }); | |
this.setResultMarker(this.searchCoordinates); // set user-location marker / search-marker | |
} else { | |
this.setMapBounds({ position: this.mapOptions.centerCoords }); | |
} | |
}); | |
}, | |
subscribeToMapScroll() { | |
this.$store.subscribeAction((action) => { | |
if (action.type === 'scrollMapIntoView') { | |
this.scrollMapIntoView(); | |
} | |
}); | |
}, | |
initMarkers() { | |
this.markers = this.mapLocations.map((marker) => { | |
const active = this.activeLocation && marker.storeCode === this.activeLocation.storeCode; | |
const newMarker = new this.google.maps.Marker({ | |
...marker, | |
map: this.map, | |
}); | |
this.setMarkerIcon({ marker: newMarker, active }); | |
return newMarker; | |
}); | |
this.markers.forEach((marker) => { | |
this.google.maps.event.addListener(marker, 'click', () => { | |
this.$store.dispatch('setDetailID', marker.storeCode); | |
const position = { | |
lat: marker.position.lat(), | |
lng: marker.position.lng(), | |
}; | |
this.$store.dispatch('setCurrentCoordinates', position); // needed for sorting the list depending on currentSearchCoordinates | |
this.$store.dispatch('expandListItem', marker.storeCode); | |
document.querySelectorAll('.StoreFinderLocationsList')[0].scrollTop = 0; // Reset List scrollTop position to show expanend item | |
}); | |
this.google.maps.event.addListener(marker, 'mouseover', () => { | |
const icon = { | |
url: this.iconType({ attributes: marker.attributes, hover: true }), | |
anchor: this.markerAnchor, | |
}; | |
marker.setIcon(icon); | |
}); | |
this.google.maps.event.addListener(marker, 'mouseout', () => { | |
// check if store is active, then don't set default icon | |
if (this.activeLocation && this.activeLocation.storeCode !== marker.storeCode) { | |
const icon = { | |
url: this.iconType({ attributes: marker.attributes }), | |
anchor: this.markerAnchor, | |
}; | |
marker.setIcon(icon); | |
} | |
}); | |
}); | |
this.setMarkerClusterer(); | |
}, | |
removeMarkersFromMap() { | |
this.markers.forEach((marker) => { | |
marker.setMap(null); | |
}); | |
}, | |
resetResultMarker() { | |
if (!this.resultMarker) return; | |
this.resultMarker.setMap(null); | |
}, | |
addNewMarkersToMap({ filterState, activeLocation }) { | |
this.markers.forEach((marker) => { | |
if (filterState) { | |
// if filter is activated only show marker which have a store | |
if (marker.attributes.isStore) { | |
marker.setMap(this.map); | |
} | |
return; | |
} | |
// compare active storecode with current marker's storecode and activate if necessary | |
if (activeLocation && marker.storeCode === activeLocation.storeCode) { | |
this.setMarkerIcon({ marker, active: true }); | |
} else { | |
this.setMarkerIcon({ marker, active: false }); | |
} | |
marker.setMap(this.map); | |
}); | |
}, | |
setMarkerClusterer(filterState = false) { | |
if (this.clusterer) { | |
this.clusterer.clearMarkers(); | |
} | |
let { markers } = this; | |
if (filterState) { | |
markers = this.markers.filter(marker => marker.attributes.isStore); | |
} | |
this.clusterer = new MarkerClusterer(this.map, markers, { | |
maxZoom: this.mapOptions.maxZoom || 20, | |
styles: this.markerClusterStyle, | |
}); | |
}, | |
setResultMarker(location) { | |
if (!this.resultMarker) { | |
this.resultMarker = new this.google.maps.Marker({ | |
position: location, | |
map: this.map, | |
}); | |
const icon = { | |
url: this.iconType({ attributes: { isResult: true } }), | |
anchor: this.markerAnchor, | |
}; | |
this.resultMarker.setIcon(icon); | |
} else { | |
// if marker is already there, just change position | |
this.resultMarker.setPosition(location); | |
this.resultMarker.setMap(this.map); | |
} | |
}, | |
iconType({ attributes, hover = false, isActive = false } = {}) { | |
const { hasAtm, isStore, isResult } = attributes; | |
const path = this.mapOptions.iconPath; | |
const suffix = hover || isActive ? '-hover.svg' : '.svg'; | |
if (hasAtm && isStore) { | |
return `${path}storefinder-store-atm${suffix}`; | |
} else if (!hasAtm && isStore) { | |
return `${path}storefinder-store${suffix}`; | |
} else if (isResult) { | |
return `${path}storefinder-result.svg`; | |
} | |
return `${path}storefinder-atm${suffix}`; | |
}, | |
setMapBounds({ position, zoom, showClosest = false } = {}) { | |
// for search and location coordinates we should show the closest store | |
if (showClosest) { | |
const bounds = new this.google.maps.LatLngBounds(); | |
// get closest place to set bounds accordingly | |
const locationsWithDistance = this.mapLocations.map((marker) => { | |
const distance = this.calculateDistance(position, marker.position); // in meters | |
return { | |
distance, | |
...marker, | |
}; | |
}); | |
const [closest] = locationsWithDistance.sort((a, b) => a.distance - b.distance); | |
const closestStore = new this.google.maps.LatLng(closest.position.lat, closest.position.lng); | |
const center = new this.google.maps.LatLng(position.lat, position.lng); | |
bounds.extend(closestStore); | |
bounds.extend(center); | |
this.map.fitBounds(bounds); | |
} else { | |
// otherwise just center to the wanted location | |
this.map.setCenter(position); | |
// only use zoom if we don't set the bounds automatically | |
if (zoom) { | |
this.map.setZoom(zoom); | |
} | |
} | |
}, | |
removeActiveMarkers() { | |
this.markers.forEach((marker) => { | |
const icon = { | |
url: this.iconType({ attributes: marker.attributes, hover: false, isActive: false }), | |
anchor: this.markerAnchor, | |
}; | |
marker.setIcon(icon); | |
}); | |
}, | |
setMarkerIcon({ marker, active = false } = {}) { | |
const icon = { | |
url: this.iconType({ attributes: marker.attributes, hover: false, isActive: active }), | |
anchor: this.markerAnchor, | |
}; | |
marker.setIcon(icon); | |
}, | |
magnify() { | |
if (!this.map) return; | |
this.map.setZoom(this.map.getZoom() + 1); | |
}, | |
demagnify() { | |
if (!this.map) return; | |
this.map.setZoom(this.map.getZoom() - 1); | |
}, | |
scrollMapIntoView() { | |
this.$refs.map.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
}, | |
}, | |
}; | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment