as-checkbox-a.js

import {Component_A} from './basic/as-component.js';
import {FP_Checkbox_A} from './fp-ext/fp-checkbox-ext.js';
import {ErrorCode} from './../exception/exceptionDesc.js';

/**
 * @typedef TComponents.CheckboxProps
 * @prop {object} [options] Additional options for the checkbox component
 * Items in this object include:
 * - **responsive** (boolean, default: false): Whether the checkbox should be responsive
 * @prop {Function} [onCreated] Function to be called when the component is created
 * @prop {Function} [onMounted] Function to be called when the component is mounted
 * @prop {Function} [onChange] Function to be called when checkbox state changes
 * @prop {string} [position] CSS position property
 * @prop {number} [top] Top position in pixels
 * @prop {number} [left] Left position in pixels
 * @prop {number} [borderRadius] Border radius of the component
 * @prop {number} [rotation] Rotation of the component in degrees
 * @prop {number} [zIndex] Z-index of the component
 * @prop {string} [text] Checkbox text
 * @prop {string} [value] Checkbox value
 * @prop {number} [selectedIndex] <span style="color:red; font-weight: bold;"> Invalid attribute.</span>
 * @prop {string} [color] Color of the checkbox text
 * @prop {object} [font] Font settings for the checkbox text
 * This object controls text appearance:
 * - **fontSize** (number, default: 12): Font size in pixels.
 * - **fontFamily** (string, default: 'Segoe UI'): Font family name.
 * - **style** (object): Font style configuration, containing `fontStyle`, `fontWeight`, `textDecoration`.
 * @prop {object} [inputVar] Data binding configuration
 * This object configures the bound input variable:
 * - **type** (string): Input variable type. Default is `Component_A.INPUTVAR_TYPE.BOOL`
 * - **func** (string): Binding behavior. Default is `Component_A.INPUTVAR_FUNC.CUSTOM`
 * - **value** (*): Initial bound value, default is 0
 * - **isHidden** (boolean, default: false): Whether the variable is hidden
 * @prop {boolean} [enabled] Whether the checkbox is enabled
 * @prop {boolean} [checked] Checked state of the checkbox
 * @prop {string} [dataStruct] Data structure name for binding
 * @memberof TComponents
 */

/**
 * @ignore
 */
const logModule = 'as-checkbox-a';

/**
 * @description Checkbox component that allows a single selection. Each checkbox can be separately checked or unchecked.
 * This class focuses on the specific properties of the Checkbox component.
 * Since it inherits from Accessor_A, all basic properties (e.g., height, width) are available but documented in the Accessor_A part.
 * @class TComponents.Checkbox_A
 * @extends TComponents.Component_A
 * @memberof TComponents
 * @param {HTMLElement} parent - HTML element that is going to be the parent of the component
 * @param {TComponents.CheckboxProps} props - Properties of the checkbox component.
 * @example
 * const checkbox = new TComponents.Checkbox_A(document.body, {
 *   position: 'absolute',
 *   zIndex: 1000,
 *   text: 'Execute'
 * });
 *
 * checkbox.render();
 */
export class Checkbox_A extends Component_A {
  constructor(parent, props = {}) {
    super(parent, props);
    this._props;
    this._bindData;
    this._checkbox = new FP_Checkbox_A();
  }

  /**
   * @description Returns the default values of class properties (excluding parent properties).
   * @member TComponents.Checkbox_A#defaultProps
   * @method
   * @protected
   * @returns {TComponents.CheckboxProps} - The default properties of the checkbox component.
   */
  defaultProps() {
    return {
      options: {
        responsive: false,
      },
      // lifecycle
      onCreated: '',
      onMounted: '',
      onDispose: '',
      onChange: '',
      // X/Y/W/H/B/R
      position: 'static',
      top: 0,
      left: 0,
      borderRadius: 0,
      rotation: 0,
      zIndex: 0,
      // description text label
      text: '',
      // checkbox value.
      value: null,
      selectedIndex: 0, // Invalid attribute
      // font and color
      color: '#000000',
      font: {
        fontSize: 12,
        fontFamily: 'Segoe UI',
        style: {
          fontStyle: 'normal',
          fontWeight: 'normal',
          textDecoration: 'none',
        },
      },
      // Bind input variable.
      inputVar: {
        type: Component_A.INPUTVAR_TYPE.BOOL,
        func: Component_A.INPUTVAR_FUNC.CUSTOM,
        value: 0,
        isHidden: false,
      },
      // state properites
      enabled: true,
      checked: false,
      dataStruct: '',
    };
  }

