Adding a default popup on the map

Open in CodeSandbox

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {MMapPopupPositionProps} from '@mappable-world/mappable-default-ui-theme';
      import {ACTION, CUSTOM_POPUP_COORDS, DESCRIPTION, POPUP_TEXT, TEXT_POPUP_COORDS, TITLE} from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      main();
      async function main() {
        await mappable.ready;
        const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapControls, MMapControlButton} = mappable;

        const {MMapPopupMarker} = await mappable.import('@mappable-world/mappable-default-ui-theme');

        map = new MMap(document.getElementById('app'), {location: LOCATION});

        map.addChild(new MMapDefaultSchemeLayer({}));
        map.addChild(new MMapDefaultFeaturesLayer({}));

        const updatePositions = (position: MMapPopupPositionProps) => {
          textPopup.update({position});
          customPopup.update({position});
        };

        map.addChild(
          new MMapControls({position: 'top right'}, [
            new MMapControlButton({text: 'Left', onClick: () => updatePositions('left')}),
            new MMapControlButton({text: 'Left Top', onClick: () => updatePositions('left top')}),
            new MMapControlButton({text: 'Left Bottom', onClick: () => updatePositions('left bottom')}),
            new MMapControlButton({text: 'Bottom', onClick: () => updatePositions('bottom')}),
            new MMapControlButton({text: 'Top', onClick: () => updatePositions('top')}),
            new MMapControlButton({text: 'Right Top', onClick: () => updatePositions('right top')}),
            new MMapControlButton({text: 'Right Bottom', onClick: () => updatePositions('right bottom')}),
            new MMapControlButton({text: 'Right', onClick: () => updatePositions('right')})
          ])
        );

        const textPopup = new MMapPopupMarker({coordinates: TEXT_POPUP_COORDS, draggable: true, content: POPUP_TEXT});
        map.addChild(textPopup);

        const customPopup = new MMapPopupMarker({
          coordinates: CUSTOM_POPUP_COORDS,
          draggable: true,
          content: createDefaultPopup
        });
        map.addChild(customPopup);

        function createDefaultPopup(): HTMLElement {
          const popupRootElement = document.createElement('span');
          popupRootElement.classList.add('popup');

          const popupHeaderElement = document.createElement('span');
          popupHeaderElement.classList.add('header');
          popupRootElement.appendChild(popupHeaderElement);

          const titleElement = document.createElement('span');
          titleElement.classList.add('header_title');
          titleElement.textContent = TITLE;
          popupHeaderElement.appendChild(titleElement);

          const closeButton = document.createElement('button');
          closeButton.classList.add('header_close');
          closeButton.addEventListener('click', () => {
            customPopup.update({show: false});
          });
          popupHeaderElement.appendChild(closeButton);

          const descriptionElement = document.createElement('span');
          descriptionElement.classList.add('description');
          descriptionElement.textContent = DESCRIPTION;
          popupRootElement.appendChild(descriptionElement);

          const actionButton = document.createElement('button');
          actionButton.classList.add('action');
          actionButton.textContent = ACTION;
          actionButton.addEventListener('click', () => {
            alert('Click on action button!');
          });
          popupRootElement.appendChild(actionButton);

          return popupRootElement;
        }
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import type {MMapPopupPositionProps} from '@mappable-world/mappable-default-ui-theme';
      import {ACTION, CUSTOM_POPUP_COORDS, DESCRIPTION, POPUP_TEXT, TEXT_POPUP_COORDS, TITLE} from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      main();
      async function main() {
        const [mappableReact] = await Promise.all([
          mappable.import('@mappable-world/mappable-reactify'),
          mappable.ready
        ]);
        const reactify = mappableReact.reactify.bindTo(React, ReactDOM);

        const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapControls, MMapControlButton} =
          reactify.module(mappable);

        const {useState, useCallback} = React;

        const {MMapPopupMarker} = reactify.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('app')
        );

        function App() {
          const [position, setPosition] = useState < MMapPopupPositionProps > undefined;
          const [showCustom, setShowCustom] = useState(true);

          const positionLeft = useCallback(() => setPosition('left'), []);
          const positionLeftTop = useCallback(() => setPosition('left top'), []);
          const positionLeftBottom = useCallback(() => setPosition('left bottom'), []);
          const positionBottom = useCallback(() => setPosition('bottom'), []);
          const positionTop = useCallback(() => setPosition('top'), []);
          const positionRightTop = useCallback(() => setPosition('right top'), []);
          const positionRightBottom = useCallback(() => setPosition('right bottom'), []);
          const positionRight = useCallback(() => setPosition('right'), []);

          const customPopupContent = useCallback(
            () => (
              <span className="popup">
                <span className="header">
                  <span className="header_title">{TITLE}</span>
                  <button className="header_close" onClick={() => setShowCustom(false)}></button>
                </span>
                <span className="description">{DESCRIPTION}</span>
                <button className="action" onClick={() => alert('Click on action button!')}>
                  {ACTION}
                </button>
              </span>
            ),
            []
          );

          return (
            <MMap location={LOCATION} ref={(x) => (map = x)}>
              <MMapDefaultSchemeLayer />
              <MMapDefaultFeaturesLayer />
              <MMapControls position="top right">
                <MMapControlButton text="Left" onClick={positionLeft} />
                <MMapControlButton text="Left Top" onClick={positionLeftTop} />
                <MMapControlButton text="Left Bottom" onClick={positionLeftBottom} />
                <MMapControlButton text="Bottom" onClick={positionBottom} />
                <MMapControlButton text="Top" onClick={positionTop} />
                <MMapControlButton text="Right Top" onClick={positionRightTop} />
                <MMapControlButton text="Right Bottom" onClick={positionRightBottom} />
                <MMapControlButton text="Right" onClick={positionRight} />
              </MMapControls>
              <MMapPopupMarker coordinates={TEXT_POPUP_COORDS} draggable position={position} content={POPUP_TEXT} />
              <MMapPopupMarker
                coordinates={CUSTOM_POPUP_COORDS}
                draggable
                position={position}
                show={showCustom}
                content={customPopupContent}
              />
            </MMap>
          );
        }
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>

    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {MMapPopupPositionProps} from '@mappable-world/mappable-default-ui-theme';
      import {ACTION, CUSTOM_POPUP_COORDS, DESCRIPTION, POPUP_TEXT, TEXT_POPUP_COORDS, TITLE} from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      main();
      async function main() {
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);

        const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapControls, MMapControlButton} =
          vuefy.module(mappable);

        const {MMapPopupMarker} = vuefy.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));

        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapDefaultFeaturesLayer,
            MMapControls,
            MMapControlButton,
            MMapPopupMarker
          },
          setup() {
            const refMap = (ref: any) => {
              window.map = ref?.entity;
            };
            const position = Vue.ref < MMapPopupPositionProps > undefined;
            const showCustom = Vue.ref(true);

            const positionLeft = () => (position.value = 'left');
            const positionLeftTop = () => (position.value = 'left top');
            const positionLeftBottom = () => (position.value = 'left bottom');
            const positionBottom = () => (position.value = 'bottom');
            const positionTop = () => (position.value = 'top');
            const positionRightTop = () => (position.value = 'right top');
            const positionRightBottom = () => (position.value = 'right bottom');
            const positionRight = () => (position.value = 'right');

            const customPopupAction = () => {
              alert('Click on action button!');
            };

            return {
              ACTION,
              CUSTOM_POPUP_COORDS,
              DESCRIPTION,
              LOCATION,
              POPUP_TEXT,
              TEXT_POPUP_COORDS,
              TITLE,
              position,
              showCustom,
              refMap,
              positionLeft,
              positionLeftTop,
              positionLeftBottom,
              positionBottom,
              positionTop,
              positionRightTop,
              positionRightBottom,
              positionRight,
              customPopupAction
            };
          },
          template: `
            <MMap :location="LOCATION" :ref="refMap">
                <MMapDefaultSchemeLayer />
                <MMapDefaultFeaturesLayer />
                <MMapControls position="top right">
                    <MMapControlButton text="Left" :onClick="positionLeft" />
                    <MMapControlButton text="Left Top" :onClick="positionLeftTop" />
                    <MMapControlButton text="Left Bottom" :onClick="positionLeftBottom" />
                    <MMapControlButton text="Bottom" :onClick="positionBottom" />
                    <MMapControlButton text="Top" :onClick="positionTop" />
                    <MMapControlButton text="Right Top" :onClick="positionRightTop" />
                    <MMapControlButton text="Right Bottom" :onClick="positionRightBottom" />
                    <MMapControlButton text="Right" :onClick="positionRight" />
                </MMapControls>

                <MMapPopupMarker :coordinates="TEXT_POPUP_COORDS" :draggable="true" :position="position">
                    <template #content>
                        {{POPUP_TEXT}}
                    </template>
                </MMapPopupMarker>

                <MMapPopupMarker :coordinates="CUSTOM_POPUP_COORDS" :draggable="true" :position="position" :show="showCustom">
                    <template #content>
                        <span class="popup">
                            <span class="header">
                                <span class="header_title">{{TITLE}}</span>
                                <button class="header_close" @click="showCustom=false"></button>
                            </span>
                            <span class="description">{{DESCRIPTION}}</span>
                            <button class="action" @click="customPopupAction">
                                {{ACTION}}
                            </button>
                        </span>
                    </template>
                </MMapPopupMarker>
            </MMap>`
        });
        app.mount('#app');
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.popup {
  display: flex;
  flex-direction: column;
  row-gap: 8px;
}

