I’ve got a problem with my VueJS project. I made a GoogleMaps Component within a DetailView.vue. The maps component uses three props like driver, waypoints and stopOver.
The project structure is:
App.vue.js containts Login.vue.js with a route to the main Dashboard View.
Inside this MainView, there is some menu elements like ‘Tours’ with a DataTable inside.
Clicking on the datatable row, VueJS presents a DetailView.vue.js inside a v-dialog (using Vuetify).
Inside this DetailView theres another datatable, a switch and at the top, my map component.
<LiefermaxMap style=”width: 100%; height: 50%;” :waypoints=”waypoints” :driver=”driverLocation” :stopOver=”showCalculatedTour”></LiefermaxMap>
waypoints and driverLocation are computed properties, showCalculatedTour is a regular property, depending on the state of the switch.
Closing this Detailvue.js Dialog and opening another tour results in pushing another set of waypoints (and driverLocation too) to the property instead of replacing the old ones.
waypoints() {
var points = [];
if (!this.showCalculatedTour) {
this.loadnoSpecific.items.items.forEach(element => {
var p = { lat: element.latitude, lng: element.longitude, done: element.done };
points.push(p);
});
} else {
this.data.contracts.forEach(element => {
var c = element.customer.position;
var p = { lat: c.lat, lng: c.lng, done: element.done }
points.push(p);
});
}
console.log(‘points: ‘ + points)
return points;
},
driverLocation() {
const drv = this.data.location;
const marker = { lat: drv.latitude, lng: drv.longitude };
return marker;
},
Heres a screenshot of the project.
What I’m doing wrong?
UPDATE:
Expected behavior: remove all the other waypoints from the previous selected tours
Actual behavior: waypoints are added to the old ones
Tours.vue.js Example (the view behind the dialog in the screenshot):
<template>
<div>
<v-toolbar flat color=”white”>
<v-toolbar-title>Touren</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-data-table style=”width: 70vw;”
:headers=”headers”
:items=”tours.items.items”
:rows-per-page-items=”[5,10,20,50,100]“
:loading=”isLoading”
:total-items=”totalItems”
:must-sort=”true”
:pagination.sync=”pagination”
class=”elevation-1”
\>
<template slot=”items” slot-scope=”props”>
<tr @click=”rowClicked(props.item)”>
<td class=”text-xs-left”>{ props.item.loadno }</td>
<td class=”text-xs-left”>{ props.item.tourname }</td>
<td class=”text-xs-left”>{ props.item.kfz }</td>
<td class=”text-xs-left”>{ props.item.driverID }</td>
<td class=”text-xs-left”>{ props.item.device }</td>
<td class=”text-xs-left”>{ props.item.created_at | formatDate }</td>
</tr>
</template>
</v-data-table>
<v-dialog v-model=”showDetail” transition=”dialog-bottom-transition” max-width=”70vw”>
<TourDetail v-model=”showDetail” :data=”selectedTour” style=”height: 90vh;”></TourDetail>
</v-dialog>
</div>
</template>
<script>
import { settings } from ‘../settings’;
import { authHeader } from ‘../_helpers’;
import TourDetail from ‘./TourDetail.vue’;
export default {
name: ‘tours’,
components: {
TourDetail
},
data() {
return {
totalItems: 0,
selectedTour: {},
showDetail: false,
pagination: {
descending: true,
page: 1,
rowsPerPage: 10,
sortBy: ‘loadno’
},
headers: [ // .. ]
};
},
watch: {
pagination(newValue, oldValue) {
this.getDataFromApi();
},
tours(newValue, oldValue) {
this.totalItems = newValue.items.total;
}
},
mounted() {
this.getDataFromApi();
},
computed: {
tours() {
return this.$store.state.tours.all;
},
isLoading() {
return this.$store.state.tours.loading;
}
},
methods: {
rowClicked(tour) {
const requestOptions = {
method: ‘GET’,
headers: authHeader()
};
const loadno = tour.loadno;
console.log(‘LoadNo: ‘ + loadno);
this.$store.dispatch(‘locations/getLoadnoSpecific’, { loadno });
fetch(
`${settings.apiUrl}/tours/loadno/complete/` + loadno,
requestOptions
).then(response =>
response.json().then(json => {
this.selectedTour = json.tour;
this.showDetail = true;
})
);
},
getDataFromApi() {
const perpage = this.pagination.rowsPerPage;
const page = this.pagination.page;
const sortby = this.pagination.sortBy;
const descending = this.pagination.descending;
this.$store.dispatch(‘tours/getAll’, {
perpage,
page,
sortby,
descending
});
}
}
};
</script>
TourDetail.vue
<template>
<v-flex style=”height: 100%;”>
<v-toolbar
color=”primary”
dark
flat
style=”z-index: 2; position: absolute;”
\>
<v-toolbar-title class=”justify-center”>Fahrer: { data.driver.firstname + ‘ ‘ + data.driver.lastname } | iPad: { data.device } | Fahrzeug: { data.kfz }</v-toolbar-title>
</v-toolbar>
<v-card style=”height: 100%; position: relative;”>
<LiefermaxMap style=”width: 100%; height: 50%;” :waypoints=”waypoints” :driver=”driverLocation” :stopOver=”showCalculatedTour”></LiefermaxMap>
<v-layout row wrap style=”height: 50%;”>
<v-flex xs12>
<v-card style=”height: 100%;”>
<v-switch v-model=”showCalculatedTour” :label=”switchLabel” style=”position: absolute; right: 10%;”></v-switch>
<v-data-table
:headers=”headers”
:items=”data.contracts”
:expand=”expand”
:rows-per-page-text=”perPageText”
style=”padding: 5%;”
\>
<template slot=”items” slot-scope=”props”>
…
</template>
<template slot=”expand” slot-scope=”props”>
<v-card>
<div>
…
</div>
</v-card>
</template>
</v-data-table>
</v-card>
</v-flex>
</v-layout>
</v-card>
</v-flex>
</template>
<script>
import LiefermaxMap from ‘../components/LiefermaxMap.vue’;
export default {
components: {
LiefermaxMap
},
computed: {
loadnoSpecific() {
return this.$store.state.locations.loadnoSpecific;
},
waypoints() {
var points = [];
if (!this.showCalculatedTour) {
this.loadnoSpecific.items.items.forEach(element => {
var p = { lat: element.latitude, lng: element.longitude, done: element.done };
points.push(p);
});
} else {
this.data.contracts.forEach(element => {
var c = element.customer.position;
var p = { lat: c.lat, lng: c.lng, done: element.done }
points.push(p);
});
}
console.log(‘points: ‘ + points)
return points;
},
driverLocation() {
const drv = this.data.location;
const marker = { lat: drv.latitude, lng: drv.longitude };
return marker;
},
switchLabel() {
return this.showCalculatedTour ? ‘errechnete Route’ : ‘gefahrene Route’;
}
},
props: [‘data’],
data() {
return {
showCalculatedTour: false,
googleWaypoints: [],
drivenWaypoints: [],
expand: false,
perPageText: ‘pro Seite’,
completeTourLoading: true,
completeTour: [{}],
selectedContract: {},
headers: [… ]
};
},
methods: { … }
}
};
</script>
<style scoped>
// …
</style>
And LiefermaxMap component:
<template>
<div v-show=”mapInitialized” class=”liefermaxmap” id=”liefermaxmap” ref=”liefermaxmap”></div>
</template>
<script>
export default {
name: ‘LiefermaxMap’,
props: [‘waypoints’, ‘driver’, ‘stopOver’],
data: function() {
return {
mapName: ‘liefermaxmap’,
markerCoordinates: [{}],
mapInitialized: false,
directionsService: null,
directionsDisplay: null,
vueGMap: null,
bounds: null,
markers: []
};
},
watch: {
waypoints(newValue, oldValue) {
console.log(‘Waypoints changed, count: ‘ + newValue.length);
console.log(newValue)
this.setWaypoints();
}
},
methods: {
createGoogleMaps: function() {
return new Promise((resolve, reject) => {
let gmap = document.createElement(‘script’);
gmap.src =
‘https://maps.googleapis.com/maps/api/js?key=XXXX';
gmap.type = ‘text/javascript’;
gmap.onload = resolve;
gmap.onerror = reject;
document.querySelector(‘head’).appendChild(gmap);
});
},
initGoogleMaps: function() {
const localOptions = {
zoom: 4,
center: this.driver,
mapTypeId: google.maps.MapTypeId.ROADMAP,
panControl: true,
mapTypeControl: true,
mapTypeControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM
},
panControlOptions: {
position: google.maps.ControlPosition.RIGHT_CENTER
},
zoomControl: true,
zoomControlOptions: {
style: google.maps.ZoomControlStyle.LARGE,
position: google.maps.ControlPosition.RIGHT_CENTER
},
scaleControl: false,
streetViewControl: false,
streetViewControlOptions: {
position: google.maps.ControlPosition.RIGHT_CENTER
}
};
this.vueGMap = new google.maps.Map(
this.$refs[‘liefermaxmap’],
localOptions
);
// eslint-disable-next-line
var drivermarker = new google.maps.Marker({
position: new google.maps.LatLng(this.driver),
map: this.vueGMap,
icon: ‘http://maps.google.com/mapfiles/ms/micons/truck.png'
});
this.directionsService = new google.maps.DirectionsService();
this.directionsDisplay = new google.maps.DirectionsRenderer();
this.directionsDisplay.setMap(this.vueGMap);
this.mapInitialized = true;
},
addMarker(latLng, color) {
let url = ‘http://maps.google.com/mapfiles/ms/icons/';
url += color + ‘-dot.png’;
let marker = new google.maps.Marker({
map: this.vueGMap,
position: latLng,
icon: {
url: url
}
});
},
setWaypoints() {
this.directionsService = new google.maps.DirectionsService();
this.directionsDisplay = new google.maps.DirectionsRenderer();
this.directionsDisplay.setMap(this.vueGMap);
var googleWaypoints = [];
this.waypoints.forEach(element => {
var pos = new google.maps.LatLng(element.lat, element.lng);
var wp = { location: pos, stopover: false };
googleWaypoints.push(wp);
console.log(element)
if (element.done == true) {
this.addMarker(pos, ‘green’)
} else {
this.addMarker(pos, ‘red’)
}
});
var i = 0;
var j = 0;
var temparray = [];
var chunk = 15;
for (i = 0, j = googleWaypoints.length; i < j; i += chunk) {
temparray = googleWaypoints.slice(i, i + chunk);
var request = {
origin: ‘XXX’,
destination: ‘XXX’,
travelMode: ‘DRIVING’,
optimizeWaypoints: true,
waypoints: temparray
};
this.directionsService.route(request, (result, status) => {
if (status === ‘OK’) {
this.directionsDisplay.setDirections(result);
console.log(result);
}
});
}
},
googleMapsFailedToLoad() {
this.vueGMap = ‘Error while loading map’;
}
},
mounted() {
this.initGoogleMaps();
}
};
</script>
<style scoped>
.liefermaxmap {
width: 100%;
height: 100%;
}
</style>
Solution :
vue.js reuses components - use key to force mounting
Change
<LiefermaxMap style=”width: 100%; height: 50%;” :waypoints=”waypoints” :driver=”driverLocation” :stopOver=”showCalculatedTour”></LiefermaxMap>
to
<LiefermaxMap style=”width: 100%; height: 50%;” :key=”waypoints” :waypoints=”waypoints” :driver=”driverLocation” :stopOver=”showCalculatedTour”></LiefermaxMap>
use key to tell vue.js not to reuse the component - so mounted can be triggered,
or move the initMap method.