@b-side/parser
Version:
HTML Parser used to parse HTML string and bind data to it.
298 lines • 13 kB
JavaScript
;
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