SliderHandler.js

'use strict';

import $ from 'jquery';

/**
 * Class that handles all configured sliders on mouse or touch events.
 * @ignore
 */
class SliderHandler {
  /**
   * @param {Colorpicker} colorpicker
   */
  constructor(colorpicker) {
    /**
     * @type {Colorpicker}
     */
    this.colorpicker = colorpicker;
    /**
     * @type {*|String}
     * @private
     */
    this.currentSlider = null;
    /**
     * @type {{left: number, top: number}}
     * @private
     */
    this.mousePointer = {
      left: 0,
      top: 0
    };

    /**
     * @type {Function}
     */
    this.onMove = $.proxy(this.defaultOnMove, this);
  }

  /**
   * This function is called every time a slider guide is moved
   * The scope of "this" is the SliderHandler object.
   *
   * @param {int} top
   * @param {int} left
   */
  defaultOnMove(top, left) {
    if (!this.currentSlider) {
      return;
    }

    let slider = this.currentSlider, cp = this.colorpicker, ch = cp.colorHandler;

    // Create a color object
    let color = !ch.hasColor() ? ch.getFallbackColor() : ch.color.getClone();

    // Adjust the guide position
    slider.guideStyle.left = left + 'px';
    slider.guideStyle.top = top + 'px';

    // Adjust the color
    if (slider.callLeft) {
      color[slider.callLeft](left / slider.maxLeft);
    }
    if (slider.callTop) {
      color[slider.callTop](top / slider.maxTop);
    }

    // Set the new color
    cp.setValue(color);
    cp.popupHandler.focus();
  }

  /**
   * Binds the colorpicker sliders to the mouse/touch events
   */
  bind() {
    let sliders = this.colorpicker.options.horizontal ? this.colorpicker
      .options.slidersHorz : this.colorpicker.options.sliders;

    let sliderClasses = [];

    for (let sliderName in sliders) {
      if (!sliders.hasOwnProperty(sliderName)) {
        continue;
      }

      sliderClasses.push(sliders[sliderName].selector);
    }

    this.colorpicker.picker.find(sliderClasses.join(', '))
      .on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.pressed, this));
  }

  /**
   * Unbinds any event bound by this handler
   */
  unbind() {
    $(this.colorpicker.picker).off({
      'mousemove.colorpicker': $.proxy(this.moved, this),
      'touchmove.colorpicker': $.proxy(this.moved, this),
      'mouseup.colorpicker': $.proxy(this.released, this),
      'touchend.colorpicker': $.proxy(this.released, this)
    });
  }

  /**
   * Function triggered when clicking in one of the color adjustment bars
   *
   * @private
   * @fires Colorpicker#mousemove
   * @param {Event} e
   */
  pressed(e) {
    if (this.colorpicker.isDisabled()) {
      return;
    }
    this.colorpicker.lastEvent.alias = 'pressed';
    this.colorpicker.lastEvent.e = e;

    if (!e.pageX && !e.pageY && e.originalEvent && e.originalEvent.touches) {
      e.pageX = e.originalEvent.touches[0].pageX;
      e.pageY = e.originalEvent.touches[0].pageY;
    }
    // e.stopPropagation();
    // e.preventDefault();

    let target = $(e.target);

    // detect the slider and set the limits and callbacks
    let zone = target.closest('div');

    let sliders = this.colorpicker.options.horizontal ? this.colorpicker
      .options.slidersHorz : this.colorpicker.options.sliders;

    if (zone.is('.colorpicker')) {
      return;
    }

    this.currentSlider = null;

    for (let sliderName in sliders) {
      if (!sliders.hasOwnProperty(sliderName)) {
        continue;
      }

      let slider = sliders[sliderName];

      if (zone.is(slider.selector)) {
        this.currentSlider = $.extend({}, slider, {name: sliderName});
        break;
      } else if (slider.childSelector !== undefined && zone.is(slider.childSelector)) {
        this.currentSlider = $.extend({}, slider, {name: sliderName});
        zone = zone.parent(); // zone.parents(slider.selector).first() ?
        break;
      }
    }

    let guide = zone.find('.colorpicker-guide').get(0);

    if (this.currentSlider === null || guide === null) {
      return;
    }

    let offset = zone.offset();

    // reference to guide's style
    this.currentSlider.guideStyle = guide.style;
    this.currentSlider.left = e.pageX - offset.left;
    this.currentSlider.top = e.pageY - offset.top;
    this.mousePointer = {
      left: e.pageX,
      top: e.pageY
    };

    // TODO: fix moving outside the picker makes the guides to keep moving. The event needs to be bound to the window.
    /**
     * (window.document) Triggered on mousedown for the document object,
     * so the color adjustment guide is moved to the clicked position.
     *
     * @event Colorpicker#mousemove
     */
    $(this.colorpicker.picker).on({
      'mousemove.colorpicker': $.proxy(this.moved, this),
      'touchmove.colorpicker': $.proxy(this.moved, this),
      'mouseup.colorpicker': $.proxy(this.released, this),
      'touchend.colorpicker': $.proxy(this.released, this)
    }).trigger('mousemove');
  }

  /**
   * Function triggered when dragging a guide inside one of the color adjustment bars.
   *
   * @private
   * @param {Event} e
   */
  moved(e) {
    this.colorpicker.lastEvent.alias = 'moved';
    this.colorpicker.lastEvent.e = e;

    if (!e.pageX && !e.pageY && e.originalEvent && e.originalEvent.touches) {
      e.pageX = e.originalEvent.touches[0].pageX;
      e.pageY = e.originalEvent.touches[0].pageY;
    }

    // e.stopPropagation();
    e.preventDefault(); // prevents scrolling on mobile

    let left = Math.max(
      0,
      Math.min(
        this.currentSlider.maxLeft,
        this.currentSlider.left + ((e.pageX || this.mousePointer.left) - this.mousePointer.left)
      )
    );

    let top = Math.max(
      0,
      Math.min(
        this.currentSlider.maxTop,
        this.currentSlider.top + ((e.pageY || this.mousePointer.top) - this.mousePointer.top)
      )
    );

    this.onMove(top, left);
  }

  /**
   * Function triggered when releasing the click in one of the color adjustment bars.
   *
   * @private
   * @param {Event} e
   */
  released(e) {
    this.colorpicker.lastEvent.alias = 'released';
    this.colorpicker.lastEvent.e = e;

    // e.stopPropagation();
    // e.preventDefault();

    $(this.colorpicker.picker).off({
      'mousemove.colorpicker': this.moved,
      'touchmove.colorpicker': this.moved,
      'mouseup.colorpicker': this.released,
      'touchend.colorpicker': this.released
    });
  }
}

export default SliderHandler;