import {Component_A} from './basic/as-component.js';
import {formatOptionsString, initDynamicOptions, generateOptionsString} from './utils/utils.js';
import {Checkbox_A} from './as-checkbox-a.js';
import {ErrorCode} from './../exception/exceptionDesc.js';
/**
* @typedef TComponents.CheckboxGroupProps
* @prop {object} [options] Additional options for the component
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the checkbox group 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 {Function} [onChange] Function to be called when checkbox state changes
* @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] Text color of the component
* @prop {object} [font] Font properties of the component
* 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 {string} [optionItems] Formatted string representing the checkbox options
* @prop {object} [optionConfig] Configuration for dynamic options
* This object controls how options are provided:
* - **mode** (string, default: 'fixed'): Options mode, e.g. 'fixed', 'sync', 'initialize'
* - **type** (string): Data source type
* - **isHidden** (boolean, default: false): Whether the option configuration is hidden
* - **variablePath** (string): Path of the variable used for dynamic options
* @prop {string} [defaultState] Default state of the component
* @prop {string} [dataStruct] Data structure associated with the component
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-checkboxgroup-a';
/**
* @description Checkbox group component that allows multiple selections. Each checkbox can be seperately checked or unchecked.
* This class focuses on the specific properties of the Checkbox group 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.CheckboxGroup_A
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component
* @param {TComponents.CheckboxGroupProps} props - Properties of the checkbox group component.
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
* await checkboxGroup.render();
*/
export class CheckboxGroup_A extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
this._props;
// this.initPropsDep(['optionItems']);
}
/**
* @description Returns the default values of class properties (excluding parent properties).
* @member TComponents.CheckboxGroup_A#defaultProps
* @method
* @protected
* @returns {TComponents.CheckboxGroupProps} The default properties of the checkbox group component.
*/
defaultProps() {
return {
options: {
responsive: false,
},
tips: '',
// lifecycle
onCreated: '',
onMounted: '',
onDispose: '',
onChange: '',
// X/Y/W/H/B/R
position: 'static',
width: 320,
height: 32,
top: 0,
left: 0,
rotation: 0,
zIndex: 0,
borderRadius: 0, // Invalid attribute
// font and color
color: '#000000',
font: {
fontSize: 12,
fontFamily: 'Segoe UI',
style: {
fontStyle: 'normal',
fontWeight: 'normal',
textDecoration: 'none',
},
},
// The properties.
optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`,
optionConfig: {
mode: 'fixed', //fixed, sync,initialize
type: '',
isHidden: false,
variablePath: '',
},
defaultState: 'show_enable',
dataStruct: '',
};
}
/**
* @description Initializes the component and sets up click event handling.
* @member TComponents.CheckboxGroup_A#onInit
* @method
* @async
* @throws {Error} If an error occurs during initialization.
* @returns {Promise<void>} A promise that resolves when the initialization is complete.
*/
async onInit() {}
/**
* @description there are something need to do after render once
* @member TComponents.CheckboxGroup_A#afterRenderOnce
* @method
* @protected
* @returns {void}
*/
afterRenderOnce() {
initDynamicOptions(this._props.optionConfig, (data) => {
this.setProps({
optionItems: generateOptionsString(data),
});
});
}
/**
* @description Maps the internal components.
* @member TComponents.CheckboxGroup_A~groupComponents
* @method
* @private
* @returns {object} The mapped components
*/
groupComponents() {
// Special case, the `this.child` within the lifecycle is only initialized once during construction and will not be dynamically added or removed afterward.
// Here, we will use external properties to control the dynamic addition and removal of `child`. Therefore, to ensure that it is assigned each time, `this.child` must be set to null once.
// This code is reflected in the `initChildrenComponents` function of `Component_A`.
const child = [];
for (let i = 0; i < this.optionItems.length; i++) {
const ctext = this.optionItems[i].text;
const cvalue = this.optionItems[i].value;
const checkbox = new Checkbox_A(null, {
text: ctext,
font: this._props.font,
color: this._props.color,
checked: ctext == this.text,
enabled: this.enabled,
onChange: this._onChange.bind(this, i),
selectedIndex: i,
value: cvalue,
});
child.push(checkbox);
}
return child;
}
/**
* @description Returns an object containing the components mapped to their identifiers.
* As one of the methods of component lifecycle,
* **we do not recommend that users call this function manually.**
* @member TComponents.CheckboxGroup_A#mapComponents
* @method
* @returns {object} An object mapping identifiers to components.
*/
mapComponents() {
return {};
}
/**
* @description Returns an object containing the components mapped to their identifiers.
* @member TComponents.CheckboxGroup_A~mapComponents
* @method
* @private
* @returns {object} An object mapping identifiers to components.
*/
_mapComponents() {
// Special case, the `this.child` within the lifecycle is only initialized once during construction and will not be dynamically added or removed afterward.
// Here, we will use external properties to control the dynamic addition and removal of `child`. Therefore, to ensure that it is assigned each time, `this.child` must be set to null once.
// This code is reflected in the `initChildrenComponents` function of `Component_A`.
const child = [];
this._optionItemList = formatOptionsString(this._props.optionItems);
for (let i = 0; i < this._optionItemList.length; i++) {
let ischecked = false;
const ctext = this._optionItemList[i].text;
const cvalue = this._optionItemList[i].value;
// Get specifical child item previous checked state.
if (Array.isArray(this.child)) {
const index = this.child.findIndex((c) => c._props.text == ctext);
if (index >= 0) ischecked = this.child[index]._props.checked;
}
const checkbox = new Checkbox_A(null, {
text: ctext,
font: this._props.font,
color: this._props.color,
checked: ischecked,
enabled: this.enabled,
onChange: this._props.onChange,
selectedIndex: i,
value: cvalue,
});
child.push(checkbox);
}
this.child = [];
return child;
}
/**
* @description Renders the component on the screen and applies the necessary styles and event listeners.
* @member TComponents.CheckboxGroup_A#onRender
* @method
* @async
* @throws {Error} If an error occurs during rendering.
* @returns {Promise<void>} A promise that resolves when the rendering is complete.
*/
async onRender() {
try {
this.removeAllEventListeners();
const checkBoxGroupContainer = this.find('.tc-checkbox-group-a-container');
this.child = this.groupComponents();
for (let i = 0; i < this.child.length; i++) {
this.child[i].attachToElement(checkBoxGroupContainer);
this.child[i].render();
}
this._addTips();
} 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 HTML markup for the component.
* @member TComponents.CheckboxGroup_A#markup
* @method
* @returns {string} The HTML markup for the component.
*/
markup() {
return /*html*/ `<div class="tc-checkbox-group-a">
<div class="tc-checkbox-group-a-container"></div>
</div>`;
}
/**
* @description Callback function which is called when one of the checkboxes is clicked.
* @member TComponents.CheckboxGroup_A~_onChange
* @method
* @private
* @async
* @returns {Promise<void>} A promise that resolves when the change event is processed.
*/
async _onChange(index) {
if (this.enabled) {
try {
var fn = Component_A.genFuncTemplate(this._props.onChange, this);
if (typeof fn == 'function') {
await fn(index);
}
} catch (e) {
Component_A.popupEventError(e, 'onChange', logModule);
}
}
}
/**
* @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 group 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 group 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.CheckboxGroup_A#onChange
* @instance
* @param {Function} t - A function to be executed or a function itself to handle the `onChange` event.
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
* await checkboxGroup.render();
*
* // example 1.
* checkboxGroup.onChange = async function() {
* console.log('Checkbox group changed.', this.color);
* };
* @example
* // example 2.
* checkboxGroup.onChange = "console.log('Checkbox group changed.', this.color);";
* @example
* // example 3.
* // Note that the `this` context will not refer to the checkbox group object
* checkboxGroup.onChange = () => {
* console.log('Checkbox group changed: ', checkboxGroup.color);
* };
*/
set onChange(t) {
this.setProps({
onChange: t,
});
}
/**
* @returns {object[]}
*/
get optionItems() {
const data = formatOptionsString(this._props.optionItems);
for (let i = 0; i < data.length; i++) {
data[i].text = Component_A.tParse(data[i].text.replace(/^\n+/, ''));
}
return data;
}
/**
* @description Sets the option items from a formatted string.
* @member {string} TComponents.CheckboxGroup_A#optionItems
* @instance
* @param {string} itemsString - The formatted string of option items.
* @throws {Error} If the optionsConfig is not in fixed mode.
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
* await checkboxGroup.render();
* // Set the option items using a formatted string
* checkboxGroup.optionItems = `text4|value4;\ntext5|value5;\ntext6|value6`;
*/
set optionItems(itemsString) {
const optionConfig = this._props.optionConfig;
if (optionConfig && optionConfig.mode == 'fixed') {
this.setProps({
optionItems: itemsString,
});
} else {
throw new Error(ErrorCode.FailedToUpdateComponentWithSetter, {
cause: 'The optionConfig is not in fixed mode, can not set optionItems',
});
}
}
/**
* @returns {number[]}
*/
get checkedIndexes() {
const checkedIndexes = [];
for (let i = 0; i < this.child.length; i++) {
if (this.child[i].checked) {
checkedIndexes.push(i);
}
}
return checkedIndexes;
}
/**
* @description Sets the selected indexes.
* @member {number[]} TComponents.CheckboxGroup_A#checkedIndexes
* @instance
* @param {number[]} indexes - The Array of indexes to be selected.
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
*await checkboxGroup.render();
* // Set the checked indexes
* checkboxGroup.checkedIndexes = [0, 1];
*/
set checkedIndexes(indexes) {
for (let i = 0; i < this.child.length; i++) {
this.child[i].checked = indexes.includes(i);
}
}
/**
* @deprecated Use {@link TComponents.CheckboxGroup_A#selectedIndexes} instead.
* @returns {number[]}
*/
get selectedIndexs() {
return this.checkedIndexes;
}
/**
* @returns {number[]}
*/
get selectedIndexes() {
return this.checkedIndexes;
}
/**
* @description Sets the selected indexes. (Alias for checkedIndexes)
* @member {number[]} TComponents.CheckboxGroup_A#selectedIndexes
* @instance
* @param {number[]} indexes - The Array of indexes to be selected.
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
* await checkboxGroup.render();
* // Set the selected indexes
* checkboxGroup.selectedIndexes = [0, 1];
*/
set selectedIndexes(indexes) {
this.checkedIndexes = indexes;
}
/**
* @deprecated Use {@link TComponents.CheckboxGroup_A#selectedIndexes} instead.
* @description Sets the selected indexes. (Alias for checkedIndexes)
* @member {number[]} TComponents.CheckboxGroup_A#selectedIndexs
* @instance
* @param {number[]} indexes - The Array of indexes to be selected.
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
* await checkboxGroup.render();
* // Set the selected indexes
* checkboxGroup.selectedIndexs = [0, 1];
*/
set selectedIndexs(indexes) {
this.checkedIndexes = indexes;
}
/**
* @returns {string}
*/
get color() {
return this.props.color;
}
/**
* @description Sets the checkboxgroup's text color
* @member {string} TComponents.CheckboxGroup_A#color
* @instance
* @param {string} a For example, '#ffffff'
* @example
* const checkboxGroup = new TComponents.CheckboxGroup_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* optionItems: `text1|value1;\ntext2|value2;\ntext3|value3`
* });
*
* await checkboxGroup.render();
*
* checkboxGroup.color = '#ff0000'; // Sets the text color to red
*/
set color(a) {
this.setProps({color: a});
}
}
/**
* @description Loads the CSS styles for the CheckboxGroup_A component.
* @alias loadCssClassFromString
* @member TComponents.CheckboxGroup_A.loadCssClassFromString
* @method
* @static
* @returns {void}
* @example
* TComponents.CheckboxGroup_A.loadCssClassFromString(`
* .tc-checkbox-group-a {
* height: inherit;
* }`
* );
*/
CheckboxGroup_A.loadCssClassFromString(/*css*/ `
.tc-checkbox-group-a {
height: inherit;
width: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
}
.tc-checkbox-group-a-container {
height: 100%;
width: 100%;
overflow: hidden;
display: flex;
flex-wrap: wrap;
align-content: space-around;
justify-content: space-between;
}
.tc-checkbox-group-a-container > * {
margin-right: 10px;
}
.tc-checkbox-group-a-container > *:last-child {
margin-right: 0px;
}
`);