playbutton.js

import { Component } from "./component.js";
import { Style } from "./style.js";

/**
 * Creates a clickable pushbutton with play/pause icons.
 * <div><img src="https://www.minicomps.org/images/playbutton.png"/></div>
 * @example
 * const panel = new Panel(document.body, 20, 20, 200, 200);
 * new PlayButton(panel, 20, 20, false, event => console.log("playing!"));
 * @extends Component
 */
export class PlayButton extends Component {
  /**
   * Constructor
   * @param {HTMLElement} parent - The element to add this play button to.
   * @param {number} x - The x position of the play button. Default 0.
   * @param {number} y - The y position of the play button. Default 0.
   * @param {boolean} playing - Whether the play button initially shows as playing.
   * @param {function} defaultHandler - A function that will handle the "click" event.
   */
  constructor(parent, x, y, playing, defaultHandler) {
    super(parent, x, y);

    this._createChildren();
    this._createStyle();
    this._createListeners();

    this.setSize(PlayButton.width, PlayButton.height);
    this.addEventListener("click", defaultHandler);
    this.setPlaying(playing);
    this._addToParent();
  }

  //////////////////////////////////
  // Core
  //////////////////////////////////

  _createChildren() {
    this._wrapper.tabIndex = 0;
    this._setWrapperClass("MinimalPlayButton");
    this._createPlayIcon();
    this._createPauseIcon();
  }

  _createPlayIcon() {
    this._playIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    this._playIcon.setAttribute("width", 12);
    this._playIcon.setAttribute("height", 12);
    this._playIcon.setAttribute("class", "MinimalPlayButtonIcon");

    const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
    arrow.setAttribute("points", "2, 0, 12, 6, 2, 12");
    this._playIcon.appendChild(arrow);
    this._wrapper.appendChild(this._playIcon);
  }

  _createPauseIcon() {
    this._pauseIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    this._pauseIcon.setAttribute("width", 12);
    this._pauseIcon.setAttribute("height", 12);
    this._pauseIcon.setAttribute("class", "MinimalPlayButtonIcon");

    const bar1 = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
    bar1.setAttribute("points", "1, 0, 5, 0, 5, 12, 1, 12");
    this._pauseIcon.appendChild(bar1);

    const bar2 = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
    bar2.setAttribute("points", "7, 0, 11, 0, 11, 12, 7, 12");
    this._pauseIcon.appendChild(bar2);

    this._wrapper.appendChild(this._pauseIcon);
  }

  _createStyle() {
    const style = document.createElement("style");
    style.textContent = Style.playbutton;
    this.shadowRoot.append(style);
  }

  _createListeners() {
    this._onClick = this._onClick.bind(this);
    this._onKeyUp = this._onKeyUp.bind(this);
    this._wrapper.addEventListener("click", this._onClick);
    this._wrapper.addEventListener("keyup", this._onKeyUp);
  }

  //////////////////////////////////
  // Handlers
  //////////////////////////////////

  _onClick(event) {
    event.stopPropagation();
    if (this._enabled) {
      this._playing = !this._playing;
      this._updateButton();
      this.dispatchEvent(new CustomEvent("click", { detail: this._playing }));
    }
  }

  _onKeyUp(event) {
    if (event.keyCode === 13 && this._enabled) {
      this._wrapper.click();
    }
  }

  //////////////////////////////////
  // General
  //////////////////////////////////

  _updateButton() {
    if (this._playing) {
      this._playIcon.style.display = "none";
      this._pauseIcon.style.display = "block";
    } else {
      this._playIcon.style.display = "block";
      this._pauseIcon.style.display = "none";
    }
  }

  //////////////////////////////////
  // Public
  //////////////////////////////////

  /**
   * Adds a handler function for the "click" event on this button.
   * @param {function} handler - A function that will handle the "click" event.
   * @returns This instance, suitable for chaining.
   */
  addHandler(handler) {
    this.addEventListener("click", handler);
    return this;
  }

  /**
   * Automatically changes the value of a property on a target object with the main value of this component changes.
   * @param {object} target - The target object to change.
   * @param {string} prop - The string name of a property on the target object.
   * @return This instance, suitable for chaining.
   */
  bind(target, prop) {
    this.addEventListener("click", event => {
      target[prop] = event.detail;
    });
    return this;
  }

  /**
   * @returns whether the button is indicating a playing state.
   */
  getPlaying() {
    return this._playing;
  }

  setEnabled(enabled) {
    if (this._enabled === enabled) {
      return this;
    }
    super.setEnabled(enabled);
    if (this._enabled) {
      this._wrapper.setAttribute("class", "MinimalPlayButton");
      this._wrapper.tabIndex = 0;
    } else {
      this._wrapper.setAttribute("class", "MinimalPlayButtonDisabled");
      this._wrapper.tabIndex = -1;
    }
    return this;
  }

  /**
   * Sets whether or not the button shows a pause icon (playing == true) or a play icon (playing == false).
   * @param {boolean} playing - Whether or not the button relects it is in a playing state.
   * @returns This instance, suitable for chaining.
   */
  setPlaying(playing) {
    this._playing = playing;
    this._updateButton();
    return this;
  }

  //////////////////////////////////
  // Getters/Setters
  // alphabetical. getter first.
  //////////////////////////////////

  /**
   * Gets and sets whether or not the button shows a pause icon (playing == true) or a play icon (playing == false).
   */
  get playing() {
    return this.getPlaying();
  }
  set playing(playing) {
    this.setPlaying(playing);
  }
}

//////////////////////////////////
// DEFAULTS
//////////////////////////////////

/**
 * Default width of all PlayButtons.
 */
PlayButton.width = 40;
/**
 * Default height of all Playbuttons.
 */
PlayButton.height = 20;

customElements.define("minimal-playbutton", PlayButton);