Source: Randrix.class.js

import Validate from './helper/Validation.class';

/**
 * @fileOverview Randrix Class.
 * @author Simon Gattner <npm@0x38.de>
 * @license MIT
 * @version 1.0.2
 * @module {es6} Randrix
 */
export default class Randrix {
  /**
   * Display a random matrix panel.
   * @class Randrix
   * @classdesc Class to display a Randrix.
   * @param {object} options The Randrix options.
   * @param {string} options.message The Randrix message to display.
   * @param {string} options.selector The Randrix element selector.
   *  Default is '[data-randrix]'.
   * @param {number} options.width The Randrix width to display.
   *  Default is 5.
   * @param {number} options.height The Randrix height to display.
   *  Default is 6.
   * @param {number} options.interval The Randrix interval to display.
   *  Default is 100.
   * @param {string} options.possible The Randrix possible Chars to display.
   *  Default is 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.'.
   * @param {array} options.style The Randrix random Char styles to display.
   *  Default is undefined.
   * @param {function} options.callback The Randrix callback to
   *  run after stop. Default is null.
   */
  constructor(options) {
    this._defaults = {
      message: undefined,
      selector: '[data-randrix]',
      width: 5,
      height: 6,
      interval: 100,
      possible: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
      style: undefined,
      callback: null,
    };
    this._options = Validate.options(options);
    if (this._options.error) return this.throwError();
    // ES2018 this._settings = {...this._defaults, ...this._options};
    this._settings = Object.assign({}, this._defaults, this._options);
    this._message = this._settings.message.split('');
    this._element = document.querySelector(this._settings.selector);
    this._width = this._settings.width;
    this._height = this._settings.height;
    this._interval = this._settings.interval;
    this._possible = this._settings.possible;
    this._possibleLength = this._possible.length;
    this._style = this._settings.style;
    this._styleLength = (this._style) ? this._style.length : 0;
    this._styleEnable = this._styleLength > 0;
    this._index = 0;
    this._matrix = [];
    this._x = 0;
    this._y = 0;
    this._callback = this._settings.callback;
    this._calledback = false;
  }

  /**
   * Throw Errors if Randrix has invalid options.
   * @function Randrix.throwErrors
   */
  throwError() {
    throw new Error(this._options.error);
  }

  /**
   * Is there a callback and not executed yet?
   * @function Randrix.isCallback
   * @return {boolean} is there a callback or not and is it executed or not.
   */
  isCallback() {
    return typeof this._callback === 'function' && this._calledback === false;
  }

  /**
   * Random Char from Possible Chars
   * @function Randrix.char
   * @return {string} the Char.
   */
  char() {
    return this._possible.charAt(
      Math.floor(
        Math.random() * this._possibleLength
      )
    );
  }

  /**
   * Style the Char from Possible Styles
   * @function Randrix.charStyle
   * @return {string} the Char Style.
   */
  charStyle() {
    return (this._style) ? this._style[
      Math.floor(Math.random() * this._styleLength)
    ] : '';
  }

  /**
   * Create a Char on the Randrix
   * @function Randrix.charCreate
   * @param {number} x position of the Char.
   * @param {number} y position of the Char.
   * @return {boolean} is the Char created.
   */
  charCreate(x, y) {
    const id = `x${x}-y${y}`;
    this._matrix[id] = {
      element: this.charCreateElement(id) || {},
      x: x,
      y: y,
      interval: this.charStart(id) || (() => false),
    };
    return true;
  }

  /**
   * Create the Char Element on the Randrix
   * @function Randrix.charCreateElement
   * @param {object} id of the Char.
   * @return {object} element of the Char.
   */
  charCreateElement(id) {
    const char = this.char();
    const element = document.createElement('span');
    const text = document.createTextNode(char);

    element.appendChild(text);
    element.setAttribute('data-randrix-char', id);

    if (this._styleEnable && this.charStyle()) {
      element.setAttribute('data-randrix-style', this.charStyle());
    }
    this._element.appendChild(element);

    return element;
  }

  /**
   * Char is the Last Child on the Randrix
   * @function Randrix.charIsLastChild
   * @param {string} id of the Char.
   * @return {boolean} Char is the last child or not.
   */
  charIsLastChild(id) {
    return this._x === this._width - 1 && this._y === this._height - 1;
  }

  /**
   * Update the Char on the Randrix
   * @function Randrix.charUpdate
   * @param {string} id of the Char.
   */
  charUpdate(id) {
    const char = this.char();

    this.charUpdateElement(id, char);
    this.charUpdateStatus(id, char);
  }

