UNPKG

cedro

Version:

Javascript library to build user interfece based on widgets.

1,007 lines (858 loc) 27.7 kB
import { UID } from "../core/uid"; import { IWidget, WUIEvent, WUICallback } from "../interfaces/widget.interface"; import { Vector2D } from "../types/vector2d.type"; import { WidgetEventProps, WidgetProps, WidgetAlignTypes, WidgetTypes } from "./widget.types"; import { addNewWidget, connectWidget } from "./widget.collection"; export class Widget implements IWidget { readonly id: string; subscribers: Map<string, WUICallback>; padding: number; left: number; top: number; width: number; height: number; initialWidth: number; initialHeight: number; overflow: boolean; fixedSize: number | null; type: WidgetTypes; align: WidgetAlignTypes; visible: boolean; enabled: boolean; parent: IWidget | null; childs: IWidget[]; bodyTagName: string; body: HTMLElement; constructor( id: string | null = "", bodyTagName: string = "div", parent: IWidget | null = null ) { this.id = id ? id : UID(); this.overflow = false; this.subscribers = new Map<string, WUICallback>(); this.padding = 0; this.left = 0; this.top = 0; this.width = 0; this.height = 0; this.initialWidth = 0; this.initialHeight = 0; this.fixedSize = null; this.type = WidgetTypes.FREE; this.align = WidgetAlignTypes.VERTICAL; this.visible = true; this.enabled = true; this.parent = parent; this.childs = []; this.bodyTagName = bodyTagName; this.body = document.createElement(this.bodyTagName); this.body.id = `WUI.${id}.body`; if (parent) { //this.setType(WidgetTypes.CUSTOM); parent.addChild(this); parent.getBody().appendChild(this.body); } else { this.setType(WidgetTypes.FREE); document.body.appendChild(this.body); } this.body.addEventListener("click", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "click") { callback.then(e, this); } }); }); this.body.addEventListener("mousedown", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "mousedown") { callback.then(e, this); } }); }); this.body.addEventListener("mouseup", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "mouseup") { callback.then(e, this); } }); }); this.body.addEventListener("mousemove", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "mousemove") { callback.then(e, this); } }); }); this.body.addEventListener("wheel", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "wheel") { callback.then(e, this); } }); }); this.body.addEventListener("mouseout", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "mouseout") { callback.then(e, this); } }); }); this.body.addEventListener("mouseleave", (e) => { this.subscribers.forEach((callback) => { if (callback.event == "mouseleave") { callback.then(e, this); } }); }); this.init(); this.getMaxZIndex(); addNewWidget(this.id, this); } public run(eventId: WUIEvent): void { this.subscribers.forEach((callback) => { if (callback.event == eventId) { callback.then(new Event(eventId), this); } }); } public subscribe(cb: WUICallback) { const randomId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); this.subscribers.set(`${randomId}.${cb.event}`, cb); } public unsubscribe(event: WUIEvent) { this.subscribers.delete(`${event}`); } /** * Sets the padding value for the object. * * @param {number} p - The padding value to set. * @return {void} This function does not return a value. */ public setPadding(p: number): void { this.padding = p; } /** * Sets the value of x and updates the left position of the element. * * @param {number} x - The new value of x. * @return {void} This function does not return anything. */ public setX(x: number): void { this.left = x; this.body.style.left = `${x}px`; } /** * Sets the value of the 'y' property and updates the 'top' and 'body.style.top' properties accordingly. * * @param {number} y - The new value for the 'y' property. * @return {void} This function does not return a value. */ public setY(y: number): void { this.top = y; this.body.style.top = `${y}px`; } /** * Sets the width of the element and updates the corresponding inline style. * * @param {number} w - The new width value. * @return {void} This function does not return a value. */ public setW(w: number): void { //this.width = w; this.body.style.width = `${w}px`; } /** * Sets the initial value of the width property. * * @param {number} w - The initial width value. * @return {void} This function does not return a value. */ public setInitialW(w: number): void { this.initialWidth = w; } /** * Sets the value of the height property and updates the height of the element. * * @param {number} h - The new height value. * @return {void} This function does not return any value. */ public setH(h: number): void { //this.height = h; this.body.style.height = `${h}px`; } /** * Sets the initial height of the object. * * @param {number} h - The initial height value to set. * @return {void} This function does not return a value. */ public setInitialH(h: number): void { this.initialHeight = h; } /** * Sets the width and height of the element. * * @param {number} w - The width to set. * @param {number} h - The height to set. * @return {void} This function does not return anything. */ public setWH(w: number, h: number): void { this.width = w; this.height = h; this.body.style.width = `${w}px`; this.body.style.height = `${h}px`; //ejecutar los subscriptores this.subscribers.forEach((callback) => { if (callback.event == "resize") { callback.then(new Event("resize"), this); } }); } public setFixedSize(s: number | null): void { this.fixedSize = s; } /** * Sets the type of the widget. * * @param {WidgetTypes} type - The type of widget to set. * @return {void} This function does not return anything. */ public setType(type: WidgetTypes): void { this.type = type; let freeStyle = false; if (this.type === WidgetTypes.FREE) { freeStyle = true; } const parent = this.getParent(); if (parent) { if (parent.type === WidgetTypes.FREE) { freeStyle = true; } } if (freeStyle) { this.deleteClass("WUINonFreePosition"); this.addClass("WUIFixPosition"); this.body.style.left = ""; this.body.style.top = ""; this.body.style.width = ""; this.body.style.height = ""; this.body.style.bottom = ""; this.body.style.right = ""; } else { if (this.type === WidgetTypes.CUSTOM || this.type === WidgetTypes.FILL) { // this.body.style.position = "absolute"; // this.body.style.overflow = "hidden"; this.deleteClass("WUIFixPosition"); this.addClass("WUINonFreePosition"); } } if (this.initialWidth != 0) { this.body.style.width = `${this.initialWidth}px`; } if (this.initialHeight != 0) { this.body.style.height = `${this.initialHeight}px`; } } /** * Sets the alignment of the widget. * * @param {WidgetAlignTypes} align - The alignment to set. * @return {void} */ public setAlign(align: WidgetAlignTypes): void { this.align = align; } /** * Sets the visibility of the element. * * @param {boolean} visible - The visibility state of the element. * @return {void} */ public setVisible(visible: boolean): void { this.visible = visible; this.body.style.display = visible ? "" : "none"; } /** * Sets the enabled status of the object. * * @param {boolean} enabled - The new enabled status. * @return {void} This function does not return a value. */ public setEnabled(enabled: boolean): void { this.enabled = enabled; } /** * Sets the parent of the widget. * * @param {IWidget | null} parent - The parent widget or null if there is no parent. * @return {void} This function does not return anything. */ public setParent(parent: IWidget | null): void { if (this.parent) { if (this.body) { if (this.body.parentNode) { this.body.parentNode.removeChild(this.body); } } } this.parent = parent; this.parent?.body.appendChild(this.body); this.setType(this.type); //TODO: revisar, se coloca para tratar de que reconozca al padre como freestyle en caso de serlo. //this.parent?.addChild(this); } /** * Sets the body of the element. * * @param {HTMLElement} body - The new body element. * @return {void} This function does not return anything. */ public setBody(body: HTMLElement): void { if (this.body.parentNode) { this.body.parentNode.removeChild(this.body); } this.body = body; this.parent?.getBody().appendChild(this.body); } /** * Sets the array of child widgets for this widget. * * @param {IWidget[]} childs - The array of child widgets to set. * @return {void} */ public setChilds(childs: IWidget[]): void { this.childs = childs; } /** * Sets the overflow value. * * @param {boolean} overflow - The new value for the overflow. * @return {void} This function does not return a value. */ setOverflow(overflow: boolean): void { this.overflow = overflow; this.getBody().style.overflow = this.overflow ? "auto" : "hidden"; } /** * Adds a CSS class to the list of CSS classes. * * @param {string} cssClass - The CSS class to add. * @return {void} */ public addClass(cssClass: string): void { this.body.classList.add(cssClass); } /** * Deletes a CSS class from the list of CSS classes. * * @param {string} cssClass - The CSS class to be deleted. * @return {void} This function does not return a value. */ public deleteClass(cssClass: string): void { this.body.classList.remove(cssClass); } public deleteAllClasses(): void { this.body.classList.forEach((cssClass) => { this.deleteClass(cssClass); }); } /** * Retrieves the padding value. * * @return {number} The padding value. */ getPadding(): number { return this.padding; } /** * Retrieves the value of the 'left' property. * * @return {number} The value of the 'left' property. */ public getX(recursive: boolean = false): number { if (!recursive) { return this.left; } const parent = this.getParent(); if (parent) { return this.left + parent.getX(true); } return this.left; } /** * Retrieves the value of Y. * * @return {number} The value of Y. */ public getY(recursive: boolean = false): number { if (!recursive) { return this.top; } const parent = this.getParent(); if (parent) { return this.top + parent.getY(true); } return this.top; } /** * Get the width of the object. * * @return {number} The width of the object. */ public getW(): number { //return this.width; return this.getBody().clientWidth; } /** * Returns the value of the height property. * * @return {number} The value of the height property. */ public getH(): number { //return this.height; return this.getBody().clientHeight; } /** * Returns the position of the element relative to the viewport. * * @param {boolean} scroll - Indicates whether to account for scrolling in the position calculation. Defaults to false. * @return {Vector2D} An object with x and y properties representing the position of the element. */ public getPosition(scroll: boolean = false): Vector2D { var box = this.getBody().getBoundingClientRect(); var body = document.body; var docElem = document.documentElement; var scrollTop = scroll ? docElem.scrollTop || body.scrollTop : 0; var scrollLeft = scroll ? docElem.scrollLeft || body.scrollLeft : 0; var clientTop = docElem.clientTop || body.clientTop || 0; var clientLeft = docElem.clientLeft || body.clientLeft || 0; var top = box.top + scrollTop - clientTop; var left = box.left + scrollLeft - clientLeft; return { x: Math.round(left), y: Math.round(top) }; } public getFixedSize(): number | null { return this.fixedSize; } /** * Returns the type of the widget. * * @return {WidgetTypes} The type of the widget. */ public getType(): WidgetTypes { return this.type; } /** * Retrieves the alignment of the widget. * * @return {WidgetAlignTypes} The alignment of the widget. */ public getAlign(): WidgetAlignTypes { return this.align; } /** * Retrieves the value of the enabled flag. * * @return {boolean} The value of the enabled flag. */ public getEnabled(): boolean { return this.enabled; } /** * Returns the visibility status of the element. * * @return {boolean} The visibility status of the element. */ public getVisible(): boolean { return this.visible; } /** * Retrieves the parent widget of this widget. * * @return {IWidget | null} The parent widget or null if there is no parent. */ public getParent(): IWidget | null { return this.parent; } /** * Returns the body element of the object. * * @return {HTMLElement} The body element. */ public getBody(): HTMLElement { return this.body; } /** * Retrieves the array of child widgets. * * @return {IWidget[]} The array of child widgets. */ public getChilds(): IWidget[] { return this.childs; } /** * Returns the value of the 'overflow' property. * * @return {boolean} The value of the 'overflow' property. */ public getOverflow(): boolean { return this.overflow; } /** * Adds a child widget to the current widget. * * @param {IWidget} child - The widget to be added. * @return {void} - This function does not return anything. */ public addChild(child: IWidget | null = null): void { if (!child) return; this.childs.push(child); child.setParent(this); child.init(); /*child.resize(); child.render();*/ } public populate(): void { //se agrega a si mismo a window.w y a sus hijos. if (!window.w.get(this.id)) { window.w.set(this.id, this); } for (const child of this.childs) { child.populate(); } } /** * Disables the selection feature. * * @param {void} - * @return {void} - */ public disableSelection(): void { console.log("disableSelection"); } /** * Enables selection. * */ public enableSelection(): void { console.log("enableSelection"); } /** * Hides the element by setting its visibility to false. * * @param {void} - * @return {void} - */ public hide(): void { this.setVisible(false); } /** * Initializes the function. * * @param {type} paramName - description of parameter * @return {type} description of return value */ public init(): void { let freeStyle = false; if (this.type === WidgetTypes.FREE) { freeStyle = true; } const parent = this.getParent(); if (parent) { if (parent.type === WidgetTypes.FREE) { freeStyle = true; } } if (!freeStyle) { this.deleteClass("WUIFixPosition"); this.addClass("WUINonFreePosition"); // this.body.style.position = "absolute"; // this.body.style.overflow = "hidden"; } this.initPosition(); } /** * Initializes the position. * * @param {void} - No parameters required. * @return {void} - No return value. */ initPosition(): void { if (this.type === WidgetTypes.FILL) { if (!this.parent) { this.setX(0); this.setY(0); this.setWH(window.innerWidth, window.innerHeight); } else { //Si tiene padre que lo controle el padre! return; } } } /** * Renders the component. * * @return {void} */ public render(): void { //this.initPosition(); const padding = this.padding; const size = this.align === WidgetAlignTypes.HORIZONTAL ? this.width : this.height; let currentPosition = padding; //Obtener el available size si hay elementos con fixed size let sumFixedSizes = 0; let totalFillElements = 0; for (const child of this.childs) { if (child.type === WidgetTypes.FILL) { totalFillElements++; } } let freeElements = totalFillElements; for (const child of this.childs) { if (child.getFixedSize() !== null) { sumFixedSizes += child.getFixedSize() as number; freeElements--; } } let availableSize = size - sumFixedSizes; /** * Elementos que tienen un padre LIBRE * =================================== */ const myParent = this.getParent(); if (myParent) { if (myParent.type === WidgetTypes.FREE) { //this.getBody().style.position = "relative"; this.addClass("WUIFixPosition"); this.getBody().style.left = ""; this.getBody().style.top = ""; this.getBody().style.height = ""; this.getBody().style.width = ""; if (this.initialWidth != 0) { this.getBody().style.width = `${this.initialWidth}px`; } if (this.initialHeight != 0) { this.getBody().style.height = `${this.initialHeight}px`; } } } /************************************ */ for (const child of this.childs) { if (child.type !== WidgetTypes.FILL) { child.render(); continue; } let elementSize = (availableSize - padding) / freeElements; if (child.getFixedSize() !== null) { elementSize = child.getFixedSize() as number; } if (this.align === WidgetAlignTypes.HORIZONTAL) { child.setY(padding); child.setX(currentPosition); if (child.type === WidgetTypes.FILL) { child.setWH(elementSize - padding, this.getH() - padding * 2); } else { child.setWH(elementSize - padding, this.getH() - padding * 2); } } else if (this.align === WidgetAlignTypes.VERTICAL) { child.setX(padding); child.setY(currentPosition); if (child.type === WidgetTypes.FILL) { child.setWH(this.getW() - padding * 2, elementSize - padding); } else { child.setWH(this.getW() - padding * 2, elementSize - padding); } } currentPosition += elementSize; child.render(); } this.subscribers.forEach((callback) => { if (callback.event == "render") { callback.then(new Event("render"), this); } }); } /** * Resizes the element and its child elements. */ public resize(): void { // Initialize the position of the element this.initPosition(); this.render(); } /** * Displays the element on the screen. * * @return {void} - Does not return a value. */ public display(): void { this.setVisible(true); } /** * Toggles the visibility of something. * * @return {void} Nothing is returned. */ public toggle(): void { this.setVisible(!this.visible); } public renderHTML(content: any): HTMLElement { this.body.appendChild(content); return content as HTMLElement; } private getMaxZIndex(maxZindex: number = 0, node: ChildNode | null = null): number { const parent = node ? node : document.body; if (parent instanceof HTMLElement) { if (parseInt(window.getComputedStyle(parent).zIndex) > maxZindex) { maxZindex = parseInt(window.getComputedStyle(parent).zIndex); } } parent?.childNodes.forEach((child) => { maxZindex = this.getMaxZIndex(maxZindex, child); }); return maxZindex; } public setZIndex(zIndex: number): void { this.getBody().style.zIndex = `${zIndex}`; } public raisteTop(): void { this.setZIndex(this.getMaxZIndex() + 1); } public raiseBottom(): void { this.setZIndex(0); } /** * Attaches a widget to the current widget and delete all pre-existents widgets * * @param {IWidget} guest - The widget to attach. * @return {void} This function does not return anything. */ public attachWidget(guest: IWidget): void { this.removeAllChilds(); this.addChild(guest); guest.setParent(this); guest.render(); this.resize(); this.render(); } /** * Removes all child nodes from the current element. * * @return {void} No return value. */ public removeAllChilds(): void { while (this.getBody().childNodes.length > 0) { const child = this.getBody().firstChild; if (child) this.getBody().removeChild(child); } this.childs = []; } public free(): void { if (this.childs) { for (const child of this.childs) { child.free(); } } window.w.delete(this.id); const body = this.getBody(); const parent = body.parentNode; if (!parent && !body) return; parent?.removeChild(body); } public dispose(): void { this.removeAllChilds(); const body = this.getBody(); const parent = body.parentNode; parent?.removeChild(body); } } export function getOnlyEventProps(props: WidgetProps): WidgetEventProps { const eventProps = { onClick: props.onClick, onDrag: props.onDrag, onResize: props.onResize, onMouseDown: props.onMouseDown, onMouseUp: props.onMouseUp, onMouseMove: props.onMouseMove, onMouseOut: props.onMouseOut, onMouseLeave: props.onMouseLeave, onWheel: props.onWheel, onRender: props.onRender, }; return eventProps; } export function connectWidgetCallback( id: string | null | undefined, props: WidgetEventProps ): void { if (!id) return; connectWidget("widget-added-" + id, { event: "widget-load", then: (_e, _w) => { const widget = w.get(id) as Widget; if (widget) { widget.subscribe({ event: "click", then: (_e, _w) => { props.onClick ? props.onClick({}) : null; }, }); widget.subscribe({ event: "drag", then: (_e, _w) => { props.onDrag ? props.onDrag({}) : null; }, }); widget.subscribe({ event: "resize", then: (_e, _w) => { props.onResize ? props.onResize({}) : null; }, }); widget.subscribe({ event: "mousedown", then: (_e, _w) => { props.onMouseDown ? props.onMouseDown({}) : null; }, }); widget.subscribe({ event: "mouseup", then: (_e, _w) => { props.onMouseUp ? props.onMouseUp({}) : null; }, }); widget.subscribe({ event: "mousemove", then: (_e, _w) => { props.onMouseMove ? props.onMouseMove({}) : null; }, }); widget.subscribe({ event: "mouseout", then: (_e, _w) => { props.onMouseOut ? props.onMouseOut({}) : null; }, }); widget.subscribe({ event: "mouseleave", then: (_e, _w) => { props.onMouseLeave ? props.onMouseLeave({}) : null; }, }); widget.subscribe({ event: "wheel", then: (_e, _w) => { props.onWheel ? props.onWheel({}) : null; }, }); widget.subscribe({ event: "render", then: (_e, _w) => { props.onRender ? props.onRender({}) : null; }, }); } }, }); }