Home Reference Source Test

src/core/classes/Region.js

/**
 * @file Region.js
 */

import Binder from './Binder.js';
import Binding from './Binding.js';
import Gesture from './../../gestures/Gesture.js';
import arbiter from './../arbiter.js';
import State from './State.js';
import util from './../util.js';

/**
 * Allows the user to specify a region to capture all events to feed ZingTouch into. This can be as narrow as
 * the element itself, or as big as the document itself. The more specific an area, the better performant the
 * overall application will perform. Contains API methods to bind/unbind specific elements
 * to corresponding gestures. Also contains the ability to register/unregister new gestures.
 * @class Region
 */
class Region {

  /**
   * Constructor function for the Region class.
   * @param {Element} element - The element to capture all window events in that region to feed into ZingTouch.
   * @param {boolean} [capture=false] - Whether the region listens for captures or bubbles.
   * @param {boolean} [preventDefault=true] - Whether the default browser functionality should be disabled;
   * @param {Number} id - The id of the region, assigned by the ZingTouch object.
   */
  constructor(element, capture, preventDefault, id) {

    /**
     * The identifier for the Region. This is assigned by the ZingTouch object and is used to hash gesture ids
     * for uniqueness.
     * @type {Number}
     */
    this.id = id;

    /**
     * The element being bound to.
     * @type {Element}
     */
    this.element = element;

    /**
     * Whether the region listens for captures or bubbles.
     * @type {boolean}
     */
    this.capture = (typeof capture !== 'undefined') ? capture : false;

    /**
     * Boolean to disable browser functionality such as scrolling and zooming over the region
     * @type {boolean}
     */
    this.preventDefault = (typeof preventDefault !== 'undefined') ? preventDefault : true;

    /**
     * The internal state object for a Region. Keeps track of registered gestures, inputs, and events.
     * @type {State}
     */
    this.state = new State(id);

    var eventNames = [];
    if (window.PointerEvent) {
      eventNames = ['pointerdown', 'pointermove', 'pointerup'];
    } else {
      eventNames = ['mousedown', 'mousemove', 'mouseup', 'touchstart', 'touchmove', 'touchend'];
    }

    //Bind detected browser events to the region element.
    eventNames.map((name) => {
      element.addEventListener(name, (e) => {
        arbiter(e, this);
      }, this.capture);
    });
  }

  /**
   * Bind an element to a registered/unregistered gesture with multiple function signatures.
   * @example
   * bind(element) - chainable
   * @example
   * bind(element, gesture, handler, [capture])
   * @param {Element} element - The element object.
   * @param {String|Object} [gesture] - Gesture key, or a Gesture object.
   * @param {Function} [handler] - The function to execute when an event is emitted.
   * @param {Boolean} [capture] - capture/bubble
   * @param {Boolean} [bindOnce = false] - Option to bind once and only emit the event once.
   * @returns {Object} - a chainable object that has the same function as bind.
   */
  bind(element, gesture, handler, capture, bindOnce) {
    if (!element || (element && !element.tagName)) {
      throw 'Bind must contain an element';
    }

    bindOnce = (typeof bindOnce !== 'undefined') ? bindOnce : false;
    if (!gesture) {
      return new Binder(element, bindOnce, this.state);
    } else {
      this.state.addBinding(element, gesture, handler, capture, bindOnce);
    }
  }

  /*bind*/

  /**
   * Bind an element and sets up actions to remove the binding once it has been emitted
   * for the first time.
   * 1. bind(element) - chainable
   * 2. bind(element, gesture, handler, [capture])
   * @param {Element} element - The element object.
   * @param {String|Object} gesture - Gesture key, or a Gesture object.
   * @param {Function} handler - The function to execute when an event is emitted.
   * @param {Boolean} capture - capture/bubble
   * @returns {Object} - a chainable object that has the same function as bind.
   */
  bindOnce(element, gesture, handler, capture) {
    this.bind(element, gesture, handler, capture, true);
  }

  /*bindOnce*/

  //noinspection JSMethodCanBeStatic
  /**
   * Unbinds an element from either the specified gesture or all if no element is specified.
   * @param {Element} element -The element to remove.
   * @param {String | Object} [gesture] - A String representing the gesture, or the actual object being used.
   * @returns {Array} - An array of Bindings that were unbound to the element;
   */
  unbind(element, gesture) {
    var bindings = this.state.retrieveBindingsByElement(element);
    var unbound = [];

    bindings.forEach((binding) => {
      if (gesture) {
        if (typeof gesture === 'string' && this.state.registeredGestures[gesture]) {
          var registeredGesture = this.state.registeredGestures[gesture];
          if (registeredGesture.id === binding.gesture.id) {
            element.removeEventListener(binding.gesture.getId(), binding.handler, binding.capture);
            unbound.push(binding);
          }
        }
      } else {
        element.removeEventListener(binding.gesture.getId(), binding.handler, binding.capture);
        unbound.push(binding);
      }
    });

    return unbound;
  }

  /*unbind*/

  /**
   * Registers a new gesture with an assigned key
   * @param {String} key - The key used to register an element to that gesture
   * @param {Gesture} gesture - A gesture object
   */
  register(key, gesture) {
    if (typeof key !== 'string') {
      throw new Error('Parameter key is an invalid string');
    }

    if (!gesture instanceof Gesture) {
      throw new Error('Parameter gesture is an invalid Gesture object');
    }

    gesture.setType(key);
    this.state.registerGesture(gesture, key);
  }

  /*register*/

  /**
   * Un-registers a gesture from the Region's state such that it is no longer emittable.
   * Unbinds all events that were registered with the type.
   * @param {String|Object} key - Gesture key that was used to register the object
   * @returns {Object} - The Gesture object that was unregistered or null if it could not be found.
   */
  unregister(key) {
    this.state.bindings.forEach((binding) => {
      if (binding.gesture.getType() === key) {
        binding.element.removeEventListener(binding.gesture.getId(),
          binding.handler, binding.capture);
      }
    });

    var registeredGesture = this.state.registeredGestures[key];
    delete this.state.registeredGestures[key];
    return registeredGesture;
  }
}

export default Region;