Integration with Vue

Quick start

Warning

Supported Vue version: at least 3

Connecting via top-level-await

<!DOCTYPE html>
<html>
  <head>
    <!-- Substitute the value of the real key instead of YOUR_API_KEY -->
    <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
    <script type="module" src="index.ts"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
import {createApp} from 'vue';
import App from './App.vue';

createApp(App).mount('#app');
<script lang="ts" setup>
  import {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} from './lib/mappable';
  import type {MMapLocationRequest} from 'mappable';

  const LOCATION: MMapLocationRequest = {
    center: [25.229762, 55.289311],
    zoom: 9
  };
</script>

<template>
  <div style="width: 600px; height: 400px">
    <MMap :location="LOCATION">
      <MMapDefaultSchemeLayer />
      <MMapDefaultFeaturesLayer />

      <MMapMarker :coordinates="[25.229762, 55.289311]" :draggable="true">
        <section>
          <h1>You can drag this header</h1>
        </section>
      </MMapMarker>
    </MMap>
  </div>
</template>
import * as Vue from 'vue';

const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);

export const vuefy = mappableVue.vuefy.bindTo(Vue);
export const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} = vuefy.module(mappable);
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "typeRoots": ["./node_modules/@types", "./node_modules/@mappable-world/mappable-types"],
    "paths": {
      "mappable": ["./node_modules/@mappable-world/mappable-types"]
    }
  }
}
{
  "type": "module",
  "scripts": {
    "dev": "vite"
  },
  "devDependencies": {
    "@mappable-world/mappable-types": "^0.0.10",
    "vue": "^3.4.21",
    "@vitejs/plugin-vue": "^5.0.4",
    "typescript": "^4.9.5",
    "vite": "^5.2.8"
  }
}
import {defineConfig} from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler.js'
    }
  }
});

Install the dependencies and start the local server:

npm install
npm run dev

Open application

Specificities

  1. In the script tag that loads the compiled project js, specify the attribute type="module" to activate support for ECMAScript Modules (ESM) and top-level-await:

    <script type="module" src="index.ts"></script>
    
  2. In package.json adding a dev-dependency on the package @mappable-world/mappable-types.

    It is recommended to install the latest version:

    npm i --save-dev @mappable-world/mappable-types@latest

  3. In tsconfig.json we set compilerOptions.typeRoots with a list of paths to file types. Adding the path to the package @mappable-world/mappable-types there, so that the namespace mappable with types appears in the global scope.

    Note

    The namespace mappable contains all the class types that the JS API provides, but they are not available in the runtime until the resolving mappable.ready.

  4. In tsconfig.json we set compilerOptions.paths, in which we inform the ts compiler that when importing the package mappable, its content should be searched for in the specified path. Thanks to this, you can import types in project files as if they are not in the @mappable-world/mappable-types, but in the mappable package:

    import type {MMapLocationRequest} from 'mappable';
    

    All types must be imported from the root.

    The internal structure is not guaranteed and may change over time.

  5. In tsconfig.json, for top-level-await to work correctly, the parameter compilerOptions.module must be set to one of the following values: es2022, esnext, system or preserve. Also the compilerOptions.target parameter must be es2017 or higher.

  6. In vite.config.ts we set resolve.alias to use the "full" Vue build (vue/dist/vue.esm-bundler.js). This is necessary for the correct operation of the mappable Vue components.

  7. For each object in the JS API, there is a Vue analog. To use the Vue API version, connect the @mappable-world/mappable-vuefy module. In the lib/mappable.ts file, we wait for the JS API and vuefy module to be fully loaded, after which we export the necessary map components for use in other parts of the project:

    import * as Vue from 'vue';
    
    const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
    
    export const vuefy = mappableVue.vuefy.bindTo(Vue);
    export const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer} = vuefy.module(mappable);
    
  8. Using top-level-await in lib/mappable.ts guarantees the execution of mappable.ready and mappable.import('@mappable-world/mappable-vuefy') before importing map components, which allows you to synchronously use any JS API objects as Vue components:

    <MMap :location="LOCATION">
      <MMapDefaultSchemeLayer />
      <MMapDefaultFeaturesLayer />
      ...
    </MMap>
    

