Place in a hidden container
vanilla.html
common.css
common.js
react.html
vue.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script src="./common.js"></script>
<script>
window.map = null;
let isLoading = false;
let activeTab = '';
const createMap = async () => {
await mappable.ready;
const {MMap, MMapDefaultSchemeLayer, MMapControls} = mappable;
const {MMapZoomControl} = await mappable.import('@mappable-world/mappable-controls@0.0.1');
map = new MMap(document.getElementById('map'), {location: LOCATION, zoomStrategy: 'zoomToCenter'});
map.addChild((scheme = new MMapDefaultSchemeLayer()));
map.addChild(new MMapControls({position: 'right'}).addChild(new MMapZoomControl({})));
};
const destroyMap = () => {
map.destroy();
map = null;
};
const toggleLoader = () => {
document.querySelector('.loader-container').classList.toggle('_hide', !isLoading);
};
const fetchScript = async () => {
try {
isLoading = true;
toggleLoader();
await loadMapScript();
} catch (error) {
console.error(error);
return 'error';
} finally {
isLoading = false;
toggleLoader();
}
};
const onChangeTab = (e) => {
activeTab = e.target.id;
if (e.target.id === 'tab2') {
if (typeof mappable === 'undefined' && !isLoading) {
fetchScript().then((res) => {
if (res === 'error') return;
activeTab === 'tab2' && createMap();
});
}
typeof mappable !== 'undefined' && createMap();
} else {
map && destroyMap();
}
};
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('tab1').addEventListener('change', onChangeTab);
document.getElementById('tab2').addEventListener('change', onChangeTab);
});
</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 class="tabs">
<input type="radio" name="tabs" id="tab1" checked />
<label for="tab1">Description</label>
<input type="radio" name="tabs" id="tab2" />
<label for="tab2">Map</label>
<div class="tab content1"><p>In the Map tab there is a map of the city</p></div>
<div class="tab content2">
<div id="map">
<div class="loader-container _hide"><div class="loader" /></div>
</div>
</div>
</div>
</div>
</body>
</html>
input {
position: absolute;
display: none;
width: 0;
}
input + label {
position: relative;
display: inline-block;
margin-top: 8px;
padding: 0.5em 1em;
cursor: pointer;
border: 1px solid #aaa;
border-bottom: 0;
border-radius: 4px 4px 0 0;
background-color: #fff;
}
input:first-child + label {
margin-left: 4px;
}
input + label:hover {
background-color: #eee;
}
input:checked + label {
z-index: 1;
border-color: #428bca;
background-color: #fff;
box-shadow: 0 3px 0 -1px #fff, inset 0 5px 0 -1px #949494;
}
.tab {
display: none;
padding: 8px;
border-top: 1px solid #ccc;
}
.visible,
#tab1:checked ~ .tab.content1,
#tab2:checked ~ .tab.content2 {
display: block;
}
#map {
height: 500px;
}
.loader-container {
display: flex;
justify-content: center;
align-items: center;
height: 90vh;
}
.loader-container._hide {
display: none;
}
.loader {
border: 8px solid #cccccc80;
border-top: 8px solid #949494;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
const LOCATION = {center: [55.44279, 25.24613], zoom: 9};
function loadMapScript() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src =
'https://js.api.mappable.world/v3/?apikey=pk_jGLPsUVXSzwQimgFDNwlofchPIdEhdsdKaearbKtwUgDiYNsZGGTuVXNCrxSsXmd&lang=en_US';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
<!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://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="./common.js"></script>
<script type="text/babel">
window.map = null;
main();
async function main() {
const createMapComponent = async () => {
const [mappableReact] = await Promise.all([
mappable.import('@mappable-world/mappable-reactify'),
mappable.ready
]);
const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapListener} = reactify.module(mappable);
const {MMapZoomControl} = reactify.module(await mappable.import('@mappable-world/mappable-controls@0.0.1'));
return function () {
const [location, setLocation] = React.useState(LOCATION);
const onUpdate = React.useCallback(({location, mapInAction}) => {
// Animation not happening
if (!mapInAction) {
setLocation({
center: location.center,
zoom: location.zoom
});
}
}, []);
return (
<MMap location={location} zoomStrategy="zoomToCenter" ref={React.useCallback((x) => (map = x), [])}>
<MMapListener onUpdate={onUpdate} />
<MMapDefaultSchemeLayer />
<MMapControls position="right">
<MMapZoomControl />
</MMapControls>
</MMap>
);
};
};
// when the api is initialized, it will become a map component
let Map;
function Loader() {
return (
<div className={'loader-container'}>
<div className={'loader'} />
</div>
);
}
function App() {
const [tab, setTab] = React.useState('tab1');
const [isLoading, setIsLoading] = React.useState(false);
const onChangeTab = React.useCallback(
async (e) => {
setTab(e.target.id);
if (e.target.id === 'tab2') {
if (typeof mappable === 'undefined' && !isLoading) {
try {
setIsLoading(true);
await loadMapScript();
Map = await createMapComponent();
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
}
}
},
[isLoading]
);
return (
<div className="tabs">
<input type="radio" name="tabs" id="tab1" checked={tab === 'tab1'} onChange={onChangeTab} />
<label htmlFor="tab1">Description</label>
<input type="radio" name="tabs" id="tab2" checked={tab === 'tab2'} onChange={onChangeTab} />
<label htmlFor="tab2">Map</label>
{tab === 'tab1' && (
<div className="tab visible">
<p>In the Map tab there is a map of the city</p>
</div>
)}
{tab === 'tab2' && (
<div id="map" className="tab visible">
{isLoading ? <Loader /> : typeof Map !== 'undefined' && <Map />}
</div>
)}
</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://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script src="./common.js"></script>
<script></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>