import {Component_A} from './basic/as-component.js';
import {FP_Digital_A} from './fp-ext/fp-digital-ext.js';
import {checkCssSupport} from './utils/utils.js';
import {ErrorCode} from './../exception/exceptionDesc.js';
/**
* @typedef TComponents.SignalIndicatorProps
* @prop {object} [options] Additional options for the digital LED component.
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the component should be responsive.
* @prop {string} [tips] Tooltip text for the component.
* @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 {string} [position] CSS position property.
* @prop {number} [width] Width of the component.
* @prop {number} [height] Height of the component.
* @prop {number} [top] Top position of the component.
* @prop {number} [left] Left position of the component.
* @prop {number} [rotation] Rotation angle of the component.
* @prop {number} [zIndex] Z-index of the component.
* @prop {number} [borderRadius] <span style="color:red; font-weight: bold;"> Invalid attribute. Border radius of the component. </span>
* @prop {string} [color] The color of the digital LED component.
* @prop {string} [size] Size style template of the component.
* @prop {object} [font] Font settings for the digital LED component.
* This object controls text appearance:
* - **fontSize** (number, default: 20): Font size of the displayed text.
* - **fontFamily** (string, default: 'Segoe UI'): Font family of the displayed text.
* @prop {string|number|boolean} [text] The text value displayed by the digital LED component.
* @prop {object} [inputVar] Configuration object for input variable binding.
* 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** (string): Initial bound value, default is `'0'`.
* - **isHidden** (boolean, default: false): Whether the variable is hidden.
* @prop {string} [onChange] Please use onClick, the change event will be deprecated soon. A string representing a function to be called when the signal changes its state.
* @prop {string} [onClick] A string representing a function to be called when the component is clicked.
* @prop {boolean} [readOnly] Indicates whether the digital LED component is read-only.
* @prop {string} [defaultState] The default state of the component when initialized.
* @prop {string} [align] The alignment of the digital LED component within its container.
* @prop {string} [dataStruct] The data structure associated with the component.
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-digitalled';
/**
* @description Digital LED component that displays a digital signal and triggers callbacks when the signal changes or the component is clicked.
* This class focuses on the specific propertiesof the DigitalLed 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.DigitalLed
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - The parent HTML element of the component.
* @param {TComponents.SignalIndicatorProps} [props={}] - The properties of the Digital LED component.
* @property {TComponents.SignalIndicatorProps} _props - The properties object of the Digital LED component.
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*/
export class DigitalLed extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
/**
* @instance
* @private
* @type {TComponents.SignalIndicatorProps}
*/
this._props;
this._dig = new FP_Digital_A();
/*
* 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 Returns the default values of class properties (excluding parent properties).
* @member TComponents.DigitalLed#defaultProps
* @method
* @protected
* @returns {TComponents.SignalIndicatorProps} The default properties of the Digital LED component.
*/
defaultProps() {
return {
options: {
responsive: false,
},
tips: '',
// life cycle
onCreated: '',
onMounted: '',
onDispose: '',
// X/Y/W/H/B/R
position: 'static',
width: 40,
height: 40,
top: 0,
left: 0,
rotation: 0,
zIndex: 0,
borderRadius: 0, // Invalid attribute
// background
color: '#f0f0f0',
// size template
size: '',
// font
font: {
fontSize: 20,
fontFamily: 'Segoe UI',
},
// Input variable binding properties.
text: '0',
inputVar: {
type: Component_A.INPUTVAR_TYPE.BOOL,
func: Component_A.INPUTVAR_FUNC.CUSTOM,
value: '0', // string
isHidden: false,
},
onChange: '//Please use onClick, this event will be deprecated soon.',
// Special properties.
onClick: '',
readOnly: false,
defaultState: 'show_enable',
align: 'center',
dataStruct: '',
};
}
/**
* @description Initializes the component and sets up click event handling.
* @member TComponents.DigitalLed#onInit
* @method
* @async
* @returns {Promise<void>}
*/
async onInit() {
this._dig.onclick = this._cbOnClick.bind(this);
}
/**
* @description there are something need to do after render once
* @member TComponents.DigitalLed#afterRenderOnce
* @method
* @protected
* @returns {void}
*/
afterRenderOnce() {
if (this._props.inputVar.func == Component_A.INPUTVAR_FUNC.SYNC) {
this._bindData = Component_A.getBindData(this._props.inputVar.value, this);
}
}
/**
* @description Renders the component on the screen and applies the necessary styles and event listeners.
* @member TComponents.DigitalLed#onRender
* @method
* @throws {Error} Throws an error if rendering fails.
* @returns {void}
*/
onRender() {
try {
this.removeAllEventListeners();
const wrap = this.find('.tc-digital-container');
if (Component_A._isHTMLElement(wrap)) {
const align = this._props.align;
wrap.style.justifyContent = `${align == 'left' ? 'flex-start' : align == 'right' ? 'flex-end' : 'center'}`;
}
this._dig.attachToElement(wrap);
if (this._props.onChange) {
const fn = Component_A.genFuncTemplate(this._props.onChange, this);
if (fn) this._onChange(fn);
}
const digitalEl = this.find('.fp-components-digital-a');
if (Component_A._isHTMLElement(digitalEl)) {
digitalEl.style.color = `${this._props.color}`;
digitalEl.style.fontSize = `${this._props.font.fontSize}px`;
digitalEl.style.fontFamily = `${this._props.font.fontFamily}`;
}
this._dig.readOnly = this._props.readOnly ? true : false;
if (this.validateText(this._props.text)) {
this.textX = this._props.text; // Use the setter to ensure proper handling of text and active state
}
this._addTips();
this._compatibilityRender();
Component_A.resolveBindingExpression(this._props.text, this, 'textX');
} catch (e) {
// Runtime errors: write specific content to the log, throw error code
Logger.e(
logModule,
ErrorCode.FailedToRunOnRender,
`Error happens on onRender of digitalled component ${this.compId}.`,
e,
);
throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
}
}
/**
* @description Add a compatibility render for lower version browser, such as standard mode FlexPendant's built-in browser
* @member TComponents.DigitalLed~_compatibilityRender
* @method
* @private
* @returns {void}
*/
_compatibilityRender() {
if (!checkCssSupport('aspect-ratio', '1/1')) {
setTimeout(() => {
const {clientWidth, clientHeight} = this.container;
let min = Math.min(clientWidth, clientHeight);
const digitalContainer = this.find('.fp-components-digital-a-container');
if (min <= 0) {
min = this.height;
}
digitalContainer.style.cssText = `
width: ${min}px;
height: ${min}px;
padding:0;
`;
});
}
}
/**
* @description Returns the HTML markup for the component.
* @member TComponents.DigitalLed#markup
* @method
* @returns {string} The HTML markup for the component.
*/
markup() {
return /*html*/ `
<div class="tc-digital-container flex"></div>
`;
}
/**
* @returns {string}
*/
get text() {
return this._props.text;
}
/**
* @description Sets the text value of the Digital LED component and updates the active state accordingly.
* @member {string} TComponents.DigitalLed#text
* @instance
* @param {string|boolean|number} t - The text value to set.
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Update the text value
* digitalLed.text = '0';
*/
set text(t) {
this._updateActiveFromText(t);
}
/**
* @returns {string}
*/
get textX() {
return this._props.text;
}
/**
* @description Sets the text value of the Digital LED component and updates the active state accordingly.
* The update is performed silently without triggering change events
* or user-originated side effects.
* @member {string} TComponents.DigitalLed#textX
* @instance
* @param {string|boolean|number} t - The text value to set.
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Update the text value
* digitalLed.textX = '0';
*/
set textX(t) {
const next = this.convertDataToBool(t);
if (next == this.active) return;
this._dig.active = next;
this.commitProps({text: next ? '1' : '0'});
}
/**
* This attribute is used to set the text of the Digital LED component.
* When using this method to set the text, the change will be synchronized with the controller,
* But it will not trigger the onchange callback function or any user-originated side effects.
* @member {string} TComponents.DigitalLed#setText
* @method
* @param {string} text
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 1
* });
*
* // Render the component.
* digitalLed.render();
*
* digitalLed.setText(0);
*/
async setText(text) {
const next = this.convertDataToBool(text);
if (this._props.readOnly || next == this.active) return;
await this.syncInputData(next);
this._dig.active = next;
this._props.text = next ? '1' : '0';
}
/**
* @returns {boolean}
*/
get active() {
return this._dig.active;
}
/**
* @description Sets the active state of the Digital LED component.
* @member {boolean} TComponents.DigitalLed#active
* @instance
* @param {boolean} value - The active state to set.
* @protected
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Update the active state
* digitalLed.active = true;
*/
set active(value) {
if (value != this.active && this.enabled) {
this.trigger('change');
this._dig.active = value;
this.commitProps({text: value ? '1' : '0'});
}
}
/**
* @returns {boolean}
*/
get activeX() {
return this._dig.active;
}
/**
* @description Sets the active state of the Digital LED component.
* This method updates the active value internally without triggering
* change events or user-originated side effects.
* @member {boolean} TComponents.DigitalLed#activeX
* @instance
* @param {boolean} value - The active state to set.
* @protected
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Update the active state
* digitalLed.activeX = true;
*/
set activeX(value) {
if (value == this.active) return;
this._dig.active = value;
this.commitProps({text: value ? '1' : '0'});
}
/**
* @description Adds a callback function to be called when the signal changes its state.
* @member TComponents.DigitalLed~_onChange
* @method
* @private
* @param {Function} func - The callback function to be called when the signal changes.
* @returns {void}
*/
_onChange(func) {
this.cleanEvent('change');
this.on('change', func);
}
/**
* @description Callback function that is called when the indicator is pressed, and triggers any function registered with {@link TComponents.Digital_A#onClick}.
* @member TComponents.DigitalLed~_cbOnClick
* @method
* @private
* @async
* @returns {Promise<void>}
*/
async _cbOnClick() {
const targetValue = this.active ? false : true;
if (this._props.readOnly || targetValue == this.active || !this.enabled) return;
try {
await this.syncInputData(targetValue);
this._dig.active = targetValue;
this.commitProps({text: targetValue ? '1' : '0'});
} catch (e) {
this._dig.active = !targetValue;
Component_A.popupEventError(e, 'syncInputData', logModule);
return;
}
try {
const fn = Component_A.genFuncTemplate(this._props.onClick, this);
fn && (await fn());
} catch (e) {
Component_A.popupEventError(e, 'onClick', logModule);
}
// Updates to bound variables are done through subscriptions
// if (this.inputVar.func == Component_A.INPUTVAR_FUNC.CUSTOM) {
this.trigger('change');
// }
}
/**
* @returns {object}
*/
get inputVar() {
return this._props.inputVar;
}
/**
* @description Sets `inputVar` property.
* This property is used to set the `inputVar` configuration.
* If the configuration is invalid or doesn't meet the expected conditions,
* an exception will be triggered in the input component.
* **Note:** Invalid `inputVar` configurations may cause the input component to throw an error.
* @member {object} TComponents.DigitalLed#inputVar
* @instance
* @param {object} t - The new `inputVar` configuration object. It should contain a valid `type`, `func`, and `value` as per the component's requirements.
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Update the inputVar configuration
* digitalLed.inputVar = {
* type: TComponents.Component_A.INPUTVAR_TYPE.BOOL,
* func: TComponents.Component_A.INPUTVAR_FUNC.CUSTOM,
* value: true
* };
*/
set inputVar(t) {
this.setProps({inputVar: t});
}
/**
* @returns {Function|undefined}
*/
get onClick() {
try {
var fn = Component_A.genFuncTemplateWithPopup(this._props.onClick, this);
} catch (e) {
return undefined;
}
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the click event handler.
* 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 digitalLed 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.onClick = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onClick = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.DigitalLed#onClick
* @instance
* @param {Function|string} t - The new click handler function.
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Example 1: Using a string as the handler:
* digitalLed.onClick = "console.log('hello');"
*
* // Example 2: Using a arrow function as the handler:
* digitalLed.onClick = () => { console.log(digitalLed.text); }
*
* // Example 3: Using function with async operation:
* digitalLed.onClick = async function () {
* console.log('hello');
* };
*/
set onClick(t) {
this.setProps({onClick: t});
}
/**
* @returns {Function|undefined}
* @deprecated The `onChange` event will be deprecated soon, please use `onClick` instead. The `onChange` event may not work properly in future versions.
*/
get onChange() {
try {
var fn = Component_A.genFuncTemplate(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 Digital LED 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 digital led 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.DigitalLed#onChange
* @param {Function|string} t
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Example 1: Using a string as the handler:
* digitalLed.onChange = "console.log('hello');"
* @example
* // Example 2: Using a arrow function as the handler:
* // Note that the `this` context will not refer to the digitalLed object
* digitalLed.onChange = () => { console.log(digitalLed.text); }
* @example
* // Example 3: Using function with async operation:
* digitalLed.onChange = async function () {
* console.log('hello', this.text);
* }
*/
set onChange(t) {
this.setProps({onChange: t});
}
/**
* @returns {boolean}
*/
get readOnly() {
return this._props.readOnly;
}
/**
* @description Sets the read-only state of the Digital LED component
* @member {boolean} TComponents.DigitalLed#readOnly
* @instance
* @param {boolean} t - The read-only state.
* @example
* const digitalLed = new TComponents.DigitalLed(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: '1',
* });
*
* // Render the component
* digitalLed.render();
*
* // Update the readOnly state
* digitalLed.readOnly = true;
*/
set readOnly(t) {
this.setProps({readOnly: t});
}
}
if (checkCssSupport('aspect-ratio', '1/1')) {
//special style for aspect-ratio supported browsers
DigitalLed.loadCssClassFromString(`
.tc-digital-container .fp-components-digital-a-container,
.tc-digital-container .fp-components-digital-a-disabled {
height: 100%;
aspect-ratio: 1 / 1;
max-width: 100%;
max-height: 100%;
display: block;
position: relative;
overflow: hidden;
padding:0;
}
.tc-digital-container .fp-components-digital-a {
width: 100%;
max-height: 100%;
aspect-ratio: 1 / 1;
align-items: center;
justify-content: center;
border-radius: 50%;
display:flex;
box-sizing: border-box;
border-width:3px;
height:auto;
}
`);
} else {
//special style for aspect-ratio not supported browsers
DigitalLed.loadCssClassFromString(`
.tc-digital-container .fp-components-digital-a {
width:100%;
height:100%;
border-radius:50%;
display:flex;
align-items:center;
justify-content:center;
margin:0;
padding:0;
box-sizing: border-box;
}
`);
}
/**
* @description Add css properties to the component
* @member TComponents.DigitalLed.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* TComponents.DigitalLed.loadCssClassFromString(`
* .tc-digital-container {
* height: 100%;
* min-width: 0px;
* }
* `);
*/
DigitalLed.loadCssClassFromString(`
.tc-digital-container {
height: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
align-items: flex-start;
justify-content: flex-start;
}
.tc-digital-container .fp-components-digital-a-disabled {
cursor: not-allowed !important;
}
.tc-digital-container .fp-components-digital-a:hover {
opacity:0.7;
}
`);