Skip to content

Instantly share code, notes, and snippets.

@Jones-S
Created November 20, 2019 00:27
Show Gist options
  • Save Jones-S/79d13ef11a72bffa46e80394f0670e17 to your computer and use it in GitHub Desktop.
Save Jones-S/79d13ef11a72bffa46e80394f0670e17 to your computer and use it in GitHub Desktop.
Map Example Component
<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