Replacing a static map with a dynamic
vanilla.html
react.html
vue.html
common.css
common.ts
variables.ts
<!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>
<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 {loadMapScript} from './common';
import {LOCATION, STATIC_API_URL} from '../variables';
window.map = null;
// Function to create the map after initializing the JS Map API
async function createMap() {
// Waiting for all api elements to be loaded
await mappable.ready;
const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapComplexEntity, MMapControl} = mappable;
// Initialize the map
map = new MMap(
// Pass the link to the HTMLElement of the container
document.getElementById('map'),
// Pass the map initialization parameters
{location: LOCATION, showScaleInCopyrights: true},
// Add a map scheme layer
[new MMapDefaultSchemeLayer({})]
);
class FullscreenButton extends MMapComplexEntity<{}> {
private _element: HTMLButtonElement;
private _detachDom: () => void;
// Method for create a DOM control element
_createElement() {
// Create a div element that will be passed to the MMapControlButton
const fullScreenButtonElement = document.createElement('button');
fullScreenButtonElement.type = 'button';
fullScreenButtonElement.onclick = () => {
document.getElementById('map').classList.toggle('hidden');
document.getElementById('image-wrapper').classList.toggle('hidden');
};
fullScreenButtonElement.classList.add('button', 'exit-fullscreen');
return fullScreenButtonElement;
}
// Method for attaching the control to the map
_onAttach() {
this._element = this._createElement();
this._detachDom = mappable.useDomContext(this, this._element, this._element);
}
// Method for detaching control from the map
_onDetach() {
this._detachDom();
this._detachDom = null;
this._element = null;
}
}
// Add MMapControlButton that will enable or disable fullscreen mode
const fullScreenBtn = new MMapControl();
fullScreenBtn.addChild(new FullscreenButton({}));
// Add a container for MMapControlButton and add it to the map
map.addChild(new MMapControls({position: 'top right'}, [fullScreenBtn]));
}
// Function to async loading of JS Map API script.
async function fetchScript() {
// Load the JS Map API script
await loadMapScript();
}
// Event handler for tab change
function onResizeButtonClick() {
document.getElementById('image-wrapper').classList.toggle('hidden');
document.getElementById('map').classList.toggle('hidden');
// If mappable is not defined, fetch the script and create the map
if (typeof mappable === 'undefined') {
fetchScript().then(() => {
createMap();
});
}
}
// Add event listener for button
document.getElementById('button').addEventListener('click', onResizeButtonClick);
(document.getElementById('static-img') as HTMLImageElement).src = STATIC_API_URL;
</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 class="container">
<div id="image-wrapper" class="image-wrapper">
<img id="static-img" src="" alt="img" class="image" />
<button id="button" class="button fullscreen"></button>
</div>
<div id="map" class="map hidden"></div>
</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>
<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 {loadMapScript} from './common';
import {LOCATION, STATIC_API_URL} from '../variables';
import type {JSX} from 'react';
window.map = null;
main();
async function main() {
const {useState, useCallback} = React;
// Function for create a map component after initializing the JS Map API
const createMapComponent = async (onClickHandler: () => void) => {
// 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, MMapControls, MMapControl} = reactify.module(mappable);
// Return a functional React component representing the map
return () => {
return (
// Initialize the map and pass initialization parameters
<MMap location={LOCATION} showScaleInCopyrights={true} ref={useCallback((x) => (map = x), [])}>
{/* Add a map scheme layer */}
<MMapDefaultSchemeLayer />
{/* Add a container for MMapControlButton */}
<MMapControls position="top right">
{/* Add MMapControlButton that will enable or disable fullscreen mode */}
<MMapControl>
<button type="button" onClick={onClickHandler} className="button exit-fullscreen" />
</MMapControl>
</MMapControls>
</MMap>
);
};
};
// When the JS Map API is initialized, the Map variable will become a react component
let Map: () => JSX.Element = null;
function App() {
const [isStatic, setIsStatic] = useState(true);
const onResizeButtonClick = useCallback(async () => {
if (typeof mappable === 'undefined') {
try {
// Load the JS Map API script
await loadMapScript();
// Initialize the Map component
Map = await createMapComponent(() => setIsStatic(true));
} catch (error) {
// Log any errors that occur during script loading
console.error(error);
}
}
setIsStatic(false);
}, []);
return (
<div className="container">
{isStatic ? (
<div className="image-wrapper">
<img src={STATIC_API_URL} alt="img" className="image" />
<button className="button fullscreen" onClick={onResizeButtonClick} />
</div>
) : (
<Map />
)}
</div>
);
}
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
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 {loadMapScript} from './common';
import {LOCATION, STATIC_API_URL} from '../variables';
import type {Component} from '@vue/runtime-core';
window.map = null;
async function main() {
const Map =
Vue.defineAsyncComponent <
Component >
{
loader: () => {
return new Promise(async (resolve, reject) => {
if (typeof mappable == 'undefined') {
await loadMapScript();
}
const [mappableVue] = await Promise.all([
mappable.import('@mappable-world/mappable-vuefy'),
mappable.ready
]);
const vuefy = mappableVue.vuefy.bindTo(Vue);
const {MMap, MMapDefaultSchemeLayer, MMapControl, MMapControls} = vuefy.module(mappable);
resolve({
props: {
onClickHandler: Function
},
setup() {
const refMap = (ref) => {
window.map = ref?.entity;
};
return {
LOCATION,
refMap
};
},
components: {
MMap,
MMapDefaultSchemeLayer,
MMapControls,
MMapControl
},
template: `
<MMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap">
<!-- Add a map scheme layer -->
<MMapDefaultSchemeLayer />
<MMapControls position="top right">
<MMapControl>
<button
type="button"
@click="onClickHandler"
class="button exit-fullscreen"
/>
</MMapControl>
</MMapControls>
</MMap>
`
});
});
}
};
const app = Vue.createApp({
setup() {
const isStatic = Vue.ref(true);
const onResizeButtonClick = async () => {
isStatic.value = !isStatic.value;
};
return {
STATIC_API_URL,
isStatic,
onResizeButtonClick
};
},
components: {
Map
},
template: `
<div class="container">
<template v-if="isStatic">
<div class="image-wrapper">
<img :src="STATIC_API_URL" alt="img" class="image"/>
<button class="button fullscreen" @click="onResizeButtonClick"/>
</div>
</template>
<template v-else>
<Map :onClickHandler="onResizeButtonClick" />
</template>
</div>
`
});
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>
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.image {
border-radius: 8px;
width: 325px;
height: 225px;
}
.map {
width: 100%;
height: 100%;
}
.hidden {
display: none;
}
.image-wrapper {
position: relative;
}
.button {
all: unset;
cursor: pointer;
width: 32px;
height: 32px;
background-position: 50% 50%;
background-repeat: no-repeat;
box-shadow: 0px 2px 4px 0px #5f698333;
border-radius: 8px;
position: absolute;
top: 8px;
right: 8px;
background-color: #ffffff;
}
.button.fullscreen {
background-image: url('./fullscreen.svg');
}
.button.exit-fullscreen {
background-image: url('./fullscreen-exit.svg');
}
// Function to async load the JS Map API script.
export function loadMapScript() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
import type {MMapLocationRequest} from '@mappable-world/mappable-types';
export const LOCATION: MMapLocationRequest = {
center: [55.276, 25.195], // starting position [lng, lat]
zoom: 15.4 // starting zoom
};
export const STATIC_API_URL =
'https://static.api.mappable.world/v1?ll=55.276012,25.195050&size=650,450&scale=2&z=15&apikey=<YOUR_APIKEY>&lang=en_US';