Controls
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="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script src="./common.js"></script>
<script>
window.map = null;
main();
async function main() {
await mappable.ready;
const {
MMap,
MMapDefaultSchemeLayer,
MMapControls,
MMapControlButton,
MMapControl,
MMapCollection,
MMapDefaultFeaturesLayer
} = mappable;
const {MMapZoomControl, MMapGeolocationControl} = await mappable.import(
'@mappable-world/mappable-controls@0.0.1'
);
const {MMapOpenMapsButton} = await mappable.import('@mappable-world/mappable-controls-extra');
map = new MMap(document.getElementById('app'), {location: LOCATION});
map.addChild((scheme = new MMapDefaultSchemeLayer()));
map.addChild(new MMapDefaultFeaturesLayer());
map.addChild(new MMapControls({position: 'right'}).addChild(new MMapZoomControl({})));
map.addChild(new MMapControls({position: 'bottom'}).addChild(new MMapZoomControl({})));
map.addChild(new MMapControls({position: 'bottom left'}).addChild(new MMapOpenMapsButton({})));
map.addChild(new MMapControls({position: 'left'}).addChild(new MMapGeolocationControl({})));
class CounterControl extends mappable.MMapEntity {
_onAttach() {
this._value = 0;
this._element = document.createElement('div');
const button = document.createElement('button');
button.className = 'my-counter-control';
this._element.appendChild(button);
button.appendChild(document.createTextNode('control! '));
this._dynamicText = document.createTextNode(String(this._value));
button.appendChild(this._dynamicText);
this._detachDom = mappable.useDomContext(this, this._element);
this._interval = setInterval(() => {
this._value = (this._value + 1) % 10;
this._dynamicText.textContent = String(this._value);
}, 500);
}
_onDetach() {
clearInterval(this._interval);
this._detachDom();
this._detachDom = null;
this._dynamicText = null;
this._element = null;
}
}
const buttons = new MMapCollection()
.addChild(new MMapControlButton({text: 'first', onClick: () => console.log('first')}))
.addChild(new FooControlGroup())
.addChild(new MMapControlButton({text: 'second', onClick: () => console.log('second')}))
.addChild(new BarControlGroup())
.addChild(new MMapControl().addChild(new CounterControl()));
const controls = new MMapControls({position: 'top'})
.addChild(
new MMapControlButton({
text: '<',
onClick: () => buttons.addChild(buttons.children[0], buttons.children.length)
})
)
.addChild(buttons)
.addChild(
new MMapControlButton({
text: '>',
onClick: () => buttons.addChild(buttons.children[buttons.children.length - 1], 0)
})
);
map.addChild(controls);
function FooControlGroup() {
return new MMapCollection()
.addChild(
new MMapControlButton({
text: 'foo#1',
color: '#196dff',
background: '#f3f6fc',
onClick: () => console.log('foo#1')
})
)
.addChild(
new MMapControlButton({
text: 'foo#2',
color: '#196dff',
background: '#f3f6fc',
onClick: () => console.log('foo#2')
})
)
.addChild(
new MMapControlButton({
text: 'foo#3',
color: '#196dff',
background: '#f3f6fc',
onClick: () => console.log('foo#3')
})
);
}
function BarControlGroup() {
return new MMapCollection()
.addChild(
new MMapControlButton({
text: 'bar#1',
color: '#fff',
background: '#196dff',
onClick: () => console.log('bar#1')
})
)
.addChild(
new MMapControlButton({
text: 'bar#2',
color: '#fff',
background: '#196dff',
onClick: () => console.log('bar#2')
})
);
}
let counter = 0;
const counterButton = new MMapControlButton({
text: 'Counter #0',
onClick: () => counterButton.update({text: 'Counter #' + ++counter})
});
const controls2 = new MMapControls({position: 'top left'}).addChild(counterButton);
map.addChild(controls2);
counterButton.update({text: 'Counter #' + ++counter});
const element = document.createElement('div');
element.style.color = '#196dff';
element.style.fontWeight = 'bold';
element.textContent = 'Button with element';
const elementButton = new MMapControlButton({element});
map.addChild(new MMapControls({position: 'top right'}).addChild(elementButton));
}
</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>
.my-counter-control {
display: block;
background: #ff773288;
border-radius: 2px;
padding: 8px;
border: 0;
cursor: pointer;
white-space: nowrap;
}
const LOCATION = {center: [55.44279, 25.24613], zoom: 9};
<!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="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script src="./common.js"></script>
<script type="text/babel">
window.map = null;
main();
async function main() {
const [mappableReact] = await Promise.all([
mappable.import('@mappable-world/mappable-reactify'),
mappable.ready
]);
const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
const {
MMap,
MMapDefaultFeaturesLayer,
MMapDefaultSchemeLayer,
MMapControls,
MMapControlButton,
MMapListener,
MMapControl
} = reactify.module(mappable);
const {useState, useCallback, useEffect} = React;
const {MMapZoomControl, MMapGeolocationControl} = reactify.module(
await mappable.import('@mappable-world/mappable-controls@0.0.1')
);
const {MMapOpenMapsButton} = reactify.module(
await mappable.import('@mappable-world/mappable-controls-extra')
);
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app')
);
function App() {
const [location, setLocation] = useState(LOCATION);
const [buttons, setButtons] = useState(() => [
<MMapControlButton key="first" text="first" onClick={() => console.log('first')} />,
<FooControlGroup key="foo" />,
<MMapControlButton key="second" text="second" onClick={() => console.log('second')} />,
<BarControlGroup key="bar" />,
<CounterControl key="counter" text="0" />
]);
const onUpdate = React.useCallback(({location, mapInAction}) => {
// Animation not happening
if (!mapInAction) {
setLocation({
center: location.center,
zoom: location.zoom
});
}
}, []);
const shiftLeft = useCallback(() => setButtons((buttons) => [...buttons.slice(1), buttons[0]]), []);
const shiftRight = useCallback(
() => setButtons((buttons) => [...buttons.slice(-1), ...buttons.slice(0, -1)]),
[]
);
const [counter, setCounter] = useState(0);
const updateCounter = useCallback(() => setCounter((c) => c + 1), []);
useEffect(() => updateCounter(), []);
return (
<MMap location={location} ref={(x) => (map = x)}>
<MMapListener onUpdate={onUpdate} />
<MMapDefaultSchemeLayer />
<MMapDefaultFeaturesLayer />
<MMapControls position="right">
<MMapZoomControl />
</MMapControls>
<MMapControls position="bottom">
<MMapZoomControl />
</MMapControls>
<MMapControls position="top">
<MMapControlButton text="<" onClick={shiftLeft} />
{buttons}
<MMapControlButton text=">" onClick={shiftRight} />
</MMapControls>
<MMapControls position="bottom left">
<MMapOpenMapsButton />
</MMapControls>
<MMapControls position="left">
<MMapGeolocationControl />
</MMapControls>
<MMapControls position="top left">
<MMapControlButton text={'Counter #' + counter} onClick={updateCounter} />
</MMapControls>
<MMapControls position="top right">
<MMapControlButton>
<div style={{color: '#196dff', fontWeight: 'bold'}}>Button with element</div>
</MMapControlButton>
</MMapControls>
</MMap>
);
}
function CounterControl() {
const [n, setN] = useState(0);
useEffect(() => {
const id = setInterval(() => setN((n) => (n + 1) % 10), 500);
return () => clearInterval(id);
}, []);
return (
<MMapControl>
<div>
<button className="my-counter-control">control! {n}</button>
</div>
</MMapControl>
);
}
function FooControlGroup() {
return (
<React.Fragment>
<MMapControlButton
text="foo#1"
color="#196dff"
background="#f3f6fc"
onClick={() => console.log('foo#1')}
/>
<MMapControlButton
text="foo#2"
color="#196dff"
background="#f3f6fc"
onClick={() => console.log('foo#2')}
/>
<MMapControlButton
text="foo#3"
color="#196dff"
background="#f3f6fc"
onClick={() => console.log('foo#3')}
/>
</React.Fragment>
);
}
function BarControlGroup() {
return (
<React.Fragment>
<MMapControlButton
text="bar#1"
color="#fff"
background="#196dff"
onClick={() => console.log('bar#1')}
/>
<MMapControlButton
text="bar#2"
color="#fff"
background="#196dff"
onClick={() => console.log('bar#2')}
/>
</React.Fragment>
);
}
}
</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>
window.map = null;
main();
async function main() {
const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
const vuefy = mappableVue.vuefy.bindTo(Vue);
const {
MMap,
MMapDefaultFeaturesLayer,
MMapDefaultSchemeLayer,
MMapControls,
MMapControlButton,
MMapListener,
MMapControl
} = vuefy.module(mappable);
const {MMapZoomControl, MMapGeolocationControl} = vuefy.module(
await mappable.import('@mappable-world/mappable-controls@0.0.1')
);
const {MMapOpenMapsButton} = vuefy.module(await mappable.import('@mappable-world/mappable-controls-extra'));
const CounterControl = {
components: {MMapControl},
name: 'CounterControl',
data() {
const n = 0;
const id = null;
return {n, id};
},
mounted() {
this.id = setInterval(() => {
this.n = (this.n + 1) % 10;
}, 500);
},
unmounted() {
clearInterval(this.id);
},
template: `
<MMapControl>
<div>
<button class="my-counter-control">
control! {{n}}
</button>
</div>
</MMapControl>`
};
const FooControlGroup = {
components: {MMapControlButton},
name: 'FooControlGroup',
template: `
<MMapControlButton text="foo#1" color="#196dff" background="#f3f6fc" :onClick="() => console.log('foo#1')"/>
<MMapControlButton text="foo#2" color="#196dff" background="#f3f6fc" :onClick="() => console.log('foo#2')"/>
<MMapControlButton text="foo#3" color="#196dff" background="#f3f6fc" :onClick="() => console.log('foo#3')"/>`
};
const BarControlGroup = {
components: {MMapControlButton},
name: 'FooControlGroup',
template: `
<MMapControlButton text="bar#1" color="#fff" background="#196dff" :onClick="() => console.log('bar#1')"/>
<MMapControlButton text="bar#2" color="#fff" background="#196dff" :onClick="() => console.log('bar#2')"/>`
};
const ButtonsCarousel = {
name: 'ButtonsCarousel',
setup() {
const buttons = Vue.ref([
Vue.h(MMapControlButton, {text: 'first', onClick: () => console.log('first'), key: 1}),
Vue.h(FooControlGroup, {key: 2}),
Vue.h(MMapControlButton, {text: 'second', onClick: () => console.log('second'), key: 3}),
Vue.h(BarControlGroup, {key: 4}),
Vue.h(CounterControl, {key: 5})
]);
const shiftLeft = () => {
buttons.value = [...buttons.value.slice(1), buttons.value[0]];
};
const shiftRight = () => {
buttons.value = [...buttons.value.slice(-1), ...buttons.value.slice(0, -1)];
};
return () =>
Vue.h(MMapControls, {position: 'top'}, () => [
Vue.h(MMapControlButton, {text: '<', onClick: shiftLeft}),
...buttons.value,
Vue.h(MMapControlButton, {text: '>', onClick: shiftRight})
]);
}
};
const app = Vue.createApp({
components: {
MMap,
MMapListener,
MMapDefaultSchemeLayer,
MMapDefaultFeaturesLayer,
MMapZoomControl,
MMapControls,
MMapOpenMapsButton,
MMapGeolocationControl,
MMapControlButton,
ButtonsCarousel
},
methods: {
updateCounter() {
this.counter++;
},
onUpdate({location, mapInAction}) {
// Animation not happening
if (!mapInAction) {
this.location = {
center: location.center,
zoom: location.zoom
};
}
},
refMap(ref) {
window.map = ref?.entity;
}
},
data() {
const counter = 1;
const location = LOCATION;
const buttons = ['first', 'foo', 'second', 'bar', 'counter'];
return {location, counter, buttons};
},
template: `
<MMap :location="location" :ref="refMap">
<MMapListener :onUpdate="onUpdate" />
<MMapDefaultSchemeLayer />
<MMapDefaultFeaturesLayer />
<MMapControls position="right">
<MMapZoomControl></MMapZoomControl>
</MMapControls>
<MMapControls position="bottom">
<MMapZoomControl></MMapZoomControl>
</MMapControls>
<ButtonsCarousel />
<MMapControls position="bottom left" >
<MMapOpenMapsButton />
</MMapControls>
<MMapControls position="left" >
<MMapGeolocationControl />
</MMapControls>
<MMapControls position="top left" >
<MMapControlButton :text="'Counter #' + counter" :onClick="updateCounter" />
</MMapControls>
<MMapControls position="top right">
<MMapControlButton>
<div style="color: #196dff; font-weight: bold">Button with element</div>
</MMapControlButton>
</MMapControls>
</MMap>`
});
app.mount('#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>