Create a marker with a popup window

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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></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="typescript" type="text/babel">
      import {FIRST_MARKER_PROPS, LOCATION, SECOND_MARKER_PROPS} from '../variables';
      import {InfoMessage} from './common';

      window.map = null;

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

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

        let popupWithImage = null;
        let popupWithButtons = null;

        // Create a custom popup
        const PopupWithImage = () => {
          const popupElement = document.createElement('div');
          popupElement.classList.add('popup');

          const imageElement = document.createElement('img');
          imageElement.src = '../waves.png';
          imageElement.alt = 'waves';
          imageElement.classList.add('image');

          const popupContentElement = document.createElement('div');
          popupContentElement.classList.add('popup__content');

          const popupElementText = document.createElement('div');
          popupElementText.classList.add('popup__text');

          const popupElementTextTitle = document.createElement('div');
          popupElementTextTitle.classList.add('popup__text_title');
          popupElementTextTitle.textContent = 'Title of that pop up';
          popupElementText.appendChild(popupElementTextTitle);

          const popupElementTextContent = document.createElement('div');
          popupElementTextContent.classList.add('popup__text_content');
          popupElementTextContent.textContent =
            'Some useful information about a place. You can add whatever you want: pictures, buttons, different headings.';
          popupElementText.appendChild(popupElementTextContent);

          const buttonElement = document.createElement('button');
          buttonElement.onclick = () => {
            popupWithImage.update({
              popup: {
                show: false
              }
            });
          };
          buttonElement.classList.add('button');
          buttonElement.textContent = 'Close';

          popupContentElement.appendChild(imageElement);
          popupContentElement.appendChild(popupElementText);

          popupElement.appendChild(popupContentElement);
          popupElement.appendChild(buttonElement);

          return popupElement;
        };

        // Create a custom popup
        const PopupWithButtons = () => {
          const popupElement = document.createElement('div');
          popupElement.classList.add('popup', 'second_variant');

          const popupElementText = document.createElement('div');
          popupElementText.classList.add('popup__text');

          const popupElementTextTitle = document.createElement('div');
          popupElementTextTitle.classList.add('popup__text_title');
          popupElementTextTitle.textContent = 'Title of that pop up';
          popupElementText.appendChild(popupElementTextTitle);

          const popupElementTextContent = document.createElement('div');
          popupElementTextContent.classList.add('popup__text_content');
          popupElementTextContent.textContent =
            'Some useful information about a place. You can add whatever you want: pictures, buttons, different headings.';
          popupElementText.appendChild(popupElementTextContent);

          const popupButtonsElement = document.createElement('div');
          popupButtonsElement.classList.add('popup__buttons');

          const popupButtonsBlockElement = document.createElement('div');
          popupButtonsBlockElement.classList.add('popup__buttons__block');

          const buttonElementFirst = document.createElement('button');
          buttonElementFirst.classList.add('button');
          buttonElementFirst.textContent = 'Button 1';
          buttonElementFirst.onclick = () => {
            alert('Clicked!');
          };

          const buttonElementSecond = document.createElement('button');
          buttonElementSecond.classList.add('button');
          buttonElementSecond.textContent = 'Button 2';
          buttonElementSecond.onclick = () => {
            alert('Clicked!');
          };

          popupButtonsBlockElement.appendChild(buttonElementFirst);
          popupButtonsBlockElement.appendChild(buttonElementSecond);

          const buttonElement = document.createElement('button');
          buttonElement.classList.add('button_close');
          buttonElement.textContent = 'Primary Button';
          buttonElement.onclick = () => {
            alert('Clicked!');
          };

          const closeIconElement = document.createElement('button');
          closeIconElement.classList.add('close_icon');
          closeIconElement.onclick = () => {
            popupWithButtons.update({
              popup: {
                show: false
              }
            });
          };

          popupButtonsElement.appendChild(popupButtonsBlockElement);
          popupButtonsElement.appendChild(buttonElement);

          popupElement.appendChild(popupElementText);
          popupElement.appendChild(popupButtonsElement);
          popupElement.appendChild(closeIconElement);

          return popupElement;
        };

        // 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({})
          ]
        );

        popupWithImage = new MMapDefaultMarker({
          ...FIRST_MARKER_PROPS,
          onClick() {
            popupWithImage.update({popup: {show: true}});
          },
          popup: {content: PopupWithImage, position: 'right'}
        });

        popupWithButtons = new MMapDefaultMarker({
          ...SECOND_MARKER_PROPS,
          onClick() {
            popupWithButtons.update({popup: {show: true}});
          },
          popup: {content: PopupWithButtons, position: 'right'}
        });

        map
          // Add a default marker with a popup window from the package to the map
          .addChild(popupWithImage)
          .addChild(popupWithButtons);

        map.addChild(
          // Using MMapControls you can change the position of the control
          new MMapControls({position: 'top left'})
            // Add the geolocation control to the map
            .addChild(
              new InfoMessage({
                text: 'Click on markers'
              })
            )
        );
      }
    </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" />
    <link rel="stylesheet" href="../variables.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="./common.ts"
    ></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">
      import {FIRST_MARKER_PROPS, SECOND_MARKER_PROPS, LOCATION} from '../variables';
      import {InfoMessage} from './common';

      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, MMapControls} = reactify.module(mappable);
        const {useState} = React;

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

        function App() {
          const [showPopupWithImage, setShowPopupWithImage] = useState(false);
          const [showPopupWithButtons, setShowPopupWithButtons] = useState(false);

          function PopupWithImage() {
            return (
              <div className="popup">
                <img src="../waves.png" alt="waves" className="image" />

                <div className="popup__content">
                  <div className="popup__text">
                    <div className="popup__text_title">Title of that pop up</div>
                    <div className="popup__text_content">
                      Some useful information about a place. You can add whatever you want: pictures, buttons,
                      different headings.
                    </div>
                  </div>
                  <button className="button" onClick={() => setShowPopupWithImage(false)}>
                    Close
                  </button>
                </div>
              </div>
            );
          }

          function PopupWithButtons() {
            return (
              <div className="popup second_variant">
                <div className="popup__text">
                  <div className="popup__text_title">Title of that pop up</div>
                  <div className="popup__text_content">
                    Some useful information about a place. You can add whatever you want: pictures, buttons, different
                    headings.
                  </div>
                </div>
                <div className="popup__buttons">
                  <div className="popup__buttons__block">
                    <button className="button" onClick={() => alert('Clicked!')}>
                      Button 1
                    </button>
                    <button className="button" onClick={() => alert('Clicked!')}>
                      Button 2
                    </button>
                  </div>
                  <button className="button_close" onClick={() => alert('Clicked!')}>
                    Primary Button
                  </button>
                </div>
                <button className="close_icon" onClick={() => setShowPopupWithButtons(false)} />
              </div>
            );
          }

          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
                popup={{
                  content: PopupWithImage,
                  position: 'right',
                  show: showPopupWithImage
                }}
                onClick={() => setShowPopupWithImage(true)}
                {...FIRST_MARKER_PROPS}
              />

              <MMapDefaultMarker
                popup={{
                  position: 'right',
                  content: PopupWithButtons,
                  show: showPopupWithButtons
                }}
                onClick={() => setShowPopupWithButtons(true)}
                {...SECOND_MARKER_PROPS}
              />

              <MMapControls position="top left">
                <InfoMessageR text="Click on markers" />
              </MMapControls>
            </MMap>
          );
        }

        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" />
    <link rel="stylesheet" href="../variables.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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></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="typescript" type="text/babel">
      import {FIRST_MARKER_PROPS, SECOND_MARKER_PROPS, LOCATION} from '../variables';
      import {InfoMessage} from './common';

      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, MMapControls} =
          vuefy.module(mappable);

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

        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapDefaultFeaturesLayer,
            MMapMarker,
            MMapControls,
            MMapDefaultMarker,
            InfoMessageV
          },
          setup() {
            const popupWithImageOpen = Vue.ref(false);
            const popupWithButtonsOpen = Vue.ref(false);

            const refMap = (ref) => {
              window.map = ref?.entity;
            };

            const alertFunc = (message) => {
              alert(message);
            };

            return {
              refMap,
              LOCATION,
              FIRST_MARKER_PROPS,
              SECOND_MARKER_PROPS,
              popupWithImageOpen,
              popupWithButtonsOpen,
              alertFunc
            };
          },
          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
                v-bind="FIRST_MARKER_PROPS"
                @click="() => {
                  popupWithImageOpen = !popupWithImageOpen
                }"
                :popup="{position: 'right', show: popupWithImageOpen}"
            >
              <template #popupContent>
                <div class="popup">
                  <img src="../waves.png" alt="waves" class="image"/>
                  
                  <div class="popup__content">
                    <div class="popup__text">
                      <div class="popup__text_title">Title of that pop up</div>
                      <div class="popup__text_content">Some useful information about a place. You can add whatever you want:
                        pictures, buttons, different headings.
                      </div>
                    </div>
                    <button class="button" @click="close">
                      Close
                    </button>
                  </div>
                </div>
              </template>
            </MMapDefaultMarker>
            
            <MMapDefaultMarker
                v-bind="SECOND_MARKER_PROPS"
                @click="() => {
                  popupWithButtonsOpen = !popupWithButtonsOpen
                }"
                :popup="{position: 'right', show: popupWithButtonsOpen}"
            >
              <template #popupContent>
                <div class="popup second_variant">
                  <div class="popup__text">
                    <div class="popup__text_title">Title of that pop up</div>
                    <div class="popup__text_content">Some useful information about a place. You can add whatever you want: pictures, buttons, different headings.</div>
                  </div>
                  <div class="popup__buttons">
                    <div class="popup__buttons__block" @click.stop="() => alertFunc('Clicked!')">
                      <button class="button">
                        Button 1
                      </button>
                      <button class="button" @click.stop="() => alertFunc('Clicked!')">
                        Button 2
                      </button>
                    </div>
                    <button class="button_close" @click.stop="() => alertFunc('Clicked!')">
                      Primary Button
                    </button>
                  </div>
                  <button class="close_icon" @click="close"/>
                </div>
              </template>
            </MMapDefaultMarker>
            
            <MMapControls position="top left">
              <InfoMessageV text="Click on markers"/>
            </MMapControls>
          </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" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.info_window {
  padding: 8px 12px 8px 32px;
  border-radius: 8px;
  background-color: #313133;
  background-image: url('./info-icon.svg');
  background-position: 8px 50%;
  background-repeat: no-repeat;
  gap: 8px;
  color: #f2f5fa;
  font-size: 14px;
  line-height: 20px;
}

