import {Component_A} from './basic/as-component.js';
import {FP_Button_A} from './fp-ext/fp-button-ext.js';
import {ErrorCode} from './../exception/exceptionDesc.js';
/**
* @typedef TComponents.ButtonProps
* @prop {Function} [onClick] Function to be called when button is pressed
* @prop {string|null} [icon] - Path to image file
* @prop {string} [text] Button text
* @prop {string} [color] Button text color
* @prop {string} [backgroundColor] Button background color
* @prop {string} [border] Button border style
* @prop {number} [borderRadius] Button border radius
* @prop {Function} [onPointerRelease] Function to be called when pointer is released
* @prop {Function} [onPointerDown] Function to be called when pointer is pressed down
* @prop {string} [tips] Tooltip text for the button
* @prop {object} [font] Font properties for the button 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} [options] Additional options for the button
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the button is responsive
* @prop {Function} [onCreated] Function to be called when the button is created
* @prop {Function} [onMounted] Function to be called when the button is mounted
* @prop {object} [functionality] - Configuration object for component functionality.
* This object contains settings to control the component's behavior:
* - **type** (string): Defines the specific type of functionality to enable.
* - **params** (string): Additional parameters or arguments required by the functionality type.
* - **isHidden** (boolean, default: false): If true, the functionality is visually hidden from the user interface.
* @prop {string} [position] CSS position property, default is 'static'
* @prop {number} [width] Button width in pixels, default is 100
* @prop {number} [height] Button height in pixels, default is 32
* @prop {number} [top] Button top position in pixels, default is 0
* @prop {number} [left] Button left position in pixels, default is 0
* @prop {number} [rotation] Button rotation in degrees, default is 0
* @prop {number} [zIndex] Button z-index, default is 0
* @prop {string} [size] Size style template
* @prop {string} [styleTemplate] Style template
* @prop {string} [defaultState] Default state of the button, default is 'show_enable'
* @prop {string} [dataStruct] Data structure associated with the button
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-button';
/**
* @description Rounded button that triggers a callback when pressed. Additional callbacks can be added with the {@link TComponents.Button#onClick|onClick} method.
* This class focuses on the specific properties of the Button 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.Button
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component
* @param {TComponents.ButtonProps} props - Button properties
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*/
export class Button extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
/**
* @instance
* @private
* @type {TComponents.ButtonProps}
*/
this._props;
this._btn = new FP_Button_A();
this._mouseState = '';
}
/**
* @description Returns the default values of class properties (excluding parent properties).
* @member TComponents.Button#defaultProps
* @method
* @protected
* @returns {TComponents.ButtonProps}
*/
defaultProps() {
return {
options: {
responsive: false,
},
tips: '',
// life cycle
onCreated: '',
onMounted: '',
onDispose: '',
onClick: '',
onPointerRelease: '',
onPointerDown: '',
icon: null,
// ⭐ W/H/X/Y/B/R/Z: Component required attributes.
position: 'static',
width: 100,
height: 32,
top: 0,
left: 0,
borderRadius: 4,
rotation: 0,
zIndex: 0,
// Font
text: 'Button',
color: '#ffffff',
// font
font: {
fontSize: 12,
fontFamily: 'Segoe UI',
style: {
fontStyle: 'normal',
fontWeight: 'normal',
textDecoration: 'none',
},
},
// Background
backgroundColor: '#3366ff',
// Border
border: '0px solid #dbdbdb',
// Function
functionality: {type: '', params: '', isHidden: false},
// Style.
size: '',
styleTemplate: '',
defaultState: 'show_enable',
dataStruct: '',
};
}
/**
* @description Initialization function for the button component.
* @member TComponents.Button#onInit
* @method
* @async
* @returns {Promise<void>}
*/
async onInit() {}
/**
* @return {string}
*/
get tips() {
return this._props.tips;
}
/**
* @description Set the button disabled state and review the tips.
* @member {string} TComponents.Button#tips
* @instance
* @param {string} t
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* left: 100,
* top: 100,
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* button.tips = 'New tips for the button';
*
* // Disable button to display tips
* button.enabled = false;
*/
set tips(t) {
this.setProps({tips: t});
}
/**
* @description Click event handler.
* @member TComponents.Button~_cbOnClick
* @method
* @private
* @async
* @returns {Promise<void>}
*/
async _cbOnClick() {
return this._onClick();
}
/**
* @deprecated Use {@link _cbOnClick} instead.
* @description Click event handler.
* @member TComponents.Button~_onClick
* @method
* @private
* @async
* @returns {Promise<void>}
*/
async _onClick() {
if (this.enabled && this._props.onClick) {
try {
const fn = Component_A.genFuncTemplate(this._props.onClick, this);
this.enabled = false; // Disable the button to prevent multiple clicks
if (typeof fn == 'function') {
await fn();
}
} catch (e) {
Component_A.popupEventError(e, 'onClick');
} finally {
this.enabled = true; // Re-enable the button after the click handler is done
}
}
}
/**
* @description Pointer down event handler.
* @member TComponents.Button~_onPointerDown
* @method
* @private
* @returns {Promise<void>}
*/
async _onPointerDown() {
this._mouseState = 'down';
if (this.enabled && this._props.onPointerDown) {
try {
const fn = Component_A.genFuncTemplate(this._props.onPointerDown, this);
if (typeof fn == 'function') {
await fn();
}
} catch (e) {
Component_A.popupEventError(e, 'onPointerDown');
}
}
}
/**
* @description Pointer up/leave event handler.
* @member TComponents.Button~_onPointerRelease
* @method
* @private
* @returns {Promise<void>}
*/
async _onPointerRelease() {
if (this.enabled && this._props.onPointerRelease) {
try {
const fn = Component_A.genFuncTemplate(this._props.onPointerRelease, this);
if (typeof fn == 'function' && this._mouseState == 'down') {
await fn();
}
} catch (e) {
Component_A.popupEventError(e, 'onPointerRelease');
}
}
this._mouseState = 'up';
}
/**
* @description Renders the button component.
* @member TComponents.Button#onRender
* @method
* @throws {Error} If an error occurs during rendering.
* @returns {void}
*/
onRender() {
try {
this.removeAllEventListeners();
if (this._props.labelPos === 'left' || this._props.labelPos === 'right') {
this.container.classList.add('justify-stretch');
}
this._btn.text = this._props.text;
this._btn.icon = this._props.icon;
this._btn.borderRadius = this._props.borderRadius;
this._btn.color = this._props.color;
this._btn.backgroundColor = this._props.backgroundColor;
this._btn.border = this._props.border;
this._btn.font = this._props.font;
const btnContainer = this.find('.tc-button');
if (btnContainer) this._btn.attachToElement(btnContainer);
this.addEventListener(this._btn._root, 'click', this._cbOnClick.bind(this));
this.addEventListener(this._btn._root, 'pointerdown', this._onPointerDown.bind(this));
this.addEventListener(this._btn._root, 'pointerup', this._onPointerRelease.bind(this));
this.addEventListener(this._btn._root, 'pointerleave', this._onPointerRelease.bind(this));
this._addTips();
Component_A.resolveBindingExpression(this._props.text, this);
} catch (e) {
// Runtime errors: write specific content to the log, throw error code
Logger.e(
logModule,
ErrorCode.FailedToRunOnRender,
`Error happens on onRender of button component ${this.compId}.`,
e,
);
throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
}
}
/**
* @description Returns the markup for the button component.
* @member TComponents.Button#markup
* @method
* @returns {string} HTML markup string.
*/
markup() {
return /*html*/ `<div class="tc-button"></div>`;
}
/**
* @returns {boolean}
*/
get highlight() {
return this._btn.highlight;
}
/**
* @description Sets the highlight state of the button.
* @member {boolean} TComponents.Button~highlight
* @instance
* @private
* @param {boolean} value - The new highlight state.
*/
set highlight(value) {
this._btn.highlight = value;
}
/**
* @returns {string|null}
*/
get icon() {
return this.props.icon;
}
/**
* @description Sets the icon path.
* @member {string|null} TComponents.Button#icon
* @instance
* @param {string|null} s
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* button.icon = 'abb-icon abb-icon-abb_robot-tool_32';
*/
set icon(s) {
this.setProps({icon: s});
}
/**
* @returns {string}
*/
get color() {
return this.props.color;
}
/**
* @description Sets the text color of the button
* @member {string} TComponents.Button#color
* @instance
* @param {string} s For example, '#ffffff'
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* button.color = '#ffffff';
*/
set color(s) {
this.setProps({color: s});
}
/**
* @returns {string}
*/
get text() {
return this._props.text;
}
/**
* @description Sets the button text.
* @member {string} TComponents.Button#text
* @instance
* @param {string} value
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* button.text = 'Click Me';
*/
set text(value) {
this.commitProps({text: value});
this._btn.text = value;
}
/**
* @returns {Function|undefined}
*/
get onClick() {
const fn = Component_A.genFuncTemplate(this._props.onClick, this);
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the `onClick` event handler for the button.
* 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 button 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.Button#onClick
* @instance
* @param {Function} t - The new click handler function.
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* // Example 1.
* button.onClick = async function() {
* console.log('Button clicked!', this.text);
* };
* @example
* // Example 2.
* button.onClick = "console.log('Button clicked!', this.text);";
* @example
* // Example 3.
* // Note that the `this` context will not refer to the button object
* button.onClick = () => {
* console.log('Button clicked! ', button.text);
* };
*/
set onClick(t) {
this.setProps({onClick: t});
}
/**
* @returns {Function|undefined}
*/
get onPointerRelease() {
const fn = Component_A.genFuncTemplate(this._props.onPointerRelease, this);
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the pointer release event handler for the button.
* 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 button 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.onPointerRelease = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onPointerRelease = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.Button#onPointerRelease
* @instance
* @param {Function} t - The new pointer release handler function.
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* // Example 1.
* button.onPointerRelease = async function() {
* console.log('Pointer released on button!', this.text);
* }
* @example
* // Example 2.
* button.onPointerRelease = "console.log('Pointer released on button!', this.text);";
* @example
* // Example 3.
* // Note that the `this` context will not refer to the button object
* button.onPointerRelease = () => {
* console.log('Pointer released on button! ', button.text);
* };
*/
set onPointerRelease(t) {
this.setProps({onPointerRelease: t});
}
/**
* @returns {Function|undefined}
*/
get onPointerDown() {
const fn = Component_A.genFuncTemplate(this._props.onPointerDown, this);
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the pointer down event handler for the button.
* 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 button 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.onPointerDown = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onPointerDown = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.Button#onPointerDown
* @instance
* @param {Function} t - The new pointer down handler function.
* @example
* const button = new TComponents.Button(document.body, {
* position: 'absolute',
* zIndex: 1000,
* text: 'Click me'
* });
*
* // Render the button
* button.render();
*
* // Example 1.
* button.onPointerDown = async function() {
* console.log('Pointer down on button!', this.text);
* };
* @example
* // Example 2.
* button.onPointerDown = "console.log('Pointer down on button!', this.text);";
* @example
* // Example 3.
* // Note that the `this` context will not refer to the button object
* button.onPointerDown = () => {
* console.log('Pointer down on button! ', button.text);
* };
*/
set onPointerDown(t) {
this.setProps({onPointerDown: t});
}
}
/**
* @description Add css properties to the component
* @alias loadCssClassFromString
* @member TComponents.Button.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* TComponents.Button.loadCssClassFromString(`
* .tc-button {
* height: inherit;
* }`
* );
*/
Button.loadCssClassFromString(/*css*/ `
.tc-button {
height: inherit;
width: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
}
.tc-button .fp-components-button-disabled,
.tc-button .fp-components-button {
height: 100%;
width: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
overflow: hidden;
}
.tc-button > .fp-components-button,
.tc-button > .fp-components-button-disabled {
min-width: 0px;
min-height: 0px;
}
.tc-button > .fp-components-button-disabled {
cursor: not-allowed !important;
opacity:0.7;
}
@media (hover: hover) and (pointer: fine) {
.tc-button > .fp-components-button:hover,
.tc-button > .fp-components-button-disabled:hover {
opacity: 0.7;
}
}
@media (hover: none) and (pointer: coarse) {
.tc-button > .fp-components-button:active {
opacity: 0.7;
}
}
.tc-button > .fp-components-button {
min-width: 0px;
padding: 0px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-content: center;
flex-wrap: wrap;
}
.tc-button .fp-components-button-text {
text-overflow: ellipsis;
flex:none;
margin:0 4px;
}
.tc-button .fp-components-button-icon-font {
margin:0 4px;
}
.tc-button > .fp-components-button-disabled {
cursor: not-allowed !important;
}
.fp-components-button-disabled {
position: relative;
}
.fp-components-tmp-img {
height: 90%;
width: 90%;
padding: 5%;
img {
height: 100%;
width: 100%;
}
}
`);