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>

        <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>

        <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>

        <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>
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]
};
:root {
    --interact-interact-btn-accent: #313133;
    --text-text-prim-btn: #fff;
}
/* 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;
});
.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);
}