Specifying input props for your own classes

Vue components require explicit declaration of props so that Vue knows which of them should be treated as additional attributes.

For basic objects from the mappable namespace, the input parameters are defined by default, and no additional actions are required.

If you are a developer who creates your own classes (for example, within a separate package), then you can determine which input parameters will be in the component. The props definition supports the Vue format. For example, the custom class MMapSomeClass:

type MMapSomeClassProps = {
  id: string;
  counter?: number;
};
export class MMapSomeClass extends mappable.MMapComplexEntity<MMapSomeClassProps> {
  static [mappableVue.vuefy.optionsKey] = {props: ['id', 'counter']};
  //...
}

In addition to the array of strings, we can also use the object syntax:

export class MMapSomeClass extends mappable.MMapComplexEntity<MMapSomeClassProps> {
  static [mappableVue.vuefy.optionsKey] = {
    props: {
      id: String,
      counter: Number
    }
  };
  //...
}

or with validation of input parameters:

export class MMapSomeClass extends mappable.MMapComplexEntity<MMapSomeClassProps> {
  static [mappableVue.vuefy.optionsKey] = {
    props: {
      id: {type: String, required: true},
      counter: {type: Number, required: false}
    }
  };
  //...
}

Specifying input props when using third-party packages

If the developers of third-party packages haven't defined input parameters for their classes, you should specify them when calling vuefy yourself by passing them in as the second argument:

const mappableVue = await mappable.import('@mappable-world/mappable-vuefy');
const vuefy = mappableVue.vuefy.bindTo(Vue);
const {MMapSomeClass as MMapSomeClassV} = vuefy.module(
  {MMapSomeClass},
  {
    MMapSomeClass: ['id', 'counter'] // props for the MMapSomeClass
  }
);

Similarly, the object syntax and validations shown above are supported.

Custom implementations of objects mappable.MMapEntity for Vue

When the standard conversion is not sufficient, use the overrideKey key to specify your own implementation of vuefy:

type MMapSomeClassProps = {
  id: string;
  counter?: number;
};
/* object mappable.MMapEntity */
export class MMapSomeClass extends mappable.MMapComplexEntity<MMapSomeClassProps> {
  static [mappableVue.vuefy.overrideKey] = MMapSomeClassVuefyOverride;
  //...
}

MMapSomeClassVuefyOverride is the method that should return the Vue component. As parameters, it gets a base class, declared props, and an object with Vue and vuefy if their methods are required. In the example below, a wrapper MMapSomeClassV is created with additional logic around the component obtained by the basic vuefy method:

export const MMapSomeClassVuefyOverride: CustomVuefyFn<MMapSomeClass> = (
  MMapSomeClassI, // basic MMapSomeClass
  props, // declared props
  {vuefy, Vue}
) => {
  // Standard vuefy method
  const MMapSomeClassVuefied = vuefy.entity(MMapSomeClassI, props);
  const MMapSomeClassV = Vue.defineComponent({
    props,
    name: 'MMapSomeClassV',
    components: {MMapSomeClassVuefied},
    setup() {
      // additional logic of the user implementation
    },
    template: `<MMapSomeClassVuefied v-bind="$props" ... />`
  });
  return MMapSomeClassV;
};

the resulting component can be used in the application:

const mappableVue = await mappable.import('@mappable-world/mappable-vuefy');
const vuefy = mappableVue.vuefy.bindTo(Vue);
const MMapSomeClassV = vuefy.entity(MMapSomeClass);
const app = createApp({
  components: {MMapSomeClassV},
  template: `<MMapSomeClassV id="some_id" />`
});
app.mount('#app');