.image {
  border-radius: 8px;
}

.popup {
  display: flex;
  flex-direction: column;

  width: 254px;

  background: #fff;
  gap: 4px;
}

.popup.second_variant {
  gap: 32px;
  width: 211px;
}

.popup__content {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 8px;
}

.popup__text {
  display: flex;
  white-space: break-spaces;
  flex-direction: column;
  gap: 8px;
}

.popup__buttons {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.popup__buttons__block {
  display: flex;
  gap: 4px;
}

.close_icon {
  border: none;
  background-color: transparent;
  position: absolute;
  background-image: url('./close-icon.svg');
  background-repeat: no-repeat;
  background-position: 50% 50%;
  width: 16px;
  height: 16px;
  cursor: pointer;
  right: 8px;
}

.popup__text_title {
  color: #050d33;
  font-weight: 500;
  line-height: 28px;
  font-size: 20px;
}

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

.button_close {
  padding: 7px;
  font-size: 16px;
  cursor: pointer;

  color: var(--text-text-prim-btn);
  border: none;
  border-radius: 8px;
  outline: none;
  background-color: var(--interact-interact-btn-accent);

  transition: background-color 0.2s;
}

.button_close:hover {
  background-color: var(--interact-interact-btn-accent);
}

.button_close:active {
  background-color: var(--interact-interact-btn-accent);
}

.button {
  padding: 8px;
  width: 100%;
  cursor: pointer;
  line-height: 16px;
  font-size: 14px;

  color: #050d33;
  border: none;
  border-radius: 8px;
  outline: none;
  background: rgba(92, 94, 102, 0.06);
  font-weight: 500;
  transition: background-color 0.2s;
}

.button:hover {
  background: rgba(92, 94, 102, 0.06);
}

.button:active {
  background: rgba(92, 94, 102, 0.06);
}
/* Initialize a custom information message control
Assign a value to it after loading the map api */
export let InfoMessage = null;

interface InfoMessageProps {
  text: string;
}

// Wait for the api to load to access the entity system (MMapComplexEntity)
mappable.ready.then(() => {
  mappable.import.registerCdn(
    'https://cdn.jsdelivr.net/npm/{package}',
    '@mappable-world/mappable-default-ui-theme@0.0'
  );

  class InfoMessageClass extends mappable.MMapComplexEntity<InfoMessageProps> {
    private _element!: HTMLDivElement;
    private _detachDom!: () => void;

    // Method for create a DOM control element
    _createElement(props: InfoMessageProps) {
      // Create a root element
      const infoWindow = document.createElement('div');
      infoWindow.classList.add('info_window');
      infoWindow.innerHTML = props.text;

      return infoWindow;
    }

    // Method for attaching the control to the map
    _onAttach() {
      this._element = this._createElement(this._props);
      this._detachDom = mappable.useDomContext(this, this._element, this._element);
    }

    // Method for detaching control from the map
    _onDetach() {
      this._detachDom();
      this._detachDom = undefined;
      this._element = undefined;
    }
  }
  InfoMessage = InfoMessageClass;
});
:root {
  --interact-interact-btn-accent: #313133;
  --text-text-prim-btn: #fff;
}
import type {LngLat, MMapLocationRequest} from '@mappable-world/mappable-types';
import type {MMapDefaultMarkerProps} from '@mappable-world/mappable-default-ui-theme';

export const LOCATION: MMapLocationRequest = {
  center: [55.2883, 25.2734], // starting position [lng, lat]
  zoom: 14 // starting zoom
};

export const FIRST_MARKER_PROPS: {iconName: MMapDefaultMarkerProps['iconName']; coordinates: LngLat} = {
  iconName: 'boat_station',
  coordinates: [55.2862, 25.2763]
};
export const SECOND_MARKER_PROPS: {iconName: MMapDefaultMarkerProps['iconName']; coordinates: LngLat} = {
  iconName: 'waterpark',
  coordinates: [55.2526, 25.2762]
};