Change map behaviors

Open in CodeSandbox

<!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="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {BehaviorType} from '@mappable-world/mappable-types';
      import {AVAILABLE_BEHAVIORS, INITIALLY_ENABLED_BEHAVIORS} from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      main();

      async function main() {
          // Waiting for all api elements to be loaded
          await mappable.ready;

          // Create a custom control class for map behavior management
          interface BehaviorControlProps {
              behaviorName: string;

              behaviorType: string;

              checked: boolean;

              tooltip: boolean;

              onChange: (event: Event) => void;
          }

          class BehaviorControl extends mappable.MMapComplexEntity<BehaviorControlProps> {
              private _element: HTMLDivElement;

              private _detachDom: () => void;

              // Method for create a DOM control element
              _createElement(props: BehaviorControlProps) {
                  const {behaviorName, checked, tooltip, behaviorType, onChange} = props;

                  // Create a root element
                  const behaviorControl = document.createElement('div');
                  behaviorControl.classList.add('behaviorControl');

                  const behaviorControlBlock = document.createElement('div');
                  behaviorControlBlock.classList.add('behaviorControl__block');

                  // Create a title element
                  const title = document.createElement('div');
                  title.classList.add('behaviorControl__title');
                  title.textContent = behaviorName;
                  behaviorControlBlock.appendChild(title);

                  // Create a label element
                  const label = document.createElement('label');
                  label.classList.add('behaviorControl__label');
                  label.htmlFor = behaviorType;

                  // Create an input element (checkbox)
                  const input = document.createElement('input');
                  input.type = 'checkbox';
                  input.id = behaviorType;
                  input.checked = checked;
                  input.addEventListener('change', onChange);

                  // Create a slider element
                  const slider = document.createElement('div');
                  slider.classList.add('behaviorControl__slider');

                  // Add elements to the label
                  label.appendChild(input);
                  label.appendChild(slider);

                  // Add the label to the root element
                  behaviorControlBlock.appendChild(label);
                  behaviorControl.appendChild(behaviorControlBlock);

                  // Create a tooltip element if necessary
                  if (tooltip) {
                      const tooltipElement = document.createElement('div');
                      tooltipElement.className = 'behaviorControl__tooltip';
                      tooltipElement.innerText = 'To implement it hold down [Ctrl] / [Cmd] + left mouse button';

                      behaviorControl.appendChild(tooltipElement);
                  }

                  return behaviorControl;
              }

              // Method for attaching the control to the map
              _onAttach() {
                  this._element = this._createElement(this._props);
                  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;
              }
          }

          class BehaviorGroup extends mappable.MMapGroupEntity<{}> {
              private _element: HTMLDivElement;

              private _detachDom: () => void;

              // Method for create a DOM control element
              _createElement() {
                  // Create a root element
                  const behaviorControl = document.createElement('div');
                  behaviorControl.className = 'behavior';

                  return behaviorControl;
              }

              // 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;
              }
          }

          // State with active map behaviors
          let behaviors = INITIALLY_ENABLED_BEHAVIORS;

          const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapControl} = mappable;

          // 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, behaviors: behaviors},
              // Add a map scheme layer
              [new MMapDefaultSchemeLayer({})]
          );

          // Using MMapControls you can change the position of the control
          const controls = new MMapControls({position: 'right top'});
          map.addChild(controls);

          // Create a shared container for custom BehaviorControl's and add it to the map
          const control = new MMapControl();
          controls.addChild(control);

          const behaviorGroup = new BehaviorGroup({});
          control.addChild(behaviorGroup);

          // Create a BehaviorControls to change the behavior of the map
          AVAILABLE_BEHAVIORS.forEach((behavior) => {
              behaviorGroup.addChild(
                  new BehaviorControl({
                      behaviorName: behavior.title,
                      behaviorType: behavior.type,
                      checked: behaviors.includes(behavior.type),
                      tooltip: behavior.tooltip,
                      onChange: onChangeBehaviorHandler
                  })
              );
          });

          // Handler function for add or remove map behavior
          function onChangeBehaviorHandler(event: Event) {
              const target = event.target as HTMLInputElement;
              const behaviorName = target.id as BehaviorType;
              const checked = target.checked;

              if (checked) {
                  behaviors.push(behaviorName);
              } else {
                  behaviors = behaviors.filter((behavior) => behavior !== behaviorName);
              }
              map.setBehaviors(behaviors);
          }
      }
    </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" />
    <link rel="stylesheet" href="../variables.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="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import type {BehaviorType} from '@mappable-world/mappable-types';
      import {AVAILABLE_BEHAVIORS, INITIALLY_ENABLED_BEHAVIORS} from './common';
      import {LOCATION} from '../variables';
      import type TReact from 'react';

      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, MMapControls, MMapControl} = reactify.module(mappable);
          const {useState, useCallback} = React;

          // Create a custom control component for map behavior management
          interface BehaviorControlProps {
              behaviorName: string;

              behaviorType: string;

              checked: boolean;

              tooltip: boolean;

              onChange: (event: TReact.FormEvent<HTMLLabelElement>) => void;
          }

          function BehaviorControl(props: BehaviorControlProps) {
              const {behaviorName, checked, behaviorType, tooltip, onChange} = props;

              return (
                  <div className="behaviorControl">
                      <div className="behaviorControl__block">
                          <div className="behaviorControl__title">{behaviorName}</div>
                          <label className="behaviorControl__label" htmlFor={behaviorType} onChange={onChange}>
                              <input type="checkbox" id={behaviorType} checked={checked} />
                              <div className="behaviorControl__slider"></div>
                          </label>
                      </div>
                      {tooltip && (
                          <div className="behaviorControl__tooltip">
                              To implement it hold down [Ctrl] / [Cmd] + left mouse button
                          </div>
                      )}
                  </div>
              );
          }

          function App() {
              // State with active map behaviors
              const [behaviors, setBehaviors] = useState<BehaviorType[]>(INITIALLY_ENABLED_BEHAVIORS);

              // Handler function for add or remove map behavior
              const onChangeBehaviorHandler = useCallback(
                  (event: TReact.FormEvent<HTMLLabelElement>) => {
                      const target = event.target as HTMLInputElement;
                      const behaviorName = target.id as BehaviorType;
                      const checked = target.checked;

                      if (checked) {
                          setBehaviors([...behaviors, behaviorName]);
                      } else {
                          setBehaviors(behaviors.filter((behavior) => behavior !== behaviorName));
                      }
                  },
                  [behaviors]
              );

              return (
                  // Initialize the map and pass initialization parameters
                  <MMap location={LOCATION} showScaleInCopyrights={true} behaviors={behaviors} ref={(x) => (map = x)}>
                      {/* Add a map scheme layer */}
                      <MMapDefaultSchemeLayer />

                      {/* Using MMapControls you can change the position of the control */}
                      <MMapControls position="top right">
                          {/* Add a shared container to the map for custom BehaviorControl's */}
                          <MMapControl>
                              <div className="behavior">
                                  {/* Add a BehaviorControls to the map to change the behavior of the map */}
                                  {AVAILABLE_BEHAVIORS.map((behavior) => (
                                      <BehaviorControl
                                          key={behavior.type}
                                          behaviorName={behavior.title}
                                          behaviorType={behavior.type}
                                          checked={behaviors.includes(behavior.type)}
                                          tooltip={behavior.tooltip}
                                          onChange={onChangeBehaviorHandler}
                                      />
                                  ))}
                              </div>
                          </MMapControl>
                      </MMapControls>
                  </MMap>
              );
          }

          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" />
    <link rel="stylesheet" href="../variables.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>
    <!-- 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="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {AVAILABLE_BEHAVIORS, INITIALLY_ENABLED_BEHAVIORS} from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      async function main() {
        // For each object in the JS API, there is a Vue counterpart
        // To use the Vue version of the API, include the module @mappable-world/mappable-vuefy
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapControl} = vuefy.module(mappable);

        const BehaviorControl = Vue.defineComponent({
          props: {
            behaviorName: String,
            behaviorType: String,
            checked: Boolean,
            tooltip: Boolean
          },
          emits: ['changeBehavior'],
          template: `
      <div class="behaviorControl">
        <div class="behaviorControl__block">
          <div class="behaviorControl__title">{{ behaviorName }}</div>
          <label
            class="behaviorControl__label"
            :for="behaviorType"
          >
            <input type="checkbox" :id="behaviorType" :checked="checked"
                   @change="$emit('changeBehavior', $event.target.checked, behaviorType)"/>
            <div class="behaviorControl__slider"></div>
          </label>
        </div>
        <div v-if="tooltip" class="behaviorControl__tooltip">
          <div class="behaviorControl__tooltip">
            To implement it hold down [Ctrl] / [Cmd] + left mouse button
          </div>
        </div>
      </div>
    `
        });

        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            BehaviorControl,
            MMapControls,
            MMapControl
          },
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            const availableBehaviors = AVAILABLE_BEHAVIORS;
            const behaviors = Vue.ref([...INITIALLY_ENABLED_BEHAVIORS]);

            // Handler function for add or remove map behavior
            const onChangeBehaviorHandler = (event, behaviorType) => {
              const checked = event; //.target.checked;
              if (checked) {
                behaviors.value.push(behaviorType);
              } else {
                behaviors.value = behaviors.value.filter((b) => b !== behaviorType);
              }
              behaviors.value = [...behaviors.value];
            };

            return {
              LOCATION,
              refMap,
              availableBehaviors,
              behaviors,
              onChangeBehaviorHandler
            };
          },
          template: `
      <!--Initialize the map and pass initialization parameters-->
      <MMap :location="LOCATION" :showScaleInCopyrights="true" :behaviors="behaviors" :ref="refMap">
        <!--Add a map scheme layer-->
        <MMapDefaultSchemeLayer/>

        <!-- Using MMapControls you can change the position of the control -->
        <MMapControls position="top right">

          <!-- Add a shared container to the map for custom BehaviorControl's -->
          <MMapControl>
            <div class="behavior">

              <!-- Add a BehaviorControls to the map to change the behavior of the map -->
              <BehaviorControl
                v-for="behavior in availableBehaviors"
                :key="behavior.type"
                :behaviorName="behavior.title"
                :behaviorType="behavior.type"
                :checked="behaviors.includes(behavior.type)"
                :tooltip="behavior.tooltip"
                @changeBehavior="onChangeBehaviorHandler"
              />
            </div>
          </MMapControl>
        </MMapControls>
      </MMap>
    `
        });
        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" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.behavior {
  padding: 14px 16px 8px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.behaviorControl {
  padding: 6px 4px 0 0;
  display: flex;
  flex-direction: column;
  width: 228px;
  min-height: 32px;
  box-sizing: border-box;
  gap: 6px;
}

