nus-mtp/sashimi-note

View on GitHub
sashimi-webapp/src/logic/inputHandler/DocumentNavigator/index.js

Summary

Maintainability
B
5 hrs
Test Coverage
import interact from 'interactjs';
import domUtils from 'src/helpers/domUtils';
import unitConverter from 'src/helpers/unitConverter';
import elementUtils from 'src/helpers/elementUtils';
import EventHM from './EventHandlerManager';
import CssTransformer from './CssTransformer';

/**
 * Document Navigator manage the input halding of Pages mode
 * @param {string | Element} targetReference - Receive either a string Id or an element reference.
 * @param {Element} resizeObserveTarget - If this element is provided, it will be
 *                                        used to track the change in document
 *                                        parent's width
 */
const DocumentNavigator = function DocumentNavigator(targetReference, resizeObserveTarget) {
  // Initialise DocumentNavigator properties
  this.updateElementReference(targetReference);
  this.transform = new CssTransformer(this.el.container);
  this.resizeObserveTarget = resizeObserveTarget || null;

  // 1. Set viewport on init;
  this.eventHandler = new EventHM(this);
  this.updateElementWidth();

  // 2. Set up event listener and DOM properties;
  this.setDomBehaviour();
};

DocumentNavigator.prototype.updateElementReference = function updateElementReference(targetReference) {
  const containerReference = elementUtils.resolveElement(targetReference);
  if (!containerReference) {
    throw new Error('Element provided to DocumentNavigator is not found at this moment');
  }

  this.el = {
    container: containerReference,
    parent: containerReference.parentNode,
    element: containerReference.childNodes[0],
    html: containerReference.ownerDocument.body.parentNode
  };
};

DocumentNavigator.prototype.updateElementWidth = function updateElementWidth() {
  if (!this.width) this.width = {};
  Object.keys(this.el).forEach((key) => {
    const elementReference = this.el[key];
    let elementWidth = domUtils.getComputedStyle(elementReference).width;
    // Temporary fix for Firefox computing width as auto
    elementWidth = elementWidth || '0px';
    if (elementWidth === 'auto') elementWidth = `${elementReference.scrollWidth}px`;
    this.width[key] = unitConverter.get(elementWidth, 'px', false);
  });

  this.eventHandler.eventFn.dom.resize.call(this);
};

DocumentNavigator.prototype.setDomBehaviour = function setDomBehaviour() {
  this.addDomStyling();
  this.addEventListeners();
};

DocumentNavigator.prototype.unsetDomBehaviour = function setDomBehaviour() {
  this.removeEventListeners();
  this.removeDomStyling();
};


