Button component that I really would like to be perfect, but I'm not confident about it.
Should I keep working on it or start over?
import Emitter from '../../modules/emitter';
export default class Button {
emitter = new Emitter(this);
#iconElementLoading;
#iconElementLeft;
#iconElementRight;
#textElement;
#options = {};
#rendered = false;
#validator = {
element: value => value instanceof HTMLElement,
text: value => typeof value === 'string' && value.length > 0,
color: value => typeof value === 'string' && value.length > 2,
iconLeft: value => typeof value === 'string' && value.length > 1,
iconRight: value => typeof value === 'string' && value.length > 1,
uppercase: value => typeof value === 'boolean',
disabled: value => typeof value === 'boolean',
loading: value => typeof value === 'boolean',
onClick: value => typeof value === 'function'
};
#optionsHandler = {
set: (target, key, value) => {
if (this.#validator[key] === undefined || this.#validator[key](value) === false) {
console.warn(`Button ${key} value is not valid`);
return true;
}
this.#options[key] = value;
return true;
}
};
constructor(options) {
this.options = new Proxy({}, this.#optionsHandler);
for (const key in this.#validator) {
if (options[key] !== undefined && this.#validator[key](options[key]) === true) {
this.#options[key] = options[key];
}
}
this.render();
return this;
}
render() {
this.empty();
this.element = document.createElement('button');
this.element.classList.add('m-button');
if (this.#options.color) {
if (this.#validator.color(this.#options.color) === true) {
this.element.classList.add(`m-button--color-${this.#options.color}`);
} else {
console.warn('Button color value must be a string');
}
}
this.#textElement = document.createElement('span');
this.#textElement.classList.add('m-button__label');
this.element.appendChild(this.#textElement);
if (this.#validator.text(this.#options.text) === true) {
this.#textElement.appendChild(document.createTextNode(this.#options.text));
} else {
console.error('Button text value must be a string and not empty');
return;
}
if (this.#options.iconLeft) {
if (this.#validator.iconLeft(this.#options.iconLeft) === true) {
this.#iconElementLeft = this.#renderIconElement(this.#options.iconLeft);
this.element.insertBefore(this.#iconElementLeft, this.element.firstChild);
} else {
console.warn('Button iconLeft value must be a string');
}
}
if (this.#options.iconRight) {
if (this.#validator.iconRight(this.#options.iconRight) === true) {
this.#iconElementRight = this.#renderIconElement(this.#options.iconRight);
this.element.appendChild(this.#iconElementRight);
} else {
console.warn('Button iconRight value must be a string');
}
}
if (this.#options.uppercase) {
if (this.#validator.uppercase(this.#options.uppercase) === true) {
this.element.style.textTransform = 'uppercase';
} else {
console.warn('Button uppercase value must be a boolean');
}
}
if (this.#options.disabled) {
if (this.#validator.disabled(this.#options.disabled) === true) {
this.element.disabled = true;
} else {
console.warn('Button disabled value must be a boolean');
}
}
this.element.addEventListener('click', ev => {
this.emit('click', ev);
if (this.#options.onClick) {
if (this.#validator.onClick(this.#options.onClick) === true) {
this.#options.onClick.call(this, ev);
} else {
console.warn('Button onClick value must be a function');
}
}
});
if (this.#options.element) {
this.#options.element.appendChild(this.element);
}
this.#rendered = true;
return this.element;
}
empty() {
if (this.element) {
this.element.remove();
this.element = undefined;
this.#iconElementLoading = undefined;
this.#iconElementLeft = undefined;
this.#iconElementRight = undefined;
this.#textElement = undefined;
this.#rendered = false;
}
}
#renderIconElement(icon) {
const iconElement = document.createElement('i');
iconElement.classList.add('fa');
iconElement.classList.add(`fa-${icon}`);
return iconElement;
}
get color() {
return this.#options.color;
}
set color(color) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (color === undefined) {
this.#removeColor();
return;
}
if (this.#validator.color(color) === true) {
this.#removeColor();
this.element.classList.add(`m-button--color-${color}`);
this.#options.color = color;
} else {
console.warn('Button color value must be a string or undefined if you want to remove the color');
}
}
#removeColor() {
if (this.#options.color) {
this.element.classList.remove(`m-button--color-${this.#options.color}`);
this.#options.color = undefined;
}
}
get iconLeft() {
return this.#options.iconLeft;
}
set iconLeft(icon) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (icon === undefined) {
this.#removeIconLeft();
return;
}
if (this.#validator.iconLeft(icon) === true) {
this.#removeIconLeft();
if (!this.#iconElementLeft) {
this.#iconElementLeft = this.#renderIconElement(icon);
this.element.insertBefore(this.#iconElementLeft, this.#textElement);
}
this.#iconElementLeft.classList.add(`fa-${icon}`);
this.#options.iconLeft = icon;
} else {
console.warn('Button iconLeft value must be a string or undefined');
}
}
#removeIconLeft() {
if (this.#iconElementLeft) {
this.#iconElementLeft.remove();
this.#iconElementLeft = undefined;
}
if (this.#options.iconLeft) {
this.#iconElementLeft.classList.remove(`fa-${this.#options.iconLeft}`);
this.#options.iconLeft = undefined;
}
}
get iconRight() {
return this.#options.iconRight;
}
set iconRight(icon) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (icon === undefined) {
this.#removeIconRight();
return;
}
if (this.#validator.iconRight(icon) === true) {
this.#removeIconRight();
if (!this.#iconElementRight) {
this.#iconElementRight = this.#renderIconElement(icon);
this.element.appendChild(this.#iconElementRight);
}
this.#iconElementRight.classList.add(`fa-${icon}`);
this.#options.iconRight = icon;
} else {
console.warn('Button iconRight value must be a string or undefined');
}
}
#removeIconRight() {
if (this.#iconElementRight) {
this.#iconElementRight.remove();
this.#iconElementRight = undefined;
}
if (this.#options.iconRight) {
this.#iconElementRight.classList.remove(`fa-${this.#options.iconRight}`);
this.#options.iconRight = undefined;
}
}
get text() {
return this.#options.text;
}
set text(text) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (this.#validator.text(text) === false) {
console.warn('Button text value must be a string with a length of 1 or more or undefined if you want to remove the text');
return;
}
if (!text) {
this.#textElement.textContent = '';
this.#options.text = undefined;
} else if (typeof text === 'string') {
this.#textElement.textContent = text;
this.#options.text = text;
}
}
get loading() {
return this.#options.loading;
}
set loading(value) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (this.#validator.loading(value) === false) {
console.warn('Button loading value must be a boolean');
return;
}
if (value === false) {
if (this.#iconElementLoading) {
this.#iconElementLoading.classList.remove('m-button--loading');
this.#iconElementLoading.remove();
this.#iconElementLoading = undefined;
}
this.#options.loading = undefined;
this.disabled = false;
} else if (value === true) {
if (!this.#iconElementLoading) {
this.#iconElementLoading = this.#renderIconElement('spinner-third');
this.#iconElementLoading.classList.add('m-button--loading');
this.#iconElementLoading.classList.add('fa-duotone');
this.#iconElementLoading.classList.add('fa-spin');
this.element.prepend(this.#iconElementLoading);
}
this.#options.loading = true;
this.disabled = true;
}
}
get uppercase() {
return this.#options.uppercase;
}
set uppercase(value) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (this.#validator.uppercase(value) === false) {
console.warn('Button uppercase value must be a boolean');
return;
}
this.#options.uppercase = value;
if (value === true) {
this.element.style.textTransform = 'uppercase';
} else if (value === false) {
this.element.style.textTransform = 'initial';
}
}
get disabled() {
return this.#options.disabled;
}
set disabled(value) {
if (this.#rendered === false) {
console.error('Button element is not rendered please call render() first');
return;
}
if (this.#validator.disabled(value) === false) {
console.warn('Button disabled value must be a boolean');
return;
}
this.#options.disabled = value;
this.element.disabled = value;
}
get rendered() {
return this.#rendered;
}
}