  /**
   * @description Initializes the component and sets up click event handling.
   * @member TComponents.Checkbox_A#onInit
   * @method
   * @returns {void}
   */
  onInit() {
    /*
     * If bound to web data, `this._bindData` will have the format: { type: 'webdata', key: 'xxx' }.
     * If bound to digital signal data, `this._bindData` will have the format: { type: 'digitalsignal', key: 'xxxx' }.
     * If bound to rapid data, `this._bindData` will have the format: { type: 'rapiddata', dataType: 'xxx', module: 'xxx', name: 'xxx', task: 'xxx' }.
     */
    this._bindData = null;
  }

  /**
   * @description Renders the component on the screen and applies the necessary styles and event listeners.
   * @member TComponents.Checkbox_A#onRender
   * @method
   * @throws {Error} If an error occurs during rendering, it will throw an Error with a specific error code and message.
   * @returns {void}
   */
  onRender() {
    try {
      this.removeAllEventListeners();

      const checkBoxContainer = this.find('.tc-checkbox-a');
      if (checkBoxContainer) {
        this._checkbox.desc = this._props.text;
        this._checkbox.checked = this._props.checked;
        this._checkbox.enabled = this._props.enabled;
        this._checkbox.font = this._props.font;
        this._checkbox.color = this._props.color;
        if (this._props.inputVar && this._props.inputVar.func == Component_A.INPUTVAR_FUNC.SYNC) {
          this._bindData = Component_A.getBindData(this._props.inputVar.value, this);
        }
        this._checkbox.onclick = this._cbOnChange.bind(this);
        this._checkbox.attachToElement(checkBoxContainer);
      }
    } catch (e) {
      // Runtime errors: write specific content to the log, throw error code
      Logger.e(
        logModule,
        ErrorCode.FailedToRunOnRender,
        `Error happens on onRender of checkbox component ${this.compId}.`,
        e,
      );
      throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
    }
  }

  /**
   * @description Returns the HTML markup for the component.
   * @member TComponents.Checkbox_A#markup
   * @method
   * @returns {string} The HTML markup for the component.
   */
  markup() {
    return /*html*/ `<div class="tc-checkbox-a"></div>`;
  }

  /**
   * @description Adds a callback function that will be called when the checkbox state is changed.
   * @member TComponents.Checkbox_A~_cbOnChange
   * @method
   * @private
   * @async
   * @returns {Promise<void>} A promise that resolves when the checkbox state is successfully changed.
   */
  async _cbOnChange(value) {
    if (!this._props.enabled) return;
    try {
      await this.syncInputData(value);
    } catch (e) {
      // Restore to the previous state.
      this._checkbox.checked = !value;
      this.commitProps({checked: !value});
      Component_A.popupEventError(e, 'syncInputData', logModule);
      return;
    }
    this.commitProps({checked: value});

    try {
      var fn = Component_A.genFuncTemplate(this._props.onChange, this);
      fn && (await fn());
    } catch (e) {
      Component_A.popupEventError(e, 'onChange', logModule);
    }
  }

  /**
   * @returns {string}
   */
  get checked() {
    return this._props.checked;
  }

  /**
   * @description Sets the checked state for the checkbox.
   * @member {string} TComponents.Checkbox_A#checked
   * @instance
   * @param {string} t - The checked state.
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   *
   * checkbox.render();
   * // Set the checkbox to checked state
   * checkbox.checked = true;
   */
  set checked(t) {
    this.setProps({checked: t});
  }

  /**
   * @returns {boolean}
   */
  get enabled() {
    return this._props.enabled;
  }

  /**
   * @description Gets the enabled state for the component.
   * @member {boolean} TComponents.Checkbox_A#enabled
   * @instance
   * @param {boolean} t - The enabled state.
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   * checkbox.render();
   * // Set the checkbox to enabled state
   * checkbox.enabled = true;
   */
  set enabled(t) {
    this.setProps({enabled: t});
  }

  /**
   * @returns {Function|undefined}
   */
  get onChange() {
    try {
      var fn = Component_A.genFuncTemplateWithPopup(this._props.onChange, this);
    } catch (e) {
      return undefined;
    }
    if (typeof fn == 'function') return fn;
    else return undefined;
  }

