sevejs
Version:
🌳 Simple and light JS components manager
197 lines (173 loc) • 5.9 kB
JavaScript
/**
* @module components
*/
import { capitalize } from "./utils/capitalize";
/**
* COMPONENTS OBJECT THAT IS EXTENDED BY ALL COMPONENTS
* @class Component
* @see {Component}
* @param {map} components - list of all other components
*/
const Component = class {
constructor(scope) {
this.DOM = { el: scope };
this._compEvents = [];
this.componentsId = 0;
this.components;
this.app;
this.binded = {}; //for saving binded function -> sub/pub (think about a snippet)
}
/**
* Update component list from the app
* @private
* @param {object} components - component list
* @returns {void} - Nothing
*/
_compUpdate(components) {
this.components = components;
this.app = this.components.get("App").get("global");
}
_compResize(event) {
this.resize(event);
}
/**
* Destroy extended content before to call simple destroy
* @private
* @returns {void} - Nothing
*/
_compDestroy() {
// destroy all events - since iteration is erasing itself -> start at the end (as usual)
let index = this._compEvents.length;
while (index > 0) {
index -= 1;
this.off(index, this._compEvents[index], true);
}
// remove all binded functions
this.binded = {};
// then, call for inner component destroy method
this.destroy();
}
/**
* Call another component function
* @see {call}
* @param {string} func - function name that is called
* @param {*} args - list of arguments for the function
* @param {string} comp - component name that will be called (becarefull of the uppercase if needed)
* @param {string} [id] - id of the component if not first one
* @returns {void} - Nothing
* @example
* this.call('openNav', ['homepage', true], 'Navigation', 'main');
*/
call(func, args, comp, id) {
// if function without arguments (but without id)
if (args && !comp) {
comp = args;
args = false;
}
comp = capitalize(comp);
// check if component, then function exist before a call
if (this.components.has(comp)) {
id = id || this.components.get(comp).entries().next().value[0];
const component = this.components.get(comp).get(id);
if (component?.[func]) {
args = Array.isArray(args) ? args : [args];
return component[func](...args);
}
}
}
/**
* global symbol for event Callback.
* @callback requestCallback
*/
/**
* method that create and save an event in order to facilitate its cyclelife accross the component
* @see {on}
* @param {string} event.e - event that is listened
* @param {HTMLElement} event.target - DOM element that must be targeted by the event
* @param {requestCallback} event.cb - callback function triggered at event (work well with binded function)
* @param {object} event.opts - options allowed in addEventListener method
* @returns {number} - return the event id in order to easily remove it
* @example
* const cb = () => {
* console.log("Hello World!");
* };
* const eventId = this.on({e: 'click', target: document, cb, {once: true}})
*/
on(event) {
// get event attributes and create it
const { e, target, cb, opts = false } = event;
target.addEventListener(e, cb, opts);
// save and return
this._compEvents.push(Object.assign(event, { id: this.componentsId }));
return this.componentsId++; // return ID (before ++ to start at 0)
}
/**
* method that remove an event in order to facilitate its cyclelife accross the component
* @see {off}
* @param {number} id - Id of the event to remove (returned by addEvent method {@link Component})
* @param {object} [event] - event that should be removed
* @param {boolean} [byIndex = false] - Is the id param a _compEvents index
* @returns {void} - Nothing
* @example
* this.off(eventId);
*/
off(id, event, byIndex = false) {
let index;
// if no event fournished, find it
if (!event) {
// get saved event
for (let i = 0; i < this._compEvents.length; i++) {
if (this._compEvents[i].id === id) {
event = this._compEvents[i];
index = i;
break;
}
}
}
// get the good id
index = byIndex ? id : index;
// remove the event
const { e, target, cb, opts = false } = event;
target.removeEventListener(e, cb, opts);
this._compEvents.splice(index, 1);
}
/**
* Default init method (init at construction). Should be use to constructor replacement when init component
* @returns {void} - Nothing
*/
init() {}
/**
* Default resize method (init at construction). Should be use to constructor replacement when resize component
* @returns {void} - Nothing
*/
resize() {}
/**
* Default destroy method that is automatically called as component destroy
* @returns {void} - Nothing
*/
destroy() {}
};
//TODO: -> static function (need babel plugin) - proto shared between all instances
Component.prototype.emitter = {};
Component.prototype.trigger = function (name, ...args) {
if (!!Component.prototype.emitter[name] === false) return;
// "sliced method" allow shallow copy to avoid array modification in iteration
//-> unsubscribe in the triggered func would change the array length during iteration
//-> potential callback skipping
Component.prototype.emitter[name].slice().forEach((func) => {
func(...args);
});
};
Component.prototype.subscribe = function (name, func) {
if (!!Component.prototype.emitter[name] === false) {
Component.prototype.emitter[name] = [];
}
Component.prototype.emitter[name].push(func);
};
Component.prototype.unsubscribe = function (name, func) {
let funcs = Component.prototype.emitter[name];
if (!!funcs === false) return;
const funcIndex = funcs.indexOf(func);
funcs.splice(funcIndex);
};
export default Component;