heissepreise/site/views/view.js

154 lines
4.9 KiB
JavaScript
Raw Normal View History

2023-06-11 18:29:31 +02:00
const { getBooleanAttribute } = require("../misc");
class View extends HTMLElement {
constructor() {
super();
this._model = null;
2023-06-09 02:03:57 +02:00
this._listener = () => this.render();
this._disableChangeEvent = false;
}
2023-06-11 02:37:35 +02:00
static traverse(element, parents, filter, childrenProcessed) {
if (!element) return;
if (element.getAttribute("x-id")) {
if (filter(parents, element)) parents.push(element);
else return;
}
const childNodes = element.childNodes;
for (let i = 0; i < childNodes.length; i++) {
const child = childNodes[i];
if (child.nodeType === Node.ELEMENT_NODE) {
View.traverse(child, parents, filter, childrenProcessed);
}
}
if (parents.length > 0) parents.pop();
childrenProcessed(parents, element);
}
static elements(view) {
2023-06-11 18:29:31 +02:00
let elements = [...view.querySelectorAll("[x-id]")];
elements = elements.filter((el) => {
let parent = el.parentElement;
while (parent != view) {
if (parent instanceof View) return false;
if (getBooleanAttribute(parent, "x-notraverse")) return false;
parent = parent.parentElement;
}
return true;
});
const result = {};
2023-06-11 18:29:31 +02:00
elements.forEach((element) => {
if (result[element.getAttribute("x-id")]) {
console.log(`Duplicate element x-id ${element.getAttribute("x-id")} in ${view.localName}`);
}
result[element.getAttribute("x-id")] = element;
});
return result;
}
2023-06-11 02:37:35 +02:00
get elements() {
return View.elements(this);
}
set model(model) {
if (this._model) this._model.removeListener(this._listener);
this._model = model;
this._model.addListener(this._listener);
this.render();
}
get model() {
return this._model;
}
get state() {
const elements = this.elements;
const properties = ["checked", "value"];
const state = {};
for (const key of Object.keys(elements)) {
const element = elements[key];
if (!element.hasAttribute("x-state")) continue;
const elementState = {};
for (const property of properties) {
if (property in element) {
elementState[property] = element[property];
}
}
state[key] = elementState;
}
return state;
}
set state(state) {
const elements = this.elements;
this._disableChangeEvent = true;
for (const key of Object.keys(elements)) {
const elementState = state[key];
if (elementState) {
const element = elements[key];
for (const property in elementState) {
element[property] = elementState[property];
if (element.localName === "input" && element.getAttribute("type") === "radio") {
const changeEvent = new CustomEvent("x-change", {
bubbles: true,
cancelable: true,
});
element.dispatchEvent(changeEvent);
}
}
}
}
this._disableChangeEvent = false;
this.fireChangeEvent();
}
2023-06-09 02:03:57 +02:00
render() {}
setupEventHandlers() {
const handler = (event) => this.fireChangeEvent();
const elements = this.elements;
for (const key of Object.keys(elements)) {
const element = elements[key];
2023-06-11 02:37:35 +02:00
if (element._handlerSet) continue;
if (element.hasAttribute("x-change")) {
element.addEventListener("change", handler);
2023-06-11 02:37:35 +02:00
element._handlerSet = true;
}
if (element.hasAttribute("x-click")) {
element.addEventListener("click", handler);
2023-06-11 02:37:35 +02:00
element._handlerSet = true;
}
if (element.hasAttribute("x-input")) {
element.addEventListener("input", handler);
2023-06-11 02:37:35 +02:00
element._handlerSet = true;
}
if (element.hasAttribute("x-input-debounce")) {
const DEBOUNCE_MS = 50;
let timeoutId = 0;
const debounceHandler = (event) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
this.fireChangeEvent();
}, DEBOUNCE_MS);
};
element.addEventListener("input", debounceHandler);
2023-06-11 02:37:35 +02:00
element._handlerSet = true;
}
}
}
fireChangeEvent() {
if (this._disableChangeEvent) return;
const event = new CustomEvent("x-change", {
bubbles: true,
cancelable: true,
});
this.dispatchEvent(event);
}
}
exports.View = View;