DocumentNavigator.prototype.addDomStyling = function addDomStyling() {
  // Reset parent height to force DOM relayout
  // [1] Initialise layout properties
  const containerReference = this.el.container;
  const containerRefStyle = domUtils.getComputedStyle(containerReference);
  containerReference.documentNavigator = {
    originalStyle: {
      height: containerRefStyle.height,
      width: containerRefStyle.width
    }
  };

  setTimeout(() => {
    // [2] Initialise layout properties
    let tempContainerHeight = domUtils.getComputedStyle(this.el.container).height;
    let tempContainerWidth = domUtils.getComputedStyle(this.el.element).width;

    if (tempContainerHeight === 'auto') tempContainerHeight = this.el.container.scrollHeight;
    if (tempContainerWidth === 'auto') tempContainerWidth = this.el.element.scrollWidth;

    tempContainerHeight = parseFloat(tempContainerHeight) + 500;
    tempContainerWidth = parseFloat(tempContainerWidth);
    containerReference.documentNavigator.originalStyle.height = tempContainerHeight;
    containerReference.documentNavigator.originalStyle.width = tempContainerWidth;

    const renderHeight = `${tempContainerHeight * this.transform.scale}px`;
    const renderWidth = `${tempContainerWidth * this.transform.scale}px`;
    this.el.parent.style.height = renderHeight;
    this.el.parent.style.width = renderWidth;
  }, 1000);

  // Store default parent reference properties before overwriting
  this.defaultProperties = {
    html: {
      attribute: {
        touchEvent: this.el.html.getAttribute('touch-action'),
      }
    },
    parent: {
      style: {
        padding: this.el.parent.style.padding,
        margin: this.el.parent.style.margin,
        width: this.el.parent.style.width
      }
    },
    container: {
      style: {
        /* eslint quote-props: 0 */
        width: this.el.container.style.width,
        margin: this.el.container.style.margin,
        position: this.el.container.style.position,
        'transform-origin': this.el.container.style['transform-origin']
      }
    }
  };

  const newProperties = {
    html: {
      attribute: {
        touchEvent: 'pan-x pan-y',
      }
    },
    parent: {
      style: {
        padding: 0,
        margin: '0 auto',
        width: '100%',
      }
    },
    container: {
      style: {
        /* eslint quote-props: 0 */
        width: '1px',
        margin: '0 auto',
        position: 'relative',
        'transform-origin': 'center top',
      }
    }
  };

  Object.keys(newProperties)
        .forEach((elementKey) => {
          if (newProperties[elementKey].style) {
            domUtils.overwriteStyle(this.el[elementKey].style, newProperties[elementKey].style);
          }
          if (newProperties[elementKey].attribute) {
            Object.keys(newProperties[elementKey].attribute)
                  .forEach((attrKey) => {
                    this.el[elementKey].setAttribute(attrKey, newProperties[elementKey].attribute[attrKey]);
                  });
          }
        });
};

DocumentNavigator.prototype.removeDomStyling = function removeDomStyling() {
  Object.keys(this.defaultProperties)
        .forEach((elementKey) => {
          if (this.defaultProperties[elementKey].style) {
            domUtils.overwriteStyle(this.el[elementKey].style, this.defaultProperties[elementKey].style);
          }
          if (this.defaultProperties[elementKey].attribute) {
            Object.keys(this.defaultProperties[elementKey].attribute)
                  .forEach((attrKey) => {
                    this.el[elementKey].setAttribute(attrKey, this.defaultProperties[elementKey].attribute[attrKey]);
                  });
          }
        });
};

DocumentNavigator.prototype.addEventListeners = function addEventListeners() {
  this.eventInstance = {
    pointers: {},
    numPointers: 0,
    state: {
      action: null,
      pointerType: null
    }
  };

  const iframeDoc = this.el.parent.ownerDocument;
  const iframeWin = iframeDoc.defaultView || iframeDoc.parentWindow;
  this.interactable = interact(this.el.html);
  this.interactable.draggable(this.eventHandler.settings.draggable);
  this.interactable.gesturable(this.eventHandler.settings.gesturable);

  this.eventListeners = [
    {
      event: 'DOMMouseScroll',
      fn: this.eventHandler.eventFn.mousewheel.bind(this),
      target: iframeWin,
      boolean: false
    }, {
      event: 'mousewheel',
      fn: this.eventHandler.eventFn.mousewheel.bind(this),
      target: iframeWin,
      boolean: false
    }, {
      event: 'pointerdown',
      fn: this.eventHandler.eventFn.pointer.down.bind(this),
      target: iframeWin,
    }, {
      event: 'pointerup',
      fn: this.eventHandler.eventFn.pointer.up.bind(this),
      target: iframeWin,
    }, {
      event: 'resize',
      fn: this.updateElementWidth.bind(this),
      target: iframeWin,
    }
  ];

  // Add resize observer if it exist
  if (this.resizeObserveTarget) {
    this.eventListeners.push({
      event: 'transitionend',
      fn: this.updateElementWidth.bind(this),
      target: this.resizeObserveTarget
    });
  }

  this.eventListeners.forEach((listener) => {
    listener.target.addEventListener(listener.event, listener.fn, listener.boolean);
  });
};

DocumentNavigator.prototype.removeEventListeners = function removeEventListeners() {
  this.interactable.unset();

  this.eventListeners.forEach((listener) => {
    listener.target.removeEventListener(listener.event, listener.fn);
  });
};

export default DocumentNavigator;