This is the second part in a series about building a map-based event manager with MEVN stack and Vuetify. You can find the rest of the series here
vue2-google-maps
First make sure you have a google maps API key. If you don’t, get one here
We’re going to use the vue2-google-maps
library to access the Maps API. This will give us reactive 2-way bindings for the events on the map.
$ yarn add vue2-google-maps
$ yarn add @babel/runtime-corejs2
you need to add the
@babel/runtime-corejs2
package because of an issue with thevue2-google-maps
library
in main.js
:
import * as VueGoogleMaps from 'vue2-google-maps';
Vue.use(VueGoogleMaps, {
load: {
key: 'your api key',
libraries: 'places'
}
});
We’ll use the places library to get the place name as a default for the event title.
Create a new file: Map.vue
, in your components dir. This will encapsulate all the map stuff.
Paste the following into the new file:
<template>
<div>
<gmap-map
ref="mapRef"
style="height: 100%"
:center="location"
:zoom="14"
>
<gmap-marker
v-for="(event, index) in events"
:key="'marker'+index"
:position="event.position"
:icon="require('../assets/event_marker.png')"
@click="panTo(event.position)"
></gmap-marker>
<gmap-circle
v-for="(event, index) in events"
:key="'circle'+index"
:center="event.position"
:options="{
strokeColor: '#000',
strokeOpacity: 0,
strokeWeight: 0,
fillColor: '#ff6ada',
fillOpacity: 0.25,
radius: 500
}"
@click="panTo(event.position)"
></gmap-circle>
</gmap-map>
</div>
</template>
<script>
export default {
name: "Map",
data() {
return {
map: null,
events: [
{
position: {
lng: 34.77,
lat: 32.09
}
},
{
position: {
lng: 34.78,
lat: 32.08
}
}
],
location: {
lng: 34.77,
lat: 32.09
}
};
},
mounted() {
this.$refs.mapRef.$mapPromise.then(map => {
this.map = map;
});
},
methods: {
panTo(position) {
if (!this.map) return;
this.map.panTo(position);
}
}
};
</script>
<style scoped="true">
</style>
<gmap-map
ref="mapRef"
style="height: 100%"
:center="location"
:zoom="14"
>
The main map component, with center coordinates and initial zoom as props.
<gmap-marker
v-for="(event, index) in events"
:key="'marker'+index"
:position="event.position"
:icon="require('../assets/event_marker.png')"
@click="panTo(event.position)"
></gmap-marker>
We’re looping over the events
data field (initialized statically for now, later from mongo), and creating a <gmap-marker>
with the event’s position. We’re injecting it with a custom icon from the assets dir, and a click handler which pans to the marker’s position.
<gmap-circle
v-for="(event, index) in events"
:key="'circle'+index"
:center="event.position"
:options="{
strokeColor: '#000',
strokeOpacity: 0,
strokeWeight: 0,
fillColor: '#ff6ada',
fillOpacity: 0.25,
radius: 500
}"
@click="panTo(event.position)"
></gmap-circle>
The same but with nice pink circles :)
Notice the usage of :options
prop for passing custom circle properties.
Because we’re using vue directives to render the markers and circles, they are reactive and changes in the data will reflect on the map.
mounted() {
this.$refs.mapRef.$mapPromise.then(map => {
this.map = map;
});
},
This is how we wait for the map to be initialized before we can use methods like panTo.
panTo(position) {
if (!this.map) return;
this.map.panTo(position);
}
This will pan the map smoothly to the marker’s position, instead of centering abruptly. The condition deals with the edge case of clicking a marker before we can use pan.
Now let’s add our new Map component to the main dashboard layout.
In Dashboard.vue
, replace the contents of <v-content>
with the following:
<v-content>
<v-container fluid fill-height pa-0>
<v-layout
justify-center
align-center
>
<v-flex fill-height d-flex xs12>
<Map></Map>
</v-flex>
</v-layout>
</v-container>
</v-content>
Vuetify’s layout system is built around the hierarchy of: v-container -> v-layout -> v-flex
, with <v-layout>
functioning as rows, and <v-flex>
as colums.
In our case we want to position only one item - our map component - in the center of the container, so we use justify-center
and align-center
.
Because Vuetify works with a 12 column system, the xs12
sets the width of the cell to fill the <v-layout>
in all screen sizes (extra small and up).
The fill-height
attribute sets the height to fill the container.
To get rid of the padding, we use pa-0
(padding-all-0)
Finally import the Map component:
import Map from './Map';
export default {
components: {
Map,
},
...
}
And there you have it, a map component centered in the main container showing custom reactive markers.
The code for this part is available here.