Create a marker with a popup window

Open on 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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {LngLat, MMapMarker} from '@mappable-world/mappable-types';
      import {CUSTOM_POPUP, CUSTOM_POPUP_2, DEFAULT_POPUP, LOCATION, LONG_TEXT} from '../variables';

      window.map = null;

      main();
      async function main() {
          // Waiting for all api elements to be loaded
          await mappable.ready;
          const {MMapComplexEntity, MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} = mappable;

          // Import the package to add a default marker
          const {MMapDefaultMarker} = await mappable.import('@mappable-world/mappable-default-ui-theme');

          // Create a custom marker class with a popup
          interface CustomMarkerWithPopupProps {
              coordinates: LngLat; // marker position [lng, lat]
              popupContent: string;
              zIndex?: number;
              blockBehaviors?: boolean;
          }
          class CustomMarkerWithPopup extends MMapComplexEntity<CustomMarkerWithPopupProps> {
              private _marker: MMapMarker;
              private _popup: MMapMarker | null = null;

              // Handler for attaching the control to the map
              _onAttach() {
                  this._createMarker();
              }
              // Handler for detaching control from the map
              _onDetach() {
                  this._marker = null;
              }
              // Handler for updating marker properties
              _onUpdate(props: CustomMarkerWithPopupProps) {
                  if (props.zIndex !== undefined) {
                      this._marker?.update({zIndex: props.zIndex});
                  }
                  if (props.coordinates !== undefined) {
                      this._marker?.update({coordinates: props.coordinates});
                  }
              }
              // Method for creating a marker element
              _createMarker() {
                  const element = document.createElement('div');
                  element.className = 'marker';
                  element.onclick = () => {
                      this._openPopup();
                  };

                  this._marker = new MMapMarker({coordinates: this._props.coordinates}, element);
                  this.addChild(this._marker);
              }

              // Method for creating a popup window element
              _openPopup() {
                  if (this._popup) {
                      return;
                  }

                  const element = document.createElement('div');
                  element.className = 'popup';

                  const textElement = document.createElement('div');
                  textElement.className = 'popup__text';
                  textElement.textContent = this._props.popupContent;

                  const closeBtn = document.createElement('button');
                  closeBtn.className = 'popup__close';
                  closeBtn.textContent = 'Close Popup';
                  closeBtn.onclick = () => this._closePopup();

                  element.append(textElement, closeBtn);

                  const zIndex = (this._props.zIndex ?? MMapMarker.defaultProps.zIndex) + 1_000;
                  this._popup = new MMapMarker(
                      {
                          coordinates: this._props.coordinates,
                          zIndex,
                          // This allows you to scroll over popup
                          blockBehaviors: this._props.blockBehaviors
                      },
                      element
                  );
                  this.addChild(this._popup);
              }

              _closePopup() {
                  if (!this._popup) {
                      return;
                  }

                  this.removeChild(this._popup);
                  this._popup = null;
              }
          }

          // Initialize the map
          map = new MMap(
              // Pass the link to the HTMLElement of the container
              document.getElementById('app'),
              // Pass the map initialization parameters
              {location: LOCATION, showScaleInCopyrights: true},
              [
                  // Add a map scheme layer
                  new MMapDefaultSchemeLayer({}),
                  // Add a layer of geo objects to display the markers
                  new MMapDefaultFeaturesLayer({})
              ]
          );

          map
              // Add a default marker with a popup window from the package to the map
              .addChild(
                  new MMapDefaultMarker({
                      size: 'normal',
                      coordinates: DEFAULT_POPUP,
                      color: {day: '#006efc', night: '#006efc'},
                      popup: {content: 'Popup on the default marker', position: 'left'}
                  })
              )
              // Add a custom marker with a popup window to the map
              .addChild(
                  new CustomMarkerWithPopup({
                      coordinates: CUSTOM_POPUP,
                      popupContent: 'Popup on the custom marker (not scrollable)'
                  })
              )
              .addChild(
                  new CustomMarkerWithPopup({coordinates: CUSTOM_POPUP_2, popupContent: LONG_TEXT, blockBehaviors: true})
              );
      }
    </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="react, typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import type {LngLat} from '@mappable-world/mappable-types';
      import {LOCATION, LONG_TEXT} from '../variables';
      import {CUSTOM_POPUP, CUSTOM_POPUP_2, DEFAULT_POPUP} from '../variables';

      window.map = null;

      main();
      async function main() {
        // For each object in the JS API, there is a React counterpart
        // To use the React version of the API, include the module @mappable-world/mappable-reactify
        const [mappableReact] = await Promise.all([
          mappable.import('@mappable-world/mappable-reactify'),
          mappable.ready
        ]);
        const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
        const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} = reactify.module(mappable);
        const {useState} = React;

        // Import the package to add a default marker
        const {MMapDefaultMarker} = reactify.module(
          await mappable.import('@mappable-world/mappable-default-ui-theme')
        );

        function App() {
          return (
            // Initialize the map and pass initialization parameters
            <MMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
              {/* Add a map scheme layer */}
              <MMapDefaultSchemeLayer />
              {/* Add a layer of geo objects to display the markers */}
              <MMapDefaultFeaturesLayer />

              {/* Add a default marker with a popup window from the package to the map */}
              <MMapDefaultMarker
                size="normal"
                coordinates={DEFAULT_POPUP}
                color={{day: '#006efc', night: '#006efc'}}
                popup={{content: 'Popup on the default marker', position: 'left'}}
              />
              {/* Add a custom marker with a popup window to the map */}
              <CustomMarkerWithPopup
                coordinates={CUSTOM_POPUP}
                popupContent="Popup on the custom marker (not scrollable)"
              />
              <CustomMarkerWithPopup coordinates={CUSTOM_POPUP_2} popupContent={LONG_TEXT} blockBehaviors />
            </MMap>
          );
        }

        // Create a custom marker component with a popup
        interface CustomMarkerWithPopupProps {
          coordinates: LngLat; // marker position [lng, lat]
          popupContent: string;
          zIndex?: number;
          blockBehaviors?: boolean;
        }

        function CustomMarkerWithPopup(props: CustomMarkerWithPopupProps) {
          const [popupOpen, setPopupOpen] = useState(false);

          return (
            <>
              <MMapMarker coordinates={props.coordinates}>
                <div className="marker" onClick={() => setPopupOpen(true)}></div>
              </MMapMarker>

              {/**
               * Create or delete a popup using conditional rendering.
               * `MMapMarker.blockBehaviors` prop allows you to scroll over popup.
               */}
              {popupOpen && (
                <MMapMarker
                  coordinates={props.coordinates}
                  zIndex={(props.zIndex ?? mappable.MMapMarker.defaultProps.zIndex) + 1_000}
                  blockBehaviors={props.blockBehaviors}
                >
                  <div className="popup">
                    <div className="popup__text">{props.popupContent}</div>
                    <button className="popup__close" onClick={() => setPopupOpen(false)}>
                      Close Popup
                    </button>
                  </div>
                </MMapMarker>
              )}
            </>
          );
        }

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('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>
<!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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {CUSTOM_POPUP, CUSTOM_POPUP_2, DEFAULT_POPUP, LOCATION, LONG_TEXT} from '../variables';

      window.map = null;

      async function main() {
        // For each object in the JS API, there is a Vue counterpart
        // To use the Vue version of the API, include the module @mappable-world/mappable-vuefy
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} = vuefy.module(mappable);

        // Import the package to add a default marker
        const {MMapDefaultMarker} = vuefy.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));

        const CustomMarkerWithPopup = Vue.defineComponent({
          setup(props) {
            const popupOpen = Vue.ref(false);
            const togglePopup = () => {
              popupOpen.value = !popupOpen.value;
            };
            const zIndexCmp = Vue.computed(() => (props.zIndex ?? mappable.MMapMarker.defaultProps.zIndex) + 1_000);

            return {popupOpen, togglePopup, zIndexCmp};
          },
          props: {
            coordinates: Array,
            popupContent: String,
            zIndex: Number,
            blockBehaviors: Boolean
          },
          components: {MMapMarker},
          template: `
          <MMapMarker :coordinates="coordinates">
            <!-- Create or delete a popup using conditional rendering  -->
            <div class="marker" @click="togglePopup"></div>
          </MMapMarker>
          <MMapMarker v-if="popupOpen" :coordinates="coordinates" :zIndex="zIndexCmp" :blockBehaviors="blockBehaviors">
            <div class="popup">
              <div class="popup__text">{{ popupContent }}</div>
              <button class="popup__close" @click="togglePopup">Close Popup</button>
            </div>
          </MMapMarker>
        `
        });

        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapDefaultFeaturesLayer,
            MMapMarker,
            MMapDefaultMarker,
            CustomMarkerWithPopup
          },
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            return {refMap, LOCATION, LONG_TEXT, DEFAULT_POPUP, CUSTOM_POPUP, CUSTOM_POPUP_2};
          },
          template: `
          <MMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap">
            <!-- Add a map scheme layer -->
            <MMapDefaultSchemeLayer/>
            <!-- Add a layer of geo objects to display the markers -->
            <MMapDefaultFeaturesLayer/>

            <!-- Add a default marker with a popup window from the package to the map -->
            <MMapDefaultMarker
                size="normal"
                :coordinates="DEFAULT_POPUP"
                :color="{day: '#006efc', night: '#006efc'}"
                :popup="{content: 'Popup on the default marker', position: 'left'}"
            />
            <!-- Add a custom marker with a popup window to the map -->
            <CustomMarkerWithPopup :coordinates="CUSTOM_POPUP" popupContent="Popup on the custom marker (not scrollable)"/>
            <CustomMarkerWithPopup :coordinates="CUSTOM_POPUP_2" :popupContent="LONG_TEXT" blockBehaviors />
          </MMap>
        `
        });
        app.mount('#app');
      }
      main();
    </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>
