250 lines
7.9 KiB
TypeScript
250 lines
7.9 KiB
TypeScript
import { GefestEngine } from './engine';
|
|
import { GefestStyle } from './style';
|
|
|
|
// The general class of Gefest
|
|
type GefestElementEvents = "onClick";
|
|
export abstract class GefestElement {
|
|
style: GefestStyle | null = null;
|
|
isHidden: boolean = false;
|
|
//onClick: (() => void) | null = null;
|
|
readonly gefestId: string;
|
|
protected content: (GefestElement | string)[] | string;
|
|
protected attributes: Record<string, string> = {};
|
|
protected classList: Set<string> = new Set();
|
|
protected eventHandlers: ((eventType: GefestElementEvents, eventData: unknown) => void)[] = [];
|
|
|
|
protected clickHandler: (() => void) | null = null;
|
|
|
|
emit(event: GefestElementEvents, data: unknown = undefined) {
|
|
for (let handler of this.eventHandlers)
|
|
new Promise((rs) => rs(handler(event, data)));
|
|
}
|
|
|
|
on(event: GefestElementEvents, handler: (data : unknown) => void) {
|
|
this.eventHandlers.push((calledEvent: GefestElementEvents, calledData: unknown) => {
|
|
if (calledEvent === event)
|
|
return handler(calledData);
|
|
});
|
|
}
|
|
|
|
once(event: GefestElementEvents, handler: (data : unknown) => void) {
|
|
const addedHandler = (calledEvent: GefestElementEvents, calledData: unknown) => {
|
|
if (calledEvent === event) {
|
|
const index = this.eventHandlers.indexOf(addedHandler);
|
|
this.eventHandlers.splice(index, 1);
|
|
return handler(calledData);
|
|
}
|
|
};
|
|
|
|
this.eventHandlers.push(addedHandler);
|
|
}
|
|
|
|
set onClick(handler: (() => void) | null) {
|
|
if (handler) {
|
|
this.clickHandler = handler;
|
|
} else {
|
|
this.clickHandler = null;
|
|
}
|
|
}
|
|
|
|
get onClick(): (() => void) | null {
|
|
if (this.clickHandler === null)
|
|
return null;
|
|
return () => {
|
|
this.clickHandler!();
|
|
this.emit("onClick");
|
|
|
|
GefestEngine.activateOnClick();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance of the element from an HTML string.
|
|
* @param html The HTML string to parse.
|
|
* @returns The created GefestElement.
|
|
*/
|
|
static fromHTML(html: string): GefestElement {
|
|
const htmlElement = new DOMParser().parseFromString(html, 'text/html').body.firstChild as HTMLElement;
|
|
const element = new (class extends GefestElement {
|
|
protected wrapHTML(content: string): string {
|
|
return content;
|
|
}
|
|
})(htmlElement.innerHTML);
|
|
|
|
for (const attr of htmlElement.attributes) {
|
|
try {
|
|
element.setAttribute(attr.name, attr.value);
|
|
} catch (_) {}
|
|
}
|
|
|
|
for (const className of htmlElement.classList) {
|
|
element.addClass(className);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance of the element.
|
|
* @param content The content for the element. Can be a string, an array of strings, an array of GefestElements or a combination of both.
|
|
*/
|
|
constructor (content : (GefestElement | string)[] | string = []) {
|
|
this.gefestId = GefestEngine.register(this);
|
|
this.attributes["data-gefest-id"] = this.gefestId;
|
|
this.content = content;
|
|
}
|
|
|
|
/**
|
|
* Call to GefestEngine for rerender element
|
|
*/
|
|
update (): void {
|
|
GefestEngine.reRenderByGI(this.gefestId);
|
|
}
|
|
|
|
/**
|
|
* Builds the element and returns it as a string. If the element has a style, it applies the style to the rendered HTML.
|
|
* @param isFromStyle Indicates if the build is being called from a style application. This prevents infinite recursion when applying styles.
|
|
* @returns The built element as a string.
|
|
*/
|
|
build (isFromStyle: boolean = false): string {
|
|
if (this.isHidden)
|
|
this.setAttribute('hidden', '');
|
|
else
|
|
this.removeAttribute('hidden');
|
|
|
|
if (!isFromStyle)
|
|
return this.applyStyle(this.render(), this);
|
|
return this.render();
|
|
};
|
|
|
|
/**
|
|
* Checks if an attribute key is reserved.
|
|
* @param key The attribute key.
|
|
*/
|
|
checkReservedAttribute(key: string): void {
|
|
if (({ "style": true, "class": true, "data-gefest-id": true })[key])
|
|
throw new Error(`The attribute "${
|
|
key
|
|
}" is reserved. Use the setPersonalStyle method for styles and setClass method for classes.`);
|
|
}
|
|
|
|
/**
|
|
* Sets a personal style for the element. This will be applied on top of any style that is set for the element.
|
|
* @param style The personal style to set. If null, the personal style will be removed.
|
|
*/
|
|
setPersonalStyle(style: string | null): void {
|
|
if (style === null) {
|
|
delete this.attributes.style;
|
|
return;
|
|
}
|
|
this.attributes.style = style;
|
|
}
|
|
|
|
/**
|
|
* Adds a class to the element's class list and updates the "class" attribute accordingly.
|
|
* @param className The class name to add.
|
|
*/
|
|
addClass(className: string): void {
|
|
this.classList.add(className);
|
|
this.attributes.class = Array.from(this.classList).join(' ');
|
|
}
|
|
|
|
/**
|
|
* Removes a class from the element's class list and updates the "class" attribute accordingly.
|
|
* @param className The class name to remove.
|
|
*/
|
|
removeClass(className: string): void {
|
|
this.classList.delete(className);
|
|
this.attributes.class = Array.from(this.classList).join(' ');
|
|
}
|
|
|
|
/**
|
|
* Gets the list of classes for the element.
|
|
* @returns The array of class names.
|
|
*/
|
|
getClassList(): string[] {
|
|
return Array.from(this.classList);
|
|
}
|
|
|
|
/**
|
|
* Sets an attribute for the element.
|
|
* @param key The attribute key.
|
|
* @param value The attribute value.
|
|
*/
|
|
setAttribute (key: string, value: string): void {
|
|
this.checkReservedAttribute(key);
|
|
this.attributes[key] = value;
|
|
}
|
|
|
|
/**
|
|
* Gets an attribute value by its key.
|
|
* @param key The attribute key.
|
|
* @returns The attribute value or undefined if not found.
|
|
*/
|
|
getAttribute (key: string): string | undefined {
|
|
return this.attributes[key];
|
|
}
|
|
|
|
/**
|
|
* Removes an attribute by its key.
|
|
* @param key The attribute key.
|
|
*/
|
|
removeAttribute (key: string): void {
|
|
this.checkReservedAttribute(key);
|
|
delete this.attributes[key];
|
|
}
|
|
|
|
/**
|
|
* Applies the element's style to the given HTML.
|
|
* @param html The HTML to apply the style to.
|
|
* @returns The styled HTML.
|
|
*/
|
|
protected applyStyle(html: string, element?: GefestElement): string {
|
|
if (this.style)
|
|
return this.style.applyStyle(html, element);
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Converts the element's attributes to a string.
|
|
* @returns The attributes string.
|
|
*/
|
|
protected attributesToString (): string {
|
|
return Object.entries(this.attributes)
|
|
.map(([key, value]) => `${key}="${value}"`)
|
|
.join(' ');
|
|
}
|
|
|
|
/**
|
|
* Wraps the given content in HTML tags.
|
|
* @param content The content to wrap.
|
|
* @returns The wrapped HTML.
|
|
*/
|
|
protected wrapHTML (content: string): string {
|
|
const attrString = this.attributesToString();
|
|
return `<div ${attrString}>${content}</div>`;
|
|
}
|
|
|
|
/**
|
|
* Renders the element and returns it as a string.
|
|
* @returns The rendered element as a string.
|
|
*/
|
|
protected render (): string {
|
|
if (typeof this.content === 'string') {
|
|
return this.wrapHTML(this.content);
|
|
}
|
|
const rendered : string[] = [];
|
|
for (const item of this.content) {
|
|
if (typeof item === 'string') {
|
|
rendered.push(item);
|
|
}
|
|
else if (item instanceof GefestElement) {
|
|
if (!item.style && this.style) {
|
|
item.style = this.style;
|
|
}
|
|
rendered.push(item.build());
|
|
}
|
|
}
|
|
return this.wrapHTML(rendered.join(''));
|
|
}
|
|
} |