[Vuejs]-How to use Vue components with Mapbox IControl

0๐Ÿ‘

โœ…

<template>
  <span :id="id" class="mapboxgl-ctrl mapboxgl-ctrl-group">
    <button type="button">
      <FitFeaturesIcon class="mapboxgl-ctrl-icon" aria-hidden="true" />
    </button>
  </span>
</template>

<script>
import FitFeaturesIcon from './FitFeaturesIcon'

const id = 'FitFeaturesControl'

export default {
  components: { FitFeaturesIcon },
  data: () => ({
    id
  })
}

export class CFitFeaturesControl {
  onAdd(map) {
    this._map = map
    this._container = document.getElementById(id)
    return this._container
  }
}
</script>

0๐Ÿ‘

I tried to to the same thing โ€“ use a Vue Component inside the MapBox Control. I had difficulties getting other solutions to work, so I eventually cooked up my own thing. This is far from perfect and I am sure there are better solutions to this.

I create a temporary Vue Instance that is used to render the component, in my case an icon (https://oh-vue-icons.js.org). The resulting DOM element is then returned and used by MapBox. My solution is based on this codepen.

import { createApp, h } from "vue";
import mapboxgl from "mapbox-gl";
import { OhVueIcon } from "oh-vue-icons";
import "@/icons";

export class MapboxGLButtonControl implements mapboxgl.IControl {
    icon: string;
    className = "";
    title = "";
    eventHandler = (e: MouseEvent) => {};
    activeToggle = false;

    map: mapboxgl.Map | undefined = undefined;
    container: HTMLDivElement | undefined = undefined;
    btn: HTMLButtonElement | undefined = undefined;

    constructor(
        icon: string,
        {
            className = "",
            title = "",
            eventHandler = (e: MouseEvent) => {},
            activeToggle = false,
        }
    ) {
        this.icon = icon;
        this.className = className;
        this.title = title;
        this.eventHandler = eventHandler;
        this.activeToggle = activeToggle;
    }

    onAdd(map: mapboxgl.Map) {
        this.map = map;

        const { title, icon, className, eventHandler, activeToggle } = this;

        this.container = document.createElement("div");
        this.container.className = "mapboxgl-ctrl-group mapboxgl-ctrl";

        const tempApp = createApp({
            render() {
                this.btn = h(
                    "button",
                    {
                        class: "mapbox-gl-draw_ctrl-draw-btn" + " " + className,
                        type: "button",
                        title: title,
                        onClick: (e: MouseEvent) => {
                            if (activeToggle && e.target) {
                                this.btn.el?.classList.toggle("btn-active");
                            }

                            eventHandler(e);
                        },
                    },
                    h(OhVueIcon, {
                        name: icon,
                    })
                );

                return this.btn;
            },
        });
        const mountedApp = tempApp.mount(this.container);
        return this.container;
    }

    onRemove() {
        if (this.container && this.container.parentNode) {
            this.container.parentNode.removeChild(this.container);
        }

        this.map = undefined;
        this.container = undefined;
        this.btn = undefined;
    }
}

Leave a comment