Hello all. I have a Vue project in which we are plotting markers shaped as pins. Part of the requirement is that the markers must point to the location rather than be centered on top of it. So we're using an offset to reposition the markers and then to reposition the popup so it's on the top center of the marker. On initial click of the marker, everything works fine, but when I try to zoom to the marker, the map zooms, but then gets the following error:
aA {name: 'view:goto-interrupted', details: undefined, message: 'Goto was interrupted'}
details: undefined
message: "Goto was interrupted"
name: "view:goto-interrupted"
[[Prototype]]: oA
I have tried to solve it in a few different ways, but this is the current method. Can anyone help me figure out what's going wrong? (Or if there's a better way to achieve my objective as a whole...) Here's the code:
<!-- eslint-disable no-console -->
<template>
<div
id="map"
style="width: 100%;"
/>
</template>
<script>
import Map from '@arcgis/core/Map';
import MapView from '@arcgis/core/views/MapView';
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer';
import CustomContent from '@arcgis/core/popup/content/CustomContent';
import * as reactiveUtils from '@arcgis/core/core/reactiveUtils';
import Graphic from '@arcgis/core/Graphic';
import Point from '@arcgis/core/geometry/Point';
import { createVNode, render } from 'vue';
import MarkerSVG from '../../assets/marker.svg?url';
import PopupTemplateHeader from './PopupTemplateHeader.vue';
import PopupTemplateContent from './PopupTemplateContent.vue';
export default {
data() {
return ({
selectedMarkers: [],
animationState: ''
});
},
watch: {
'$store.list.data.records': {
handler() {
this.$nextTick(this.plotRecords);
},
immediate: true
}
},
created() {
this.$mitt.on('open-popup', (id) => {
const graphic = this.layer.graphics.items.find((i) => i.attributes.record.id === id);
this.openPopup([graphic]);
});
this.$mitt.on('close-popup', this.clearPopup);
},
mounted() {
window.MapComponent = this;
this.map = new Map({
basemap: 'streets-navigation-vector',
customParameters: { type: 'map' }
});
this.view = new MapView({
map: this.map,
popup: {
collapseEnabled: false,
dockOptions: {
buttonEnabled: false
},
highlightEnabled: false,
alignment: 'top-center',
viewModel: {
autoCloseEnabled: false,
includeDefaultActions: true
}
},
center: [-98.805, 38.027], // lng, lat
zoom: 4,
container: 'map',
constraints: {
minZoom: 2,
maxZoom: 15,
rotationEnabled: false
}
});
// Move zoom toggles to bottom right
this.view.ui.move(['zoom'], 'bottom-right');
this.view.on('click', (event) => {
// do not let Esri handle the click event
event.stopPropagation();
this.view.hitTest(event).then(({ results }) => {
const markers = results.filter((r) => r.graphic.symbol?.type === 'picture-marker').map((f) => f.graphic);
if (markers.length) {
this.openPopup(markers);
} else {
this.clearPopup();
}
});
});
reactiveUtils.on(
() => this.view.popup,
"trigger-action",
(event) => {
if (event.action.id === 'zoom-to-feature') {
this.openPopup([this.view.popup.selectedFeature], true);
}
else {
console.log('EVENT ID: ', event.action.id);
console.log('EVENT: ', event);
console.log('GEOMETRY: ', this.view.popup.selectedFeature.geometry);
}
});
// whenever the popup title changes, render the custom header content and inject it
reactiveUtils.when(() => this.view.popup?.title, () => {
const interval = setInterval(() => {
const container = this.$el.querySelector(`div[id="${this.view.popup.titleId}"] .maps-popup-header`);
if (container) {
const recordId = container.classList[1];
const record = this.$store.list.data.records.find((r) => r.id === recordId);
if (record) {
const header = createVNode(PopupTemplateHeader, { columns: this.$store.list.info.displayColumns, record, addressFieldsArray: this.$store.addressFieldsArray });
render(header, document.createElement('div'));
container.innerHTML = header.el.innerHTML;
}
clearInterval(interval);
}
}, 10);
});
reactiveUtils.when(() => this.view.animation, (animation) => {
this.animationState = animation.state; // Should be 'running'
animation.when((animation) => {
this.animationState = animation.state; // Should be 'finished'
})
.catch((animation) => {
this.animationState = animation.state; // should be 'stopped'
});
});
},
methods: {
async openPopup(markers, zoomIn = false) {
// clear any existing popup
if (this.view.popup.visible) {
this.clearPopup();
}
// pan the map center to the first marker
this.panTo(markers[0].geometry, zoomIn);
markers.forEach((marker) => {
const { record } = marker.attributes;
const recordTitle = record.fields?.Name?.displayValue ?? record.fields?.Name?.value ?? '';
marker.popupTemplate = {
title: `<div class="maps-popup-header ${record.id}">${recordTitle}</div>`,
content: [new CustomContent({
creator: () => {
// build content
const content = createVNode(PopupTemplateContent, { columns: this.$store.list.info.displayColumns, record });
render(content, document.createElement('div'));
return content.el;
}
})],
overwriteActions: true,
actions: [
{
id: 'zoom-to-feature',
className: 'esri-icon-zoom-in-magnifying-glass',
icon: 'magnifying-glass-plus',
type: 'button',
visible: true,
title: "{messages.zoom}"
}
]
};
});
this.selectedMarkers = [ ...markers ];
const location = this.createOffsetPoint(markers[0].geometry);
if (this.animationState === 'finished') {
this.view.openPopup({
features: markers,
location
});
}
},
clearPopup() {
this.view?.closePopup();
if (this.view.popup.clear) {
this.view.popup.features.forEach((feature) => {
delete feature.popupTemplate;
});
this.view.popup.clear();
}
},
async panTo(target, zoomIn) {
const offset = this.createOffsetPoint(target, 'zoom');
const zoom = zoomIn ? this.view.zoom + 4 : this.view.zoom;
// use modified extent center as target
return await this.view.goTo(
{ geometry: offset, zoom },
{ duration: 750 }
)
},
plotRecords() {
// clear any existing popups before plotting
this.clearPopup();
if (this.layer) {
this.map.layers.remove(this.layer);
}
const latField = this.$store.latLongFields.lat;
const longField = this.$store.latLongFields.long;
const graphics = this.$store.list.data.records?.reduce((arr, record) => {
if (this.$store.hasGeo(record)) {
arr.push(
new Graphic({
attributes: { record },
geometry: {
type: 'point', // autocasts as new Point()
longitude: record.fields[longField].value,
latitude: record.fields[latField].value
},
symbol: {
type: 'picture-marker', // autocasts as new SimpleMarkerSymbol()
url: import.meta.env.MODE === 'development' ? `https://localhost:5173${MarkerSVG}` : MarkerSVG,
height: 26,
width: 20,
yoffset: 13
}
})
);
}
return arr;
}, []);
if (graphics?.length) {
this.layer = new GraphicsLayer({ graphics });
this.map.layers.add(this.layer);
this.view.goTo(graphics);
}
},
createOffsetPoint(recordPoint, type = 'popup') {
// Convert to screen point to get location of x and y in pixels
const screenPoint = this.view.toScreen(recordPoint);
// Subtract 26 to offset the marker and its offset
screenPoint.y = type === 'popup' ? screenPoint.y - 26 : screenPoint.y - 100;
// Convert offset screenpoint to regular point to use in Graphic.
return this.view.toMap({ ...screenPoint });
}
}
};
</script>
<style>
.esri-popup__main-container {
border-radius: 5px;
}
.esri-popup__header {
border-bottom: 1px solid #dddd;
margin-bottom: 10px;
padding-top: 3px;
}
.esri-popup__button:hover {
background-color: unset;
}
.esri-popup__content {
margin-bottom: 0;
}
.esri-feature__content-element {
margin-bottom: 0 !important;
}
.esri-popup--aligned-top-center .esri-popup__pointer-direction, .esri-popup--aligned-bottom-center .esri-popup__pointer-direction {
transform: scale(1, 1.1) rotate(45deg) !important;
}
</style>
I have not worked in Vue, so I am going to use React terms. Most likely your issue is caused by altering the state during the goTo animation causing the component to reload. Does that make sense in a Vue context?