imp(cetra) better plugin handling; ignore components within templates; docs

This commit is contained in:
Benjamin Wegener
2024-01-16 20:11:59 +00:00
parent 687042b24a
commit d6f72d32b8

View File

@@ -15,6 +15,8 @@ class Cetra extends Emitter {
INVALID_PLUGIN_TYPE: 'INVALID_PLUGIN_TYPE',
INVALID_COMMAND: 'INVALID_COMMAND',
COMMAND_NOT_FOUND: 'COMMAND_NOT_FOUND',
PLUGIN_NOT_FOUND: 'PLUGIN_NOT_FOUND',
REGISTERED_PLUGIN_MISSING_EXEC: 'REGISTERED_PLUGIN_MISSING_EXEC',
};
// class static globals
static #instances = new Map();
@@ -157,6 +159,7 @@ class Cetra extends Emitter {
const entityKey = Object.keys(Cetra.#entityTypes).find((key) => {
return Cetra.#entityTypes[key] == entityType;
});
if (!entityKey) {
Logger.error(Cetra.#ERRORS.INVALID_ENTITY_TYPE, entityType);
return false;
@@ -178,7 +181,7 @@ class Cetra extends Emitter {
throw new TypeError(Cetra.#ERRORS.INVALID_ENTITY);
}
if (typeof fn == 'function') {
if (Cetra.#isValidPlugin(fn)) {
if (!(fn instanceof CetraPlugin)) {
if (fn.name) {
Registry.unregister(uniqueId, this.#secretKey);
@@ -209,10 +212,14 @@ class Cetra extends Emitter {
if (typeof fn === 'undefined') return false;
switch (entityType) {
case 'Plugin':
return fn instanceof CetraPlugin || typeof fn === 'function';
return Cetra.#isValidPlugin(fn);
case 'Command':
return typeof fn === 'string';
case 'Type':
return typeof fn === 'function';
return (
typeof fn === 'function' &&
fn.constructor.name === 'Function'
);
default:
return false;
}
@@ -353,13 +360,20 @@ class Cetra extends Emitter {
* @param {string} identifier - The identifier of the plugin.
* @returns {Promise<CetraPlugin>|Function|Object|undefined} - The plugin object or a promise that resolves to the plugin object. Returns undefined if the plugin is not found or if the identifier is not a string.
*/
getPlugin(identifier) {
if (!(identifier instanceof String)) {
Logger.error(Cetra.#ERRORS.INVALID_IDENTIFIER, identifier);
#getPlugin(identifier) {
if (typeof identifier != 'string') {
Logger.error(
Cetra.#ERRORS.INVALID_IDENTIFIER,
identifier,
typeof identifier
);
return undefined;
}
if (!this.#internals.get('plugins').has(identifier)) return undefined;
if (!this.#internals.get('plugins').has(identifier)) {
Logger.error(Cetra.#ERRORS.PLUGIN_NOT_FOUND, identifier);
return undefined;
}
const plugin = this.#internals.get('plugins').get(identifier);
@@ -444,6 +458,40 @@ class Cetra extends Emitter {
});
}
/**
* Checks if a plugin is a valid Cetra plugin.
* @private
* @param {Function} klass - The plugin to be checked.
* @returns {boolean} - Returns true if the plugin is a valid Cetra plugin, otherwise false.
*/
static #isValidPlugin(klass) {
let isInstance = false,
isClass = false,
isFunction = false;
if (
klass instanceof CetraPlugin ||
klass.constructor.name != 'Function'
) {
isInstance = true;
} else if (typeof klass === 'function') {
isFunction = true;
try {
isInstance = new klass() instanceof CetraPlugin;
} catch (error) {
return false;
}
if (!isInstance) {
isClass = Object.prototype.isPrototypeOf.call(
CetraPlugin.prototype,
klass.prototype
);
}
}
return isInstance || isClass || isFunction;
}
/**
* Initializes a plugin by registering it with the Cetra registry.
*
@@ -459,16 +507,15 @@ class Cetra extends Emitter {
});
try {
if (!['function', 'object'].includes(typeof klass))
throw new TypeError(
'plugin is not of type function or object or instance of a CetraPlugin (child) class'
);
if (klass instanceof CetraPlugin) {
if (typeof klass.register === 'function') {
return klass.register(uid);
if (!Cetra.#isValidPlugin(klass)) {
if (klass.plugin) {
Registry.unregister(uniqueId, this.#secretKey);
return this.#initPlugin(klass.plugin);
}
if (typeof klass == 'string' && this.#isUrl(klass))
return klass;
throw new TypeError(Cetra.#ERRORS.INVALID_PLUGIN_TYPE);
}
@@ -480,6 +527,9 @@ class Cetra extends Emitter {
}
}
if (typeof klass.register === 'function')
return klass.register(uid);
// what else could it be?
if (typeof klass !== 'object')
throw new TypeError(Cetra.#ERRORS.INVALID_PLUGIN_TYPE);
@@ -530,7 +580,9 @@ class Cetra extends Emitter {
* @param {boolean} [recursive=true] - Indicates whether to recursively process child nodes.
*/
#readComponents() {
let comps = this.queryElements('', { _parent: this.#root });
let comps = this.#queryElements('', {
_parent: this.#root,
});
if (!comps || comps.length == 0)
return Logger.log('no components found (yet)');
@@ -539,7 +591,7 @@ class Cetra extends Emitter {
comp.matches(
`[data-${this.#internals
.get('settings')
.get('selector')}-id]`
.get('selector')}-id]:not(template)`
)
)
continue;
@@ -589,7 +641,7 @@ class Cetra extends Emitter {
* @param {string} selector - The selector string to query the DOM with.
* @returns {NodeList} A NodeList of matching DOM elements.
*/
queryElements(attributes = '', options = {}) {
#queryElements(attributes = '', options = {}) {
return Array.from(
(options._parent || document).querySelectorAll(
options.selector || this.#selectors(attributes)
@@ -597,7 +649,18 @@ class Cetra extends Emitter {
);
}
queryElement(attributes = '', options = {}) {
/**
* Finds and returns the first element that matches the specified selector.
* If no selector is provided, it uses the selectors generated from the attributes.
*
* @param {string} attributes - The attributes used to generate the selectors.
* @param {Object} options - Additional options for querying the element.
* @param {Element} options._parent - The parent element to search within. Defaults to the document.
* @param {string} options.selector - The specific selector to use for querying the element.
*
* @returns {Element|null} The first element that matches the selector, or null if no match is found.
*/
#queryElement(attributes = '', options = {}) {
return (options._parent || document).querySelector(
options.selector || this.#selectors(attributes)
);
@@ -641,12 +704,10 @@ class Cetra extends Emitter {
comp.getAttribute(
`data-${this.#internals.get('settings').get('selector')}`
) ??
comp.getAttribute(this.#internals.get('settings').get('selector'));
if (typeof compType == 'undefined' || !compType) {
Logger.error(Cetra.#ERRORS.INVALID_COMPONENT_TYPE);
return null;
}
comp.getAttribute(
this.#internals.get('settings').get('selector')
) ??
'BaseComponent';
let compData = new Map([
[
@@ -771,8 +832,6 @@ class Cetra extends Emitter {
*/
handleMutation(mutations, _observer) {
if (_observer != this) return;
console.log('ITS HAPPENING', mutations, _observer);
}
/**
@@ -786,75 +845,21 @@ class Cetra extends Emitter {
#registerPlugins(plugins) {
if (!Array.isArray(plugins)) throw new Error('plugins is not an array');
for (const [identifier, plugin] of plugins) {
this.addPlugin(identifier, plugin);
this.#addEntity('Plugin', identifier, plugin);
// this.addPlugin(identifier, plugin);
}
}
/**
* Registers a plugin with the specified name and options.
* @memberof Cetra
* @instance
* @method
* @param {string} name - The name of the plugin.
* @param {object} options - The options for the plugin.
*/
addPlugin(identifier, plugin) {
const uniqueId = Registry.register({
key: this.#secretKey,
obj: plugin,
parent: this.#identifier,
});
registerCommands(commands, identifier) {
if (!Array.isArray(commands))
throw new TypeError(Cetra.#ERRORS.INVALID_COMMANDS_TYPE);
try {
if (!identifier) {
Logger.error(Cetra.#ERRORS.INVALID_IDENTIFIER);
Registry.unregister(uniqueId, this.#secretKey);
return;
for (const command of commands) {
if (typeof command != 'string') {
Logger.error(Cetra.#ERRORS.INVALID_COMMAND, command);
continue;
}
if (this.#internals.get('plugins').has(identifier)) {
Registry.unregister(uniqueId, this.#secretKey);
Registry.register({
key: this.#secretKey,
obj: plugin,
parent: this.#identifier,
});
}
if (!plugin) throw new TypeError(Cetra.#ERRORS.INVALID_PLUGIN_TYPE);
if (typeof plugin === 'string') {
if (!this.#isUrl(plugin))
throw new TypeError(Cetra.#ERRORS.INVALID_PLUGIN_URL);
Logger.info(`Plugin ${identifier} is loaded from ${plugin}`);
this.#internals.get('plugins').set(identifier, {
plugin,
key: uniqueId,
parent: this.#identifier,
});
return;
}
if (!['function', 'object'].includes(typeof plugin))
throw new TypeError(Cetra.#ERRORS.INVALID_PLUGIN_TYPE);
const pluginInstance = this.#initPlugin(plugin);
if (!pluginInstance)
throw new TypeError(Cetra.#ERRORS.INVALID_PLUGIN_TYPE);
Registry.register({
obj: pluginInstance,
key: this.#secretKey,
parent: this.#identifier,
});
this.#internals.get('plugins').set(identifier, pluginInstance);
this.emit('onAfterRegisterPlugin', pluginInstance);
} catch (err) {
Logger.error(err);
Registry.unregister(uniqueId, this.#secretKey);
this.#addEntity('Command', command, identifier);
}
}
@@ -867,7 +872,10 @@ class Cetra extends Emitter {
* @param {any} e - The event object.
* @throws {Error} If the component with the given ID is not found.
*/
exec(componentId, update = { command, args, evt }) {
exec(
componentId,
update = { command: 'none', args: [], evt: new CustomEvent('empty') }
) {
if (Array.isArray(update.args)) update.args = [];
const comp = this.#internals.get('compIndex').get(componentId);
@@ -880,22 +888,44 @@ class Cetra extends Emitter {
return;
}
this.#internals
.get('commands')
.get(update.command)
.forEach((command) => {
if (!command.exec) {
if (typeof command != 'function' || !command.name) {
Logger.error(
Cetra.#ERRORS.INVALID_COMMAND,
update.command
);
}
command(comp, update);
}
const command = this.#internals.get('commands').get(update.command);
command.exec(comp, update);
});
if (!command)
return Logger.error(
Cetra.#ERRORS.COMMAND_NOT_FOUND,
update.command,
update
);
if (typeof command !== 'string') {
if (typeof command.constructor.name !== 'Function') {
return Logger.error(
Cetra.#ERRORS.INVALID_COMMAND,
update.command
);
}
return command(comp, update);
}
const plugin = this.#getPlugin(command);
if (!plugin)
return Logger.error(
Cetra.#ERRORS.PLUGIN_NOT_FOUND,
update.command,
update,
plugin
);
if (!plugin.exec)
return Logger.error(
Cetra.#ERRORS.REGISTERED_PLUGIN_MISSING_EXEC,
update.command,
update,
plugin
);
plugin.exec(comp, update);
}
}