ColorHandler.js

'use strict';

import $ from 'jquery';
import ColorItem from './ColorItem';

/**
 * Handles everything related to the colorpicker color
 * @ignore
 */
class ColorHandler {
  /**
   * @param {Colorpicker} colorpicker
   */
  constructor(colorpicker) {
    /**
     * @type {Colorpicker}
     */
    this.colorpicker = colorpicker;
  }

  /**
   * @returns {*|String|ColorItem}
   */
  get fallback() {
    return this.colorpicker.options.fallbackColor ?
      this.colorpicker.options.fallbackColor : (this.hasColor() ? this.color : null);
  }

  /**
   * @returns {String|null}
   */
  get format() {
    if (this.colorpicker.options.format) {
      return this.colorpicker.options.format;
    }

    if (this.hasColor() && this.color.hasTransparency() && this.color.format.match(/^hex/)) {
      return this.isAlphaEnabled() ? 'rgba' : 'hex';
    }

    if (this.hasColor()) {
      return this.color.format;
    }

    return 'rgb';
  }

  /**
   * Internal color getter
   *
   * @type {ColorItem|null}
   */
  get color() {
    return this.colorpicker.element.data('color');
  }

  /**
   * Internal color setter
   *
   * @ignore
   * @param {ColorItem|null} value
   */
  set color(value) {
    this.colorpicker.element.data('color', value);

    if ((value instanceof ColorItem) && (this.colorpicker.options.format === 'auto')) {
      // If format is 'auto', use the first parsed one from now on
      this.colorpicker.options.format = this.color.format;
    }
  }

  bind() {
    // if the color option is set
    if (this.colorpicker.options.color) {
      this.color = this.createColor(this.colorpicker.options.color);
      return;
    }

    // if element[color] is empty and the input has a value
    if (!this.color && !!this.colorpicker.inputHandler.getValue()) {
      this.color = this.createColor(
        this.colorpicker.inputHandler.getValue(), this.colorpicker.options.autoInputFallback
      );
    }
  }

  unbind() {
    this.colorpicker.element.removeData('color');
  }

  /**
   * Returns the color string from the input value or the 'data-color' attribute of the input or element.
   * If empty, it returns the defaultValue parameter.
   *
   * @returns {String|*}
   */
  getColorString() {
    if (!this.hasColor()) {
      return '';
    }

    return this.color.string(this.format);
  }

  /**
   * Sets the color value
   *
   * @param {String|ColorItem} val
   */
  setColorString(val) {
    let color = val ? this.createColor(val) : null;

    this.color = color ? color : null;
  }

  /**
   * Creates a new color using the widget instance options (fallbackColor, format).
   *
   * @fires Colorpicker#colorpickerInvalid
   * @param {*} val
   * @param {boolean} fallbackOnInvalid
   * @param {boolean} autoHexInputFallback
   * @returns {ColorItem}
   */
  createColor(val, fallbackOnInvalid = true, autoHexInputFallback = false) {
    let disableHexInputFallback = !fallbackOnInvalid && !autoHexInputFallback;

    let color = new ColorItem(this.resolveColorDelegate(val), this.format, disableHexInputFallback);

    if (!color.isValid()) {
      if (fallbackOnInvalid) {
        color = this.getFallbackColor();
      }

      /**
       * (Colorpicker) Fired when the color is invalid and the fallback color is going to be used.
       *
       * @event Colorpicker#colorpickerInvalid
       */
      this.colorpicker.trigger('colorpickerInvalid', color, val);
    }

    if (!this.isAlphaEnabled()) {
      // Alpha is disabled
      color.alpha = 1;
    }

    return color;
  }

  getFallbackColor() {
    if (this.fallback && (this.fallback === this.color)) {
      return this.color;
    }

    let fallback = this.resolveColorDelegate(this.fallback);

    let color = new ColorItem(fallback, this.format);

    if (!color.isValid()) {
      console.warn('The fallback color is invalid. Falling back to the previous color or black if any.');
      return this.color ? this.color : new ColorItem('#000000', this.format);
    }

    return color;
  }

  /**
   * @returns {ColorItem}
   */
  assureColor() {
    if (!this.hasColor()) {
      this.color = this.getFallbackColor();
    }

    return this.color;
  }

  /**
   * Delegates the color resolution to the colorpicker extensions.
   *
   * @param {String|*} color
   * @param {boolean} realColor if true, the color should resolve into a real (not named) color code
   * @returns {ColorItem|String|*|null}
   */
  resolveColorDelegate(color, realColor = true) {
    let extResolvedColor = false;

    $.each(this.colorpicker.extensions, function (name, ext) {
      if (extResolvedColor !== false) {
        // skip if resolved
        return;
      }
      extResolvedColor = ext.resolveColor(color, realColor);
    });

    return extResolvedColor ? extResolvedColor : color;
  }

  /**
   * Checks if there is a color object, that it is valid and it is not a fallback
   * @returns {boolean}
   */
  isInvalidColor() {
    return !this.hasColor() || !this.color.isValid();
  }

  /**
   * Returns true if the useAlpha option is exactly true, false otherwise
   * @returns {boolean}
   */
  isAlphaEnabled() {
    return (this.colorpicker.options.useAlpha !== false);
  }

  /**
   * Returns true if the current color object is an instance of Color, false otherwise.
   * @returns {boolean}
   */
  hasColor() {
    return this.color instanceof ColorItem;
  }
}

export default ColorHandler;