  /**
   * Update the Char on the Randrix
   * @function Randrix.charUpdateElement
   * @param {string} id of the Char.
   * @param {string} char to update.
   */
  charUpdateElement(id, char) {
    if (this._matrix[id] && this._matrix[id].element) {
      if (this._styleEnable) {
        this._matrix[id].element
          .setAttribute('data-randrix-style', this.charStyle());
      }
      this._matrix[id].element.innerHTML = char;
    }
  }

  /**
   * Update the Char on the Randrix
   * @function Randrix.charUpdateStatus
   * @param {string} id of the Char.
   * @param {string} char to update.
   */
  charUpdateStatus(id, char) {
    if (this._message.length !== this._index) {
      (this.charIsLastChild()) ? this.reset() :
        (!this.charMatch(id, char)) || this.charActivate(id);
    } else {
      this.charStop(id);
      if (this.isCallback()) this._calledback = true && this._callback();
    }
  }

  /**
   * Match the Char on the Randrix
   * @function Randrix.charMatch
   * @param {object} id of the Char.
   * @param {string} char to match.
   * @return {boolean} char has matched or not.
   */
  charMatch(id, char) {
    if (this.charMatchIndex(char) && this.charMatchPosition(id)) {
      if (this._debug) {
        // eslint-disable-next-line no-console
        console.log(
          'next', 'x' + this._x, 'y' + this._y, 'c' + char
        );
      }
      return true;
    } else {
      return false;
    }
  }

  /**
   * Match the Char Position on the Randrix
   * @function Randrix.charMatchIndex
   * @param {string} char to match.
   * @return {boolean} char has index matched or not.
   */
  charMatchIndex(char) {
    return this._message[this._index] === char;
  }

  /**
   * Match the Char Position on the Randrix
   * @function Randrix.charMatchPosition
   * @param {object} id of the Char.
   * @return {boolean} char has position matched or not.
   */
  charMatchPosition(id) {
    return (this._matrix[id]) ?
      // start in the first lines
      (this._index === 0 && this._matrix[id].x === 0) ||
       // forward in the current line
      (this._x === this._matrix[id].x && this._y + 1 === this._matrix[id].y) ||
      // forward to the next line
      (this._x + 1 === this._matrix[id].x && this._matrix[id].y === 0) :
    false;
  }

  /**
   * Activate the Char on the Randrix
   * @function Randrix.charActivate
   * @param {string} id of the Char.
   * @return {boolean} is the Char successful activated.
   */
  charActivate(id) {
    if (this._matrix[id]) {
      this._index = this._index + 1;
      this._x = this._matrix[id].x;
      this._y = this._matrix[id].y;
      this.charStop(id);
      this._matrix[id].element
        .setAttribute('data-randrix-char-active', 'true');
      return true;
    } else {
      return false;
    }
  }

  /**
   * Stop the Char on the Randrix
   * @function Randrix.charStop
   * @param {string} id of the Char.
   * @return {function} clearInterval function of the Char.
   */
  charStop(id) {
    return (this._matrix[id]) ? clearInterval(this._matrix[id].interval) :
      () => false;
  }

  /**
   * Start the Char on the Randrix
   * @function Randrix.charStart
   * @param {string} id of the Char.
   * @return {function} setInterval function of the Char.
   */
  charStart(id) {
    return setInterval(() => {
      this.charUpdate(id);
    }, this._interval);
  }

  /**
   * Create the Randrix
   * @function Randrix.create
   * @return {boolean} done.
   */
  create() {
    for (let x = 0; x < this._width; x++) {
      for (let y = 0; y < this._height; y++) {
        this.charCreate(x, y);
      }
    }
    return true;
  }

  /**
   * Reset the Randrix
   * @function Randrix.reset
   * @return {boolean} done.
   */
  reset() {
    this._element.innerHTML = '';
    this.stop(
      () => {
        this.constructor(this._settings);
        this.start();
      }
    );
    return true;
  }

  /**
   * Stop the Randrix
   * @function Randrix.stop
   * @param {function} callback to execute.
   * @return {boolean} done.
   */
  stop(callback) {
    for (const item in this._matrix) {
      if (this._matrix.hasOwnProperty(item)) {
        clearInterval(this._matrix[item].interval);
      }
    }
    if (typeof callback === 'function') callback();
    return true;
  }

  /**
   * Start the Randrix
   * @function Randrix.start
   * @return {boolean} done.
   */
  start() {
    (this._index === 0) ? this.create() : this.reset();
    return true;
  }
}