UNPKG

als-component

Version:

lightweight JavaScript library for creating reactive, server-rendered, and browser-based components.

87 lines (77 loc) 3.77 kB
class Component { static isBrowser = true static componentsToUpdate = new Set() static components = new WeakMap() static qs(selector, host = document) { return host.querySelector(selector) } static qsa(selector, host = document) { return [...host.querySelectorAll(selector)] } static get isAsync() { return this.prototype.render.constructor.name === 'AsyncFunction' }; actions = []; counter = 0; get element() { return Component.isBrowser ? document.querySelector(this.selector) : null } set elementOuter(v) { const element = this.element; if (element) element.outerHTML = v } constructor(props = {}, inner) { this.isAsync = this.constructor.isAsync this.props = props; this.inner = inner; this.name = this.constructor.name + (this.props.key ? this.props.key : ''); this.selector = `[component=${this.name}]`; this.hooks = { mount: [], unmount: [] }; Component.components[this.name] = this this.Component = Component } on(event, fn) { if (this.hooks[event]) this.hooks[event].push(fn) } async callAsync() { return (await this.render(this.props, this.inner)).replace(/^\<\w\w*/, m => `${m} component="${this.name}"`) } callSync() { return this.render(this.props, this.inner).replace(/^\<\w\w*/, m => `${m} component="${this.name}"`) } call() { this.Component.componentsToUpdate.add(this); return this.isAsync ? this.callAsync() : this.callSync() } action(event, fn) { const id = this.name + this.counter++; this.actions.push({ event, id, fn }); return id; } async buildAsync(updateElement = true) { const result = await this.call() if (updateElement) this.elementOuter = result if (this.Component.isBrowser) this.runActions() return result } buildSync(updateElement = true) { const result = this.call() if (updateElement) this.elementOuter = result if (this.Component.isBrowser) this.runActions() return result } build(updateElement) { return this.isAsync ? this.buildAsync(updateElement) : this.buildSync(updateElement) } update(props = this.props, inner = this.inner) { this.props = props; this.inner = inner; return this.build(true) } runActions() { const { components, componentsToUpdate } = this.Component componentsToUpdate.forEach(component => { const { actions, hooks, selector } = component; const element = document.querySelector(selector); const parent = element && element.childNodes.length ? element : document; hooks.mount.forEach(fn => fn(element)); actions.forEach(({ event, fn, id }) => { let elementsForEvent = parent.querySelectorAll(`[${event}="${id}"]`); if (elementsForEvent.length === 0) elementsForEvent = document.querySelectorAll(`[${event}="${id}"]`) if (elementsForEvent.length === 0) return; elementsForEvent.forEach(elementForEvent => { if (event === 'load') fn(elementForEvent); else if (typeof elementForEvent.addEventListener === 'function') { elementForEvent.addEventListener(event, fn); } }) }) component.actions = []; component.hooks.mount = []; component.count = 0; }) for (const name in components) { // check if there are element's components which removed const { selector, hooks } = components[name] if (document.querySelector(selector)) continue; hooks.unmount.forEach(fn => fn(name)); delete components[name]; } this.Component.componentsToUpdate.clear() } }