How can I use a vue component to send data to the map view?

1889
3
05-20-2021 01:33 PM
BrianVan_Nostrand
New Contributor III

Hello,

 

I'm trying to use vue components to create custom dom elements that will be used as widgets to manipulate the mapview in an ESRI js app. I want to be able to write "widgetX.vue" components and import them into the vue that holds my webmap/mapview, then emit commands from those child vue components and have the webmap/mapview in the esri map vue component use those commands. Here's an example of what I mean. I'm imagining a widget that will toggle the basemap between "satellite" and "streets" by emitting one of those two strings from the "BasemapWidget.vue" component and then having the map and view in the "EsriMap.vue" component react to changes that are applied by binding the emitted message to a method in the "EsriMap.vue" template. This value change works in the app currently, but (I think due to the fact that the esri map is initialized in the mounted method) the map seems unable to "hear" these changing values. 

In this example, I have added an expand whose content should update to the value that is received from the emit in the basemap widget vue. What I expect to see here is that when the basemap widget component (imported from "./BasemapWidget.vue") is clicked, the value contained in the expand will update to the value emitted from that component. I know the issue isn't in communication between the two components, because I'm able to console.log that value when the "changeBasemap" method is available from OUTSIDE of mounted.

Does my pattern make any sense? Please help me ReneRubalcava.

You might be my only hope!

EsriMap.Vue, the parent component that will hold the basemap widget component:

 

<template>
  <button @click="updateExpandValue"/>
  <BasemapWidget @basemapChanged="updateExpandValue"/>
  <div id="map_view"></div>
</template>

<script lang="ts">
import { defineComponent,ref, onMounted, Component} from "vue";
import WebMap from "@arcgis/core/WebMap";
import MapView from "@arcgis/core/views/MapView";
import Bookmarks from "@arcgis/core/widgets/Bookmarks";
import Expand from "@arcgis/core/widgets/Expand";

import WsdotBasemap from "@/layers/WsdotBasemap";
import TrafficLayer from "@/layers/TrafficLayer";
import ParkRideLayer from "@/layers/ParkRideLayer";
import basemapWidget from "./BasemapWidget.vue"
import BasemapWidget from "./BasemapWidget.vue";

export default defineComponent({
  components: {BasemapWidget},
  /*data(){
    return{
      expandValue:""
    }
  },*/
  setup(){
    const expandValue = ref<string>("")
    const updateExpandValue=(value:string)=>{
      expandValue.value=value
      console.log(`expandValue updated to ${expandValue.value}`)
    }
    return{expandValue, updateExpandValue}
  },
 async mounted(){
      const webmap = new WebMap({
        //basemap: WsdotBasemap,
        basemap: "streets",
        layers: [TrafficLayer, ParkRideLayer],
      });
      const mapView = new MapView({
        container: "map_view", // https://v3.vuejs.org/api/instance-properties.html
        map: webmap,
        extent: {
          ymax: 6316025.98739708,
          xmin: -13911155.7073957,
          xmax: -12984203.1967109,
          ymin: 5704865.77272526,
          spatialReference: { wkid: 102100 },
        },
      });
      const bookmarks = new Bookmarks({
        view: mapView,
        editingEnabled: true,
      });

      const bookmarkExpand = new Expand({
        view: mapView,
        content: bookmarks,
        expanded: false,
      });
      let contentDiv= document.createElement("div")
      contentDiv.innerHTML=this.expandValue
      const testExpand = new Expand({
        view: mapView,
        content: contentDiv,
        expanded: true,
      })
      mapView.ui.add(bookmarkExpand, "top-right");
      mapView.ui.add(testExpand, "top-right");
    }
})
</script>
<style scoped>
@import "https://js.arcgis.com/4.19/@arcgis/core/assets/esri/themes/light/main.css";
div#map_view {
    padding: 0;
    margin: 0;
    height: 70%;
    width: 100%;
}
</style>

 

 

 

 

BasemapWidget.vue, the custom component that, when clicked, emits the value that I want to have the map pick up:

 

 

 

<template>
  <div class="basemapWidget" @click="changeBasemap">
      {{selectedBasemap}}
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import WebMap from "@arcgis/core/WebMap";

export default defineComponent({
    name:'BasemapWidget',
    emits:['basemapChanged'],
    props:{
        webMap: WebMap
    },
    setup(props,{emit}){
        console.log(props.webMap)
        let selectedBasemap = ref<string>("streets-vector")
        console.log(props.webMap)
        const changeBasemap = (e:Event) =>{
            selectedBasemap.value == "streets-vector"? selectedBasemap.value = "satellite" :  selectedBasemap.value = "streets-vector"
            emit('basemapChanged',selectedBasemap.value)
        }
        return {selectedBasemap, changeBasemap}
    }
})
</script>

<style scoped>
    .basemapWidget{
        width: 50px; height: 50px;background-color: brown;
    }
</style>

 

Am I completely off base here? It seems like none of ESRI's Vue patterns match neitehr the composition API pattern nor the options API pattern that I learned in the vue tutorials I followed. Any advice is welcome.

0 Kudos
3 Replies
ReneRubalcava
Frequent Contributor

Ok, if you are using Vue3, you're going to have some issues. Vue3, and even Vuex for Vue3, use Proxy to manage state. Today, native JavaScript Proxy has some issues with our underlying Accessor we use as core our JSAPI. To be fair, we had to implement Accessor long before Proxy was finalized and it allows us to do some amazing stuff for property watching, optimizing internal changes and more.

It is on our roadmap to update this in the future and make it compatible with native Proxy, but there is no timeline for this at the moment.

So for Vue3 apps, I would suggest using a simpler pojo module to hold any JSAPI related instances, and maybe expose methods for updating those, like the maps basemap. You can still manage state in Vue3, things like basemap strings, scale, zoom level, item numbers, counts, and so on, but actually updating JSAPI related stuff should be handled in a simpler module. Something like this, although that uses Vue2.

0 Kudos
JoshuaAbbott
New Contributor II

In these past 2 years have there been any developments on this?

0 Kudos
ReneRubalcava
Frequent Contributor

You can use shallowRef to prevent the aggressive proxy waterfall that vue uses.

https://vuejs.org/api/reactivity-advanced.html#shallowref

I haven't made a vue app in a while, but you can try this one.

https://github.com/odoe/vue-jsapi-calcite

Use npm ci to make sure is installs locked versions

Found another one.

https://github.com/odoe/nearby-app

Same thing, use npm ci to use locked versions

0 Kudos