Create a marker with a popup window
vanilla.html
react.html
vue.html
common.css
common.ts
variables.ts
sandbox.json
<!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"></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"}