.marker {
  width: 25px;
  height: 25px;

  cursor: pointer;

  border: 10px solid #006efc;
  border-radius: 50%;
  background-color: #fff;

  transform: translate(-50%, -50%);
}

.popup {
  position: absolute;

  display: flex;
  overflow: scroll;
  flex-direction: column;
  align-items: center;

  width: 150px;
  max-height: 150px;
  padding: 20px 25px;

  border: 2px solid #000;
  border-radius: 7px;
  background: #fff;
  box-shadow: 0 0 8px 0 #0003;

  transform: translate(30px, -220px);

  gap: 20px;

  overscroll-behavior: contain;
  touch-action: pan-y;
}

.popup__text {
  font-size: 20px;
  font-weight: 600;
  white-space: break-spaces;
}

.popup__close {
  padding: 7px;

  font-size: 16px;
  cursor: pointer;

  color: #fff;
  border: none;
  border-radius: 7px;
  outline: none;
  background-color: rgb(253, 100, 102);

  transition: background-color 0.2s;
}

.popup__close:hover {
  background-color: rgb(253, 80, 80);
}

.popup__close:active {
  background-color: rgb(253, 100, 102);
}
mappable.ready.then(() => {
  mappable.import.registerCdn('https://cdn.jsdelivr.net/npm/{package}', [
    '@mappable-world/mappable-default-ui-theme@latest'
  ]);
});
import type {MMapLocationRequest, LngLat} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [55.44279, 25.24613], // starting position [lng, lat]
  zoom: 9 // starting zoom
};

export const DEFAULT_POPUP: LngLat = [55.71971, 25.34359];
export const CUSTOM_POPUP: LngLat = [55.13971, 25.06359];
export const CUSTOM_POPUP_2: LngLat = [55.55971, 24.92359];

export const LONG_TEXT =
  'Popup on the custom marker (scrollable)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
{"node": 20, "template": "static"}