UNPKG

@b-side/parser

Version:

HTML Parser used to parse HTML string and bind data to it.

298 lines 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Renderer = void 0; const element_1 = require("./element"); const binder_1 = require("./binder"); const utils_1 = require("./utils"); const auto_update_1 = require("./auto-update"); const prefixEvents = 'on-'; const remove = (elements, purgedata) => { elements.forEach((element) => { if (element instanceof element_1.bsIteration && element.clones.length) { element.clones.forEach((clone) => remove(clone, purgedata)); } if (element.childs.length) { remove(element.childs, purgedata); } if (element.dependancies) { element.dependancies.reset(); } if (element instanceof element_1.bsExpression) { element.siblings.forEach((sibling) => sibling.remove()); element.siblings.length = 0; element.dom?.remove(); } if (element instanceof element_1.bsText || element instanceof element_1.bsNode) { element.dom.remove(); } }); }; const placeAt = (parent, element, previousSibling) => { const document = (0, utils_1.getDocument)(); while (!parent.dom && parent.parent) { parent = parent.parent; } if (parent.dom && element.dom && !document.contains(element.dom) && !parent.dom.contains(element.dom)) { if (previousSibling && parent !== previousSibling && previousSibling.dom && parent.dom.contains(previousSibling.dom)) { if (previousSibling.dom === previousSibling.dom.nextSibling) { parent.dom.appendChild(element.dom); } else { parent.dom.insertBefore(element.dom, previousSibling.dom.nextSibling); } } else { parent.dom.insertBefore(element.dom, parent.dom.childNodes[0]); } if (element instanceof element_1.bsExpression) { element.siblings.forEach((child) => { parent.dom?.insertBefore(child, element.dom || null); }); } } }; class Renderer { constructor(elements, data) { this.elements = {}; this.data = {}; this.nextFramePropertiesUpdate = new Set(); if (elements && data) { this.bind(elements, data); } } bind(elements, data = {}) { this.elements = elements; this.data = data; new binder_1.Binder(elements, data); if (data) { new auto_update_1.AutoUpdate(data, elements, async (properties) => { await this.update(properties); }); } } updateOnNextFrame(properties) { properties.forEach((property) => { if (!this.nextFramePropertiesUpdate.has(property)) { this.nextFramePropertiesUpdate.add(property); } }); return new Promise((resolve) => { (0, utils_1.getWindow)().requestAnimationFrame(async () => { if (this.nextFramePropertiesUpdate.size) { const properties = [...this.nextFramePropertiesUpdate]; this.nextFramePropertiesUpdate.clear(); await this.build([this.elements], properties, {}); } resolve(); }); }); } async update(propertychanged) { if (propertychanged) { await this.updateOnNextFrame(propertychanged); } else { await this.build([this.elements], [], {}); } } async build(elements, propertychanged, dataModifier, parent, previousSibling) { for (const element of elements) { if (element instanceof element_1.bsIteration) { previousSibling = await this.evalFor(element, propertychanged, dataModifier, previousSibling); } else if (element instanceof element_1.bsCondition) { previousSibling = (await this.evalIf(element, propertychanged, dataModifier, previousSibling)) || previousSibling; } else if (element instanceof element_1.bsExpression) { previousSibling = await this.elvalExpression(element, propertychanged, dataModifier, previousSibling); } if (parent && (element instanceof element_1.bsNode || element instanceof element_1.bsText || element instanceof element_1.bsExpression)) { if (element instanceof element_1.bsNode) { this.setEvents(element, dataModifier, propertychanged); await this.setAttributes(element, dataModifier, propertychanged); } placeAt(parent, element, previousSibling); previousSibling = element; } if (element instanceof element_1.bsNode || element instanceof element_1.bsFragment) { const e = await this.build(element.childs, propertychanged, dataModifier, element, previousSibling); if (e?.dom && previousSibling?.dom !== e.dom && !previousSibling?.dom?.contains(e.dom)) { previousSibling = e; } } } return previousSibling; } setEvents(element, dataModifier, propertychanged) { for (const [name, expression] of element.eventsMap?.dynamic || []) { if (expression.handler && !expression.dependancies.shouldUpdate(this.data, dataModifier, propertychanged)) { continue; } const options = { capture: false, once: false, passive: false, useCapture: false, }; const callback = async (e) => { if ((await expression.execute(Object.assign({ event: e }, dataModifier))) === false) { console.warn(`Automatically cancelled event ${name}`); e.preventDefault(); e.stopPropagation(); } }; if (name === 'click') { options.passive = false; } if (expression.handler) { element.dom.removeEventListener(name, expression.handler.callback, expression.handler.options); } element.dom.addEventListener(name, callback, options); expression.handler = { callback, options }; } } async setAttributes(element, dataModifier, propertychanged) { let resetSelectedOption = false; let resetCheckedOption = false; for (const [name, expression] of element.attributesMap.dynamic || []) { if (!expression.dependancies.shouldUpdate(this.data, dataModifier, propertychanged)) { continue; } const lastResult = expression.lastResult; const result = await expression.execute(dataModifier); resetSelectedOption = name === 'selected' || lastResult === 'selected'; resetCheckedOption = name === 'checked' || lastResult === 'checked'; if (name.match(/@__[0-9]+__@/)) { const [attrName, attrValue] = result?.split('=') || ['']; resetSelectedOption = resetSelectedOption || attrName === 'selected'; resetCheckedOption = resetCheckedOption || attrName === 'checked'; if (attrName.indexOf(prefixEvents) === 0) { console.warn(`to implement: Dynamic attribute with event`, name, result); } else if (attrName) { element.dom.setAttribute(attrName, attrValue?.replace(/(^['"`]|['"`]$)/g, '') || ''); } else if (lastResult) { element.dom.removeAttribute(lastResult.split('=')[0]); } } else if (result === null || result === undefined) { element.dom.removeAttribute(name); } else if (result.toString() !== element.dom.getAttribute(name)) { if (element.dom['@b-side']) { element.dom['@b-side'].attributesValues[name] = result; } element.dom.setAttribute(name, result); } } } async evalIf(element, propertychanged, dataModifier, previousSibling) { if (element === element.logic.elements[0]) { element.logic.resolved.length = 0; } else if (element.logic.resolved.length) { remove(element.childs); return element.logic.resolved[0].childs[element.logic.resolved[0].childs.length - 1]; } let result = element.expression.lastResult; if (element.expression.dependancies.shouldUpdate(this.data, dataModifier, propertychanged)) { result = await element.expression.execute(dataModifier); } if (element.logic.isDefault || result) { previousSibling = await this.build(element.childs, propertychanged, dataModifier, element.parent, previousSibling); element.logic.resolved.push(element); } else { remove(element.childs); } return previousSibling; } async evalFor(element, propertychanged, dataModifier, previousSibling) { if (element.expression.dependancies.isLocked || (!element.expression.dependancies.shouldUpdate(this.data, dataModifier, propertychanged) && !element.dependancies.shouldUpdate(this.data, dataModifier, propertychanged))) { return element.clones.length ? element.clones[element.clones.length - 1][element.clones[element.clones.length - 1].length - 1] : previousSibling; } const propertychangedForChilds = [...propertychanged]; for (const property of element.expression.dependancies.vars) { propertychangedForChilds.push(property); } const callback = async (data, index) => { const cloneData = Object.assign({}, dataModifier, data); if (element.clones.length <= index) { element.clones.push(element.clone().childs); } previousSibling = await this.build(element.clones[index], propertychangedForChilds, cloneData, element, previousSibling); }; const result = await element.expression.execute(Object.assign({ ___c: callback }, dataModifier)); if (result < element.clones.length) { let index = element.clones.length - 1; do { remove(element.clones[index], true); element.clones.splice(index, 1); index -= 1; } while (element.clones.length > result); } return previousSibling; } async elvalExpression(element, propertychanged, dataModifier, previousSibling) { if (!element.expression || !element.expression.dependancies.shouldUpdate(this.data, dataModifier, propertychanged)) { return previousSibling; } let result = await element.expression.execute(dataModifier); if (typeof result === 'undefined' || result === null) { result = ''; } if (element.expression.resultHasChanged && element.template) { element.template.innerHTML = result.toString(); element.dom?.remove(); element.siblings.forEach((child) => child.remove()); const document = (0, utils_1.getDocument)(); const childs = [...element.template.content.childNodes]; element.dom = (childs.pop() || document.createTextNode('')); if (childs.length) { element.siblings = childs; } } return previousSibling; } toString(style = false) { const window = (0, utils_1.getWindow)(); let innerHTML = ''; if (this.elements instanceof element_1.bsFragment || this.elements instanceof element_1.bsExpression) { const document = (0, utils_1.getDocument)(); const container = document.createElement('div'); [...this.elements.dom.childNodes].forEach((child) => { if (!style && child instanceof window.HTMLStyleElement) { } else { container.appendChild(child.cloneNode(true)); } }); innerHTML = container.innerHTML.toString(); } else if (this.elements instanceof element_1.bsText) { innerHTML = this.elements.dom.textContent || ''; } else if (this.elements.dom instanceof window.HTMLElement) { innerHTML = this.elements.dom.innerHTML.toString(); } return innerHTML; } } exports.Renderer = Renderer; //# sourceMappingURL=renderer.js.map