.header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  gap: 8px;
  padding-right: 20px;

  .header_title {
    font-size: 16px;
    font-weight: 400;
    line-height: 22px;
    color: #050d33;
  }
  .header_close {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 32px;
    height: 32px;
    border: none;
    background: none;
    color: #c8c9cc;
    cursor: pointer;
    background-image: url('./close.svg');
    background-position: center;
    background-repeat: no-repeat;
  }
}

.description {
  font-size: 14px;
  font-weight: 400;
  line-height: 20px;
  color: #7b7d85;
}

.action {
  width: max-content;
  padding: 12px 16px;
  border-radius: 8px;
  border: none;
  background-color: #196dff;
  transition: background-color 0.1s ease-out;
  color: #ffffff;
  text-align: center;
  cursor: pointer;
  white-space: normal;

  font-size: 14px;
  font-style: normal;
  font-weight: 500;
  line-height: 16px;
}

.action:hover {
  background-color: #4183ff;
}
import type {LngLat} from '@mappable-world/mappable-types';
import {CENTER} from './variables';

mappable.ready.then(() => {
  mappable.import.registerCdn('https://cdn.jsdelivr.net/npm/{package}', [
    '@mappable-world/mappable-default-ui-theme@0.0'
  ]);
});

export const POPUP_TEXT = 'Default text popup';
export const CUSTOM_POPUP_COORDS: LngLat = [CENTER[0] - 0.02, CENTER[1]];
export const TEXT_POPUP_COORDS: LngLat = [CENTER[0] + 0.02, CENTER[1]];

export const TITLE = 'Default popup marker';
export const DESCRIPTION = 'Description for default popup';
export const ACTION = 'Make an action';
import type {LngLat, MMapLocationRequest} from '@mappable-world/mappable-types';

export const CENTER: LngLat = [55.442795, 25.24107];
export const LOCATION: MMapLocationRequest = {center: CENTER, zoom: 14};