.behaviorControl__title {
  font-size: 16px;
  color: #000000;
  font-weight: 500;
}

.behaviorControl__block {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.behaviorControl__tooltip {
  font-size: 12px;
  line-height: 16px;
  color: #898a8f;
  width: 213px;
}

.behaviorControl__label {
  margin-left: auto;

  height: 22px;
  width: 40px;

  display: inline-block;
  position: relative;
}

.behaviorControl__label input {
  display: none;
}

.behaviorControl__slider {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;

  cursor: pointer;
  border-radius: 22px;
  background-color: #ccc;
  transition: 0.4s;
}

.behaviorControl__slider:before {
  content: '';
  width: 16px;
  height: 16px;

  position: absolute;
  bottom: 3px;
  left: 3px;

  border-radius: 50%;
  background-color: #fff;
  transition: 0.4s;
}

.behaviorControl__label input:checked + .behaviorControl__slider {
  background-color: var(--interact-action);
}

.behaviorControl__label input:checked + .behaviorControl__slider:before {
  transform: translateX(18px);
}
import type {BehaviorType} from '@mappable-world/mappable-types';

// An array with initially enabled map behaviors
export const INITIALLY_ENABLED_BEHAVIORS: BehaviorType[] = ['drag', 'scrollZoom', 'dblClick'];

// An array of available map behaviors. These are just some of the behaviors, you can see them all in the documentation
export const AVAILABLE_BEHAVIORS: {type: BehaviorType; title: string; tooltip?: boolean}[] = [
  {
    title: 'Drag',
    type: 'drag'
  },
  {
    title: 'Scroll zoom',
    type: 'scrollZoom'
  },
  {
    title: 'Double click',
    type: 'dblClick'
  },
  {
    title: 'Mouse rotate',
    type: 'mouseRotate',
    tooltip: true
  },
  {
    title: 'Mouse tilt',
    type: 'mouseTilt',
    tooltip: true
  }
];
:root {
  --interact-action: #122db2;
}
import type {MMapLocationRequest} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [54.343, 24.483], // starting position [lng, lat]
  zoom: 14.4 // starting zoom
};