  /**
   * @description Sets the `onChange` event handler for the checkbox component.
   * The handler can either be a string representing a function to be executed or a function itself.
   * 1. If you are using an arrow function, like `()=>{}`,
   * the `this` property of the scope may not refer to the checkbox object.
   * 2. If you are using string assignment to define code execution,
   * the string should contain `only the body of the code (executable statements)`,
   * not a complete function declaration. Therefore, including function keywords like function or async function is incorrect.
   * - Correct (Statements Only): `xx.onChange = "console.log('Action done.');"`
   * - Incorrect (Function Declaration): `xx.onChange = "function() { console.log('Action done.'); }"`
   * @member {Function} TComponents.Checkbox_A#onChange
   * @instance
   * @param {string|Function} t - A string representing a function to be executed or a function itself to handle the `onChange` event.
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   * checkbox.render();
   * // Example 1: Using a string as the handler:
   * checkbox.onChange = "console.log(this.checked);"
   * @example
   * // Example 2: Using a function as the handler:
   * checkbox.onChange = function () {
   *   console.log(this.checked);
   * };
   * @example
   * // Example 3: Using an arrow function as the handler:
   * // Note that the `this` context will not refer to the checkbox object
   * checkbox.onChange = () => {
   *   console.log(checkbox.checked);
   * };
   */
  set onChange(t) {
    this.setProps({onChange: t});
  }

  /**
   * @returns {string}
   */
  get text() {
    return this._props.text;
  }

  /**
   * @description Sets the description text label for the checkbox.
   * @member {string} TComponents.Checkbox_A#text
   * @instance
   * @param {string} t - The description label text content.
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   * checkbox.render();
   * // Set the description text label
   * checkbox.text = 'Execute Task';
   */
  set text(t) {
    this.setProps({text: t});
  }

  /**
   * @returns {string}
   */
  get value() {
    return this._props.text;
  }

  /**
   * @todo This is an experimental interface; it is unstable and its use is not recommended.
   * @description **it is unstable and its use is not recommended**,
   * sets the binding value of the checkbox.
   * @member {string} TComponents.Checkbox_A#value
   * @instance
   * @param {string} t - The binding value.
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   * checkbox.render();
   * // Set the binding value
   * checkbox.value = 'execute';
   */
  set value(t) {
    this.commitProps({text: t});
  }

  /**
   * @returns {string}
   */
  get color() {
    return this.props.color;
  }

  /**
   * @description Sets the color of the description label.
   * @member {string} TComponents.Checkbox_A#color
   * @instance
   * @param {string} s For example, '#ffffff'
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   * checkbox.render();
   * // Set the color of the description label
   * checkbox.color = '#ff0000';
   */
  set color(s) {
    this.setProps({color: s});
  }

  /**
   * @deprecated
   * @returns {number}
   */
  get selectedIndex() {
    return this._props.selectedIndex;
  }

  /**
   * @deprecated The interface has been deprecated.
   * @description Sets the check box binding index.
   * @member {number} TComponents.Checkbox_A#selectedIndex
   * @instance
   * @param {number} t - The new index binded by check box.
   * @example
   * const checkbox = new TComponents.Checkbox_A(document.body, {
   *   position: 'absolute',
   *   zIndex: 1000,
   *   text: 'Execute'
   * });
   * checkbox.render();
   *
   * checkbox.selectedIndex = 0;
   */
  set selectedIndex(t) {
    this.commitProps({selectedIndex: t});
  }
}

/**
 * Add css properties to the component
 * @alias loadCssClassFromString
 * @static
 * @param {string} css - The css string to be loaded into style tag
 */
Checkbox_A.loadCssClassFromString(/*css*/ `
.tc-checkbox-a {
  height: inherit;
  width: 100%;
  min-width: 0px;
  min-height: 0px;
  padding: 0px;
  margin: 0px;
  display:flex;
}

.tc-checkbox-a .fp-components-checkbox-root-disabled,
.tc-checkbox-a .fp-components-checkbox-root {
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.tc-checkbox-a .fp-components-checkbox-disabled {
  cursor: not-allowed !important;
  border-color:var(--fp-color-BLACK-OPACITY-30);
}

.tc-checkbox-a .fp-components-checkbox:hover {
  opacity:0.7;
}

.tc-checkbox-a .fp-components-checkbox-desc {
  font-size: 14px;
  color: inherit;
  white-space: nowrap;
  margin-left: 10px;
}
`);