Controls

Open on CodeSandbox

<!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!&nbsp;{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>