This repository has been archived on 2025-03-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cetra/lib/core/BaseComponent.js

265 lines
7.0 KiB
JavaScript
Raw Permalink Normal View History

import Cetra from '../../lib/lib.js';
/**
* Represents a base component class.
*/
export default class BaseComponent {
#data = new Map([
['type', 'BaseComponent'],
['selector', 'cetra-component'],
['variables', new Map()],
['cetraId', ''],
]);
/**
* Constructs a new instance of the BaseComponent class.
* @param {Map} data - The initial data for the component.
*/
constructor(data = new Map()) {
for (const key of this.#data.keys()) {
if (
!data.has(key) ||
typeof data.get(key) != typeof this.#data.get(key)
)
continue;
this.#data.set(key, data.get(key));
}
this.#data = data;
}
/**
* Retrieves the value associated with the specified key from the data map.
* @param {string} key - The key to retrieve the value for.
* @returns {*} - The value associated with the key.
*/
get(key) {
return this.#data.get(key);
}
/**
* Sets a key-value pair in the component's data.
* @param {string} key - The key to set.
* @param {*} value - The value to set.
* @returns {BaseComponent} - The updated component instance.
*/
set(key, value) {
this.#data.set(key, value);
return this;
}
/**
* Retrieves the value of a variable by its name.
*
* @param {string} varName - The name of the variable.
* @returns {*} - The value of the variable, or null if the variable is not found.
* @throws {Error} - If no Cetra instance is found.
*/
getValue(varName) {
if (typeof varName != 'string') return null;
if (varName.startsWith('$')) varName = varName.slice(1);
if (!this.get('variables').has(varName)) return null;
const frameWork = Cetra.getInstance(this.get('cetraId'));
if (!frameWork) throw new Error('no cetra instance found');
const varValue = frameWork.convertValue(
this.get('variables').get(varName).get('type'),
this.getVarNodes(varName).shift().textContent
);
return varValue;
}
/**
* Retrieves the value of a variable.
* @param {string} varName - The name of the variable.
* @returns {*} - The value of the variable.
*/
getVal(varName) {
return this.getValue(varName);
}
/**
* Retrieves the value of a variable by its name.
*
* @param {string} varName - The name of the variable.
* @returns {*} - The value of the variable.
*/
getVar(varName) {
return this.get('variables').get(varName);
}
/**
* Retrieves the DOM node associated with the component.
* @returns {HTMLElement} The DOM node.
* @throws {Error} If no node is found.
*/
getNode() {
let node = document.querySelector(this.get('selector'));
if (!node) node = document.getElementById(this.get('id'));
if (!node) throw new Error('no node found');
return node;
}
/**
* Retrieves all DOM nodes that have a data attribute matching the specified variable name.
* @param {string} varName - The name of the variable to search for.
* @returns {Array<HTMLElement>} - An array of DOM nodes matching the variable name.
* @throws {Error} - Throws an error if no cetra instance is found.
*/
getVarNodes(varName) {
const frameWork = Cetra.getInstance(this.get('cetraId'));
if (!frameWork) throw new Error('no cetra instance found');
const varNodeSelector = `[data-${frameWork.getOption(
'selector'
)}-var^="${varName}"]`;
return [...this.getNode().querySelectorAll(varNodeSelector)];
}
/**
* Updates the component with the given deltas.
* @param {Array} deltas - An array of deltas to apply.
* @throws {TypeError} If deltas is not an array.
* @throws {Error} If no cetra instance is found.
*/
update(deltas) {
if (!Array.isArray(deltas))
throw new TypeError('deltas must be an array');
const frameWork = Cetra.getInstance(this.get('cetraId'));
if (!frameWork) throw new Error('no cetra instance found');
for (const delta of deltas) {
const { new: value, old: oldValue, varName } = delta;
if (!this.get('variables').has(varName)) continue;
const variable = this.get('variables').get(varName);
this.getVarNodes(varName).forEach((el) => {
el.textContent = value ?? variable.get('default');
});
}
}
}
2024-04-21 14:38:40 +01:00
class NewCetra {
constructor(options = {}) {
this.wrapper = options.wrapper || document.body;
this.componentMap = new Map();
this.variableMap = new Map();
this.observer = null;
}
init() {
this.discoverComponents();
this.setupObserver();
this.triggerHook('afterInit');
}
registerComponent(name, component) {
this.componentMap.set(name, component);
}
getComponent(name) {
return this.componentMap.get(name);
}
mountComponent(element) {
const componentName = element.getAttribute('data-ctra');
const component = this.getComponent(componentName) || this.getComponent('BaseComponent');
if (!component) {
console.warn(`Component "${componentName}" not found.`);
return;
}
const instance = new component(element);
element.__ctra_instance = instance;
this.triggerHook('beforeComponentMount', instance);
instance.mount();
this.triggerHook('afterComponentMount', instance);
}
unmountComponent(element) {
const instance = element.__ctra_instance;
if (!instance) {
console.warn('Unmounting a non-mounted component.');
return;
}
this.triggerHook('beforeComponentUnmount', instance);
instance.unmount();
this.triggerHook('afterComponentUnmount', instance);
delete element.__ctra_instance;
}
updateComponent(element) {
const instance = element.__ctra_instance;
if (!instance) {
console.warn('Updating a non-mounted component.');
return;
}
this.triggerHook('beforeComponentUpdate', instance);
instance.update();
this.triggerHook('afterComponentUpdate', instance);
}
getVariable(name) {
return this.variableMap.get(name);
}
setVariable(name, value) {
this.variableMap.set(name, value);
this.updateVariableBindings(name);
}
discoverComponents() {
const elements = this.wrapper.querySelectorAll('[data-ctra]');
elements.forEach(element => this.mountComponent(element));
}
setupObserver() {
this.observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.hasAttribute('data-ctra')) {
this.mountComponent(node);
}
});
mutation.removedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.__ctra_instance) {
this.unmountComponent(node);
}
});
} else if (mutation.type === 'attributes' && mutation.attributeName === 'data-ctra') {
this.updateComponent(mutation.target);
}
});
});
this.observer.observe(this.wrapper, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-ctra']
});
}
updateVariableBindings(name) {
const elements = this.wrapper.querySelectorAll(`[data-ctra-var="${name}"]`);
elements.forEach(element => {
const value = this.getVariable(name);
element.textContent = value;
});
}
triggerHook(name, ...args) {
// Placeholder for triggering lifecycle hooks
console.log(`Triggering hook: ${name}`, ...args);
}
}