Source

services/markerService.js

import * as markerService from './markerService';
// NOTE: https://stackoverflow.com/a/55193363 ES6 import into itself to allow mocking

/**
 @module markerService
 */

/**
 * Function returns the positions of a HTML element in relation to the viewport
 * @method
 * @param   {string}  elementId Identifier for the HTML element
 *
 * @returns {Object} positionalData
 */
export const getElementPositionData = (elementId) => {
  const element = document.getElementById(elementId);
  const elementData = element.getBoundingClientRect();
  return {
    left: elementData.left,
    top: elementData.top,
    height: elementData.height,
    width: elementData.width,
  };
};

/**
 * Compares the viewport position data of the map marker identified by the placeId parameter with the map itself.
 * This allows the function to determine in which quadrant of the map the HoverBox will appear,
 * and hence can determine at which side (left/right, top/bottom)
 * it is to be rendered to avoid the HoverBox from clipping outside of the map component.
 * @method
 * @param   {boolean}  isHidden Determines if the element is a visible component
 * @param   {string}  placeId Identifier for the HTML element
 *
 * @returns {Object} data required to determine the style of a Hoverbox component
 */
export const getHoverBoxStyleData = (isHidden, placeId) => {
  let foldLeft = false;
  let foldTop = false;
  let markerOffsetTopComputed = 'unset';

  // only if it is the actual hoverBox. there is a hidden 'twin' used to determine the actual height of
  // the element (since height is variable). For the hidden 'twin', these calculations are not needed
  if (!isHidden) {
    // get map data, determine vertical and horizontal midpoints
    const {
      left: mapOffsetLeft,
      top: mapOffsetTop,
      height: mapHeight,
      width: mapWidth,
    } = markerService.getElementPositionData('map-container-inner');
    const mapMidVertical = mapWidth / 2;
    const mapMidHorizontal = mapHeight / 2;

    // get place marker data
    const {
      left: markerOffsetLeft,
      top: markerOffsetTop,
    } = markerService.getElementPositionData(placeId);
    const relativeMarkerPositionLeft = markerOffsetLeft - mapOffsetLeft;
    const relativeMarkerPositionTop = markerOffsetTop - mapOffsetTop;

    // determine whether default fold (down, right) needs to be changed based on
    // relative position of marker to map midpoints
    foldLeft = relativeMarkerPositionLeft > mapMidVertical;
    foldTop = relativeMarkerPositionTop > mapMidHorizontal;

    // get actual height of element from hidden 'twin'. add some tolerance at offset for better visual
    // design. only needed for case foldTop === true
    const {
      height: observedMarkerHeight,
    } = markerService.getElementPositionData(`${placeId}-hidden`);
    const heightShiftTolerance = 8;
    markerOffsetTopComputed = foldTop
      ? `-${observedMarkerHeight + heightShiftTolerance}px`
      : 'unset';
  }
  return {
    foldLeft,
    foldTop,
    markerOffsetTopComputed,
  };
};

/**
 * Based on the visibility of an element, determine the suffix of the html-id.
 * This is needed since some elements have variable height based on their content,
 * thus if that height is to be used in a calculation it has to be read from an already rendered html element.
 * This function provides the id for the visible and hidden variant of such an element.
 * @method
 * @param   {string}  id Identifier for the HTML element
 * @param   {boolean}  isHidden Determines if the element is a visible component
 *
 * @returns {boolean} data required to determine the style of a Hoverbox component
 */
export const composeId = (id, isHidden = false) => {
  return isHidden ? `${id}-hidden` : `${id}-visible`;
};