apprt-vue

This bundle provides support for the Vue.js framework to develop bundles that have a user interface following the MVVM pattern.

Usage

First, create a Vue component using apprt-vue/Vue based on a Vue file:

import Vue from "apprt-vue/Vue";
import MyVueComponent from "./MyVueComponent.vue";

let myVueComponent = new Vue(MyVueComponent);

To integrate the Vue component into a map.apps template, it needs to be a Dijit Widget. For this purpose you can use the apprt-vue/VueDijit as a wrapper around your Vue component:

import VueDijit from "apprt-vue/VueDijit";

let dijitWidget = VueDijit(myVueComponent);

See also the bundle's API documentation for more details.

Use Cases

Adding a custom CSS class to a VueDijit

To add a class to the root node of a VueDijit (in parallel with the class vue-base), use:

import Vue from "apprt-vue/Vue";
import MyVueComponent from "./MyVueComponent.vue";

let myVueComponent = new VueDijit(MyVueComponent, { class: "myAdditionalStyleClass" });

Interacting with reactive data (based on the reactivity API)

NOTE: useReactiveSnapshot() and the reactivity API are still actively being worked on. There may be breaking changes based on user feedback; stability can not yet be guaranteed.

Use useReactiveSnapshot to read reactive data (that uses the reactivity API) in a Vue component.

Simple example:

<script setup lang="ts">
import { useReactiveSnapshot } from "apprt-vue";
import { type CountModel } from "./somewhere";

// (1)
const props = defineProps<{
    model: CountModel;
}>();

// (2)
const snapshot = useReactiveSnapshot(() => {
    return {
        count: props.model.count
    };
});
</script>

<template>
    <!-- (3) -->
    <div>Current count: {{ snapshot.count }}</div>
</template>

The component renders the current .count of the model:

Things to keep in mind:

Updating data

useReactiveSnapshot intentionally provides one-way data binding only: whenever the reactive data changes, the vue component receives a current snapshot.

While you should refer to the current snapshot for display purposes, updating data should happen via writable properties or methods; just like from "normal" (non-Vue) code.

The following example renders and increments a counter when the button is clicked:

<script setup lang="ts">
import { useReactiveSnapshot } from "apprt-vue";
import { type CountModel } from "./somewhere";

const props = defineProps<{
    model: CountModel;
}>();

const snapshot = useReactiveSnapshot(() => {
    return {
        count: props.model.count
    };
});
</script>

<template>
    <div>
        Current count: {{ snapshot.count }}
        <!-- Note: calls a method on the model instead of changing `snapshot.count`!-->
        <button @click="props.model.incrementCount()">Increment</button>
    </div>
</template>

Passing models to Vue components

By default, Vue 2.x will deeply observe an entire object graph (the object and all objects reachable via references from that object) whenever a new object is being used in a (Vue-) reactive context. This applies in props, data, and, when using the composition API, reactive() and ref() etc.

To avoid correctness or performance issues, Vue 2.x must be prevented from seeing complex object structures, especially map.apps components or ArcGIS object: their implementation uses references heavily (sometimes in a circular fashion). For example, observing a single Graphic could end up observing the entire Map with all its child objects.

There are multiple workarounds:

Example (shallowRef)

The following snippet instantiates the Vue component shown above in Updating data.

import { shallowRef } from "vue";
import CountUI from "./CountUI.vue";
const CountUIComponent = Vue.extend(CountUI); // Creates a "real" Vue class, not always needed

const model = new CountModel(/* ... */); // definition not shown
const vm = new CountUIComponent({
    propsData: {
        // BAD: This would be wrong
        //model: model

        // GOOD: this works as expected
        model: shallowRef(model)
    }
});

You can verify this manually by inspecting the relevant objects at runtime in your debugger. When Vue 2.x observes an object, it adds the __ob__ property. If it's missing, you're good to go.

Create a binding between a Vue component and business models based on Bindable

To make synchronization easier between a Vue component and a component such as esri/core/Accessor, use the apprt-binding/Binding. To ensure that your Vue component supports the Bindable interface defined by apprt-binding, use the mixin apprt-vue/mixins/Bindable.

// in your .vue file <script> tag:

import Bindable from "apprt-vue/mixins/Bindable";

export default {
    ...
    mixins: [Bindable]
}

Now your component supports the required methods.

import Binding from "apprt-binding/Binding"
import MyVueComponent from "./MyVueComponent.vue";

let vm = new Vue(MyVueComponent);
let myaccessor = ...

Binding.for(myAccessor,vm)
    .sync("message", "input")
    .enable();

Integrate external content in your .vue file

To integrate external content into your VueWidget, provide a DOM node in your template with the component apprt-vue/CtDomNode as in the following sample:

<template>
    <ct-dom-node :node="domNode"></ct-dom-node>
</template>
<script>
    import CtDomNode from "apprt-vue/CtDomNode";

    export default {
        props: ["domNode"],
        components: {
            "ct-dom-node": CtDomNode
        }
    };
</script>

Now you can set the external DOM node to your component as in the following sample:

import Vue from "apprt-vue/Vue";
import MyComponentDefinition from "./MyComponent.vue";

const myComponent = new Vue(MyComponentDefinition);
myComponent.domNode = d_construct.create("span", { innerHTML: "Hello World!" });