zingchart/zingtouch

View on GitHub
src/core/arbiter.js

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * @file arbiter.js
 * Contains logic for the dispatcher
 */

import dispatcher from './dispatcher.js';
import interpreter from './interpreter.js';
import util from './util.js';

/**
 * Function that handles event flow, negotiating with the interpreter,
 * and dispatcher.
 * 1. Receiving all touch events in the window.
 * 2. Determining which gestures are linked to the target element.
 * 3. Negotiating with the Interpreter what event should occur.
 * 4. Sending events to the dispatcher to emit events to the target.
 * @param {Event} event - The event emitted from the window object.
 * @param {Object} region - The region object of the current listener.
 */
function arbiter(event, region) {
  const state = region.state;
  const eventType = util.normalizeEvent[ event.type ];

  /*
   Return if a gesture is not in progress and won't be. Also catches the case
   where a previous event is in a partial state (2 finger pan, waits for both
   inputs to reach touchend)
   */
  if (state.inputs.length === 0 && eventType !== 'start') {
    return;
  }

  /*
   Check for 'stale' or events that lost focus
   (e.g. a pan goes off screen/off region.)
   Does not affect mobile devices.
   */
  if (typeof event.buttons !== 'undefined' &&
    eventType !== 'end' &&
    event.buttons === 0) {
    state.resetInputs();
    return;
  }

  // Update the state with the new events. If the event is stopped, return;
  if (!state.updateInputs(event, region.element)) {
    return;
  }

  // Retrieve the initial target from any one of the inputs
  const bindings = state.retrieveBindingsByInitialPos();
  if (bindings.length > 0) {
    if (region.preventDefault) {
      util.setMSPreventDefault(region.element);
      util.preventDefault(event);
    } else {
      util.removeMSPreventDefault(region.element);
    }

    const toBeDispatched = {};
    const gestures = interpreter(bindings, event, state);

    /* Determine the deepest path index to emit the event
     from, to avoid duplicate events being fired. */

    const path = util.getPropagationPath(event);
    gestures.forEach((gesture) => {
      const id = gesture.binding.gesture.getId();
      if (toBeDispatched[id]) {
        if (util.getPathIndex(path, gesture.binding.element) <
          util.getPathIndex(path, toBeDispatched[id].binding.element)) {
          toBeDispatched[id] = gesture;
        }
      } else {
        toBeDispatched[id] = gesture;
      }
    });

    Object.keys(toBeDispatched).forEach((index) => {
      const gesture = toBeDispatched[index];
      dispatcher(gesture.binding, gesture.data, gesture.events);
    });
  }

  let endCount = 0;
  state.inputs.forEach((input) => {
    if (input.getCurrentEventType() === 'end') {
      endCount++;
    }
  });

  if (endCount === state.inputs.length) {
    state.resetInputs();
  }
}

export default arbiter;