Map event listeners

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>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.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 {
          BehaviorMapEventHandler,
          DomEventHandler,
          MapEventResizeHandler,
          MapEventUpdateHandler
      } from '@mappable-world/mappable-types';
      import {BEHAVIOR, EventListener} from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      main();

      async function main() {
          // Waiting for all api elements to be loaded
          await mappable.ready;
          const {MMap, MMapDefaultSchemeLayer, MMapListener, 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: BEHAVIOR},
              // Add a map scheme layer
              [new MMapDefaultSchemeLayer({})]
          );

          // Status of map events
          const mapEvents = {
              update: false,
              resize: false
          };

          // Status of dom events
          const domEvents = {
              click: false,
              dblClick: false,
              rightDblClick: false,
              mouseMove: false,
              mouseEnter: false,
              mouseLeave: false,
              mouseDown: false
          };

          // Status of behavior events
          const behaviorEvents = {
              scrollZoom: false,
              drag: false,
              mouseRotate: false,
              mouseTilt: false
          };

          // Create an EventListener control to display the status of map events
          const mapEventsControl = new EventListener({
              title: 'Map Events',
              events: {...mapEvents}
          });

          // Create an EventListener control to display the status of dom events
          const domEventsControl = new EventListener({
              title: 'Dom Events',
              events: {...domEvents}
          });

          // Create an EventListener control to display the status of behavior events
          const behaviorEventsControl = new EventListener({
              title: 'Behavior Events',
              events: {...behaviorEvents}
          });

          /* Create and add a shared container for controls to the map.
        Using MMapControls you can change the position of the control */
          const topLeftControls = new MMapControls({position: 'top left', orientation: 'horizontal'});
          map.addChild(topLeftControls);

          // Add controls to the map
          topLeftControls
              .addChild(new MMapControl().addChild(mapEventsControl).addChild(behaviorEventsControl))
              .addChild(new MMapControl().addChild(domEventsControl));

          // Handler function for changing the status of the onUpdate event
          const updateHandler: MapEventUpdateHandler = (object) => {
              object.mapInAction ? (mapEvents.update = true) : (mapEvents.update = false);
              mapEventsControl.update({events: {...mapEvents}});
          };

          // Function that creates a handler function to change the status of the onResize event
          const createResizeHandler = (): MapEventResizeHandler => {
              const disableResizeEvent = _.debounce(() => {
                  mapEvents.resize = false;
                  mapEventsControl.update({events: {...mapEvents}});
              }, 250);

              return function (object) {
                  mapEvents.resize = true;
                  mapEventsControl.update({events: {...mapEvents}});

                  disableResizeEvent();
              };
          };

          // Function that creates a handler function to change the status of dom events
          const createDomEventHandler = (eventType: keyof typeof domEvents): DomEventHandler => {
              const disableEvent = _.debounce(() => {
                  domEvents[eventType] = false;
                  domEventsControl.update({
                      events: {
                          ...domEvents
                      }
                  });
              }, 250);

              return function (object, event) {
                  domEvents[eventType] = true;
                  domEventsControl.update({
                      events: {
                          ...domEvents
                      }
                  });

                  disableEvent();
              };
          };

          // Function that creates a handler function to change the status of behavior event
          const createBehaviorEventHandler = (isStart: boolean): BehaviorMapEventHandler => {
              return function (object) {
                  if (object.type === 'dblClick') return;
                  behaviorEvents[object.type] = isStart;
                  behaviorEventsControl.update({events: {...behaviorEvents}});
              };
          };

          /* Add a listener to the map and pass the handlers functions for the events you want to process
        These are just some of the events, you can see them all in the documentation */
          map.addChild(
              new MMapListener({
                  onUpdate: updateHandler,
                  onResize: createResizeHandler(),

                  onClick: createDomEventHandler('click'),
                  onDblClick: createDomEventHandler('dblClick'),
                  onRightDblClick: createDomEventHandler('rightDblClick'),
                  onMouseMove: createDomEventHandler('mouseMove'),
                  onMouseEnter: createDomEventHandler('mouseEnter'),
                  onMouseLeave: createDomEventHandler('mouseLeave'),
                  onMouseDown: createDomEventHandler('mouseDown'),

                  onActionStart: createBehaviorEventHandler(true),
                  onActionEnd: createBehaviorEventHandler(false)
              })
          );
      }
    </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>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.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 {
          BehaviorMapEventHandler,
          DomEventHandler,
          MapEventResizeHandler,
          MapEventUpdateHandler
      } from '@mappable-world/mappable-types';
      import {BEHAVIOR, EventListener} from './common';
      import {LOCATION} from '../variables';

      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, MMapListener, MMapControls, MMapControl} = reactify.module(mappable);

          // Using mappable-rectify, we turn a custom InfoMessage and EventListener into a React component
          const {EventListener: EventListenerR} = reactify.module({EventListener});

          const {useState, useCallback, useMemo} = React;

          function App() {
              // Status of map events
              const [mapEvents, setMapEvents] = useState({
                  update: false,
                  resize: false
              });

              // Status of dom events
              const [domEvents, setDomEvents] = useState({
                  click: false,
                  dblClick: false,
                  rightDblClick: false,
                  mouseMove: false,
                  mouseEnter: false,
                  mouseLeave: false,
                  mouseDown: false
              });

              // Status of behavior events
              const [behaviorEvents, setBehaviorEvents] = useState({
                  scrollZoom: false,
                  drag: false,
                  mouseRotate: false,
                  mouseTilt: false
              });

              // Handler function for changing the status of the onUpdate event
              const updateHandler: MapEventUpdateHandler = useCallback((object) => {
                  setMapEvents({...mapEvents, update: !!object.mapInAction});
              }, []);

              // Function that creates a handler function to change the status of the onResize event
              const createResizeHandler = useCallback((): MapEventResizeHandler => {
                  const disableResizeEvent = _.debounce(() => {
                      setMapEvents({...mapEvents, resize: false});
                  }, 250);

                  return function () {
                      setMapEvents({...mapEvents, resize: true});
                      disableResizeEvent();
                  };
              }, []);

              // Function that creates a handler function to change the status of dom events
              const createDomEventHandler = useCallback((eventType: keyof typeof domEvents): DomEventHandler => {
                  const disableEvent = _.debounce(() => {
                      setDomEvents({...domEvents, [eventType]: false});
                  }, 250);

                  return function () {
                      setDomEvents({...domEvents, [eventType]: true});

                      disableEvent();
                  };
              }, []);

              // Function that creates a handler function to change the status of behavior event
              const createBehaviorEventHandler = useCallback((isStart: boolean): BehaviorMapEventHandler => {
                  return function (object) {
                      if (object.type === 'dblClick') return;

                      behaviorEvents[object.type] = isStart;
                      setBehaviorEvents({...behaviorEvents});
                  };
              }, []);

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

                      {/* Add a shared container for controls to the map.
                            Using MMapControls you can change the position of the control */}
                      <MMapControls position="top left" orientation="horizontal">
                          <MMapControl>
                              {/* Add an EventListener control to the map to display the status of map events */}
                              <EventListenerR title="Map events" events={mapEvents} />
                              {/* Add an EventListener control to the map to display the status of behavior events */}
                              <EventListenerR title="Behavior Events" events={behaviorEvents} />
                          </MMapControl>
                          <MMapControl>
                              {/* Add an EventListener control to the map to display the status of dom events */}
                              <EventListenerR title="Dom Events" events={domEvents} />
                          </MMapControl>
                      </MMapControls>

                      {/* Add a listener to the map and pass the handlers functions for the events you want to process
                            These are just some of the events, you can see them all in the documentation */}
                      <MMapListener
                          onUpdate={updateHandler}
                          onResize={useMemo(() => createResizeHandler(), [])}
                          onClick={useMemo(() => createDomEventHandler('click'), [])}
                          onDblClick={useMemo(() => createDomEventHandler('dblClick'), [])}
                          onRightDblClick={useMemo(() => createDomEventHandler('rightDblClick'), [])}
                          onMouseMove={useMemo(() => createDomEventHandler('mouseMove'), [])}
                          onMouseEnter={useMemo(() => createDomEventHandler('mouseEnter'), [])}
                          onMouseLeave={useMemo(() => createDomEventHandler('mouseLeave'), [])}
                          onMouseDown={useMemo(() => createDomEventHandler('mouseDown'), [])}
                          onActionStart={useMemo(() => createBehaviorEventHandler(true), [])}
                          onActionEnd={useMemo(() => createBehaviorEventHandler(false), [])}
                      />
                  </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>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.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 {BEHAVIOR, EventListener} 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, MMapListener, MMapControls, MMapControl} = vuefy.module(mappable);

        // Using mappable-vuefy, we turn a custom InfoMessage and EventListener into a Vue component
        const {EventListener: EventListenerV} = vuefy.module(
          {EventListener},
          {
            EventListener: {title: String, events: Object}
          }
        );

        const mapEvents = Vue.ref({
          update: false,
          resize: false
        });

        const updateHandler = (object) => {
          mapEvents.value.update = !!object.mapInAction;
          mapEvents.value = {...mapEvents.value};
        };

        const domEvents = Vue.ref({
          click: false,
          dblClick: false,
          rightDblClick: false,
          mouseMove: false,
          mouseEnter: false,
          mouseLeave: false,
          mouseDown: false
        });

        const behaviorEvents = Vue.ref({
          scrollZoom: false,
          drag: false,
          mouseRotate: false,
          mouseTilt: false
        });

        const createResizeHandler = () => {
          const disableResizeEvent = _.debounce(() => {
            mapEvents.value.resize = false;
            mapEvents.value = {...mapEvents.value};
          }, 250);

          return function (object) {
            mapEvents.value.resize = true;
            mapEvents.value = {...mapEvents.value};
            disableResizeEvent();
          };
        };

        const createDomEventHandler = (eventType) => {
          const disableEvent = _.debounce(() => {
            domEvents.value[eventType] = false;
            domEvents.value = {...domEvents.value};
          }, 250);

          return function () {
            domEvents.value[eventType] = true;
            domEvents.value = {...domEvents.value};
            disableEvent();
          };
        };

        const createBehaviorEventHandler = (isStart) => {
          return function (object) {
            if (object.type === 'dblClick') return;
            behaviorEvents.value[object.type] = isStart;
            behaviorEvents.value = {...behaviorEvents.value};
          };
        };
        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapListener,
            MMapControls,
            MMapControl,
            EventListenerV
          },
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            return {
              LOCATION,
              BEHAVIOR,
              refMap,
              domEvents,
              mapEvents,
              behaviorEvents,
              updateHandler,
              createResizeHandler,
              createDomEventHandler,
              createBehaviorEventHandler
            };
          },
          template: `
      <!-- Initialize the map and pass initialization parameters -->
      <MMap :location="LOCATION" :showScaleInCopyrights="true" :behaviors="BEHAVIOR" :ref="refMap">
        <!-- Add a map scheme layer -->
        <MMapDefaultSchemeLayer/>

        <!-- Add a shared container for controls to the map.
        Using MMapControls you can change the position of the control -->
        <MMapControls position="top left" orientation="horizontal">
          <MMapControl>
            <!-- Add an EventListener control to the map to display the status of map events -->
            <EventListenerV title="Map Events" :events="mapEvents"/>
            <!-- Add an EventListener control to the map to display the status of behavior events -->
            <EventListenerV title="Behavior Events" :events="behaviorEvents"/>
          </MMapControl>
          <MMapControl>
            <!-- Add an EventListener control to the map to display the status of dom events -->
            <EventListenerV title="Dom Events" :events="domEvents"/>
          </MMapControl>
        </MMapControls>

        <!-- Add a listener to the map and pass the handlers functions for the events you want to process
        These are just some of the events, you can see them all in the documentation -->
        <MMapListener
          @update="updateHandler"
          @resize="(o) => createResizeHandler()(o)"
          @click="(o, e) => createDomEventHandler('click')(o, e)"
          @dblClick="(o, e) => createDomEventHandler('dblClick')(o, e)"
          @rightDblClick="(o, e) => createDomEventHandler('rightDblClick')(o, e)"
          @mouseMove="(o, e) => createDomEventHandler('mouseMove')(o, e)"
          @mouseEnter="(o, e) => createDomEventHandler('mouseEnter')(o, e)"
          @mouseLeave="(o, e) => createDomEventHandler('mouseLeave')(o, e)"
          @mouseDown="(o, e) => createDomEventHandler('mouseDown')(o, e)"
          @actionStart="o => createBehaviorEventHandler(true)(o)"
          @actionEnd="o => createBehaviorEventHandler(false)(o)"
        />
      </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>
.events {
  width: 200px;
  padding: 8px;
  display: flex;
  flex-direction: column;
}

.events:nth-child(2)::before {
  content: '';
  position: relative;
  top: -8px;
  display: block;
  border-top: 1px solid #5c5e661a;
  justify-content: center;
  width: 90%;
  margin: 0 auto;
}

.events__title {
  font-size: 14px;
  width: 184px;
  height: 20px;
  padding: 0 8px;
  align-content: center;
  color: #7b7d85;
}

.events__list {
  margin: 0;
  padding: 0;
  list-style: none;

  display: flex;
  flex-direction: column;
}

.events__item {
  color: #050d33;
  font-size: 16px;
  font-style: normal;
  font-weight: 500;
  line-height: 20px;
  padding: 8px 8px 8px 36px;
  border-radius: 8px;
  align-content: center;
  background-repeat: no-repeat;
  background-position: 5% 50%;
}

.events__item.update {
  background-image: url('./update.svg');
}

.events__item.resize {
  background-image: url('./resize.svg');
}

.events__item.scrollZoom {
  background-image: url('./scrollZoom.svg');
}

.events__item.drag {
  background-image: url('./drag.svg');
}

.events__item.mouseRotate {
  background-image: url('./mouseRotate.svg');
}

.events__item.mouseTilt {
  background-image: url('./mouseTilt.svg');
}

.events__item.click {
  background-image: url('./click.svg');
}

.events__item.dblClick {
  background-image: url('./dblClick.svg');
}

.events__item.rightDblClick {
  background-image: url('./rightDblClick.svg');
}

.events__item.mouseMove {
  background-image: url('./mouseMove.svg');
}

.events__item.mouseEnter {
  background-image: url('./mouseEnter.svg');
}

.events__item.mouseLeave {
  background-image: url('./mouseLeave.svg');
}

.events__item.mouseDown {
  background-image: url('./mouseDown.svg');
}

.events__item_active {
  background-color: var(--color-interact-component-chkd-hover);
}

.infoWindow {
  width: 300px;
  padding: 15px;
  padding-left: 50px;

  position: relative;

  border-radius: 15px;
  background-color: rgba(255, 255, 255, 0.9);
  font-size: 16px;
}

.infoWindow__icon {
  width: 30px;
  height: 30px;

  position: absolute;
  top: 50%;
  left: 10px;
  transform: translateY(-50%);
}
import type {BehaviorType} from '@mappable-world/mappable-types';
import type lodash from 'lodash';

declare global {
  // @ts-ignore
  const _: typeof lodash;
}
// An array of enabled map behaviors
export const BEHAVIOR: BehaviorType[] = ['drag', 'scrollZoom', 'dblClick', 'mouseRotate', 'mouseTilt'];

/* Initialize a custom EventListener control
Assign a value to it after loading the map api */
export let EventListener = null;

type mapEventsType = {
  title: string;
  events: {
    update: boolean;
    resize: boolean;
  };
};

type domEventsType = {
  title: string;
  events: {
    click: boolean;
    fastClick: boolean;
    dblClick: boolean;
    rightClick: boolean;
    rightDblClick: boolean;
    mouseMove: boolean;
    mouseEnter: boolean;
    mouseLeave: boolean;
    mouseDown: boolean;
  };
};

type behaviorEventsType = {
  title: string;
  events: {
    scrollZoom: boolean;
    drag: boolean;
    mouseRotate: boolean;
    mouseTilt: boolean;
  };
};

type EventListenerProps = mapEventsType | domEventsType | behaviorEventsType;

// Wait for the api to load to access the entity system (MMapComplexEntity)
mappable.ready.then(() => {
  class EventListenerClass extends mappable.MMapComplexEntity<EventListenerProps> {
    private _element: HTMLDivElement;

    private _detachDom: () => void;

    // Method for create a DOM control element
    _createElement() {
      const {title, events} = this._props;

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

      // Create an events title element
      const eventsTitle = document.createElement('div');
      eventsTitle.textContent = title;
      eventsTitle.classList.add('events__title');
      rootElement.appendChild(eventsTitle);

      // Create an events list element
      const eventsList = document.createElement('ul');
      eventsList.classList.add('events__list');
      rootElement.appendChild(eventsList);

      // Iterate through the events and create list items
      for (let event in events) {
        const eventElement = document.createElement('li');
        eventElement.classList.add('events__item', event);
        eventElement.textContent = event;
        eventElement.id = event;

        eventsList.appendChild(eventElement);
      }

      return rootElement;
    }

    // Method that is used when updating the parameters of control
    _onUpdate() {
      const {events} = this._props;
      for (let event in events) {
        const eventElement = document.getElementById(event);
        if (!eventElement) return;

        if (events[event]) {
          eventElement.classList.add('events__item_active');
        } else {
          eventElement.classList.remove('events__item_active');
        }
      }
    }

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

  EventListener = EventListenerClass;
});
:root {
  --color-interact-component-chkd-hover: rgba(18, 45, 178, 0.06);
}
import type {MMapLocationRequest} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [55.5358, 25.3176], // starting position [lng, lat]
  zoom: 11.7 // starting zoom
};