UNPKG

@veams/component

Version:

Component Class for Components in Veams for static page apps

426 lines (365 loc) 10.1 kB
'use strict'; /** * Represents a component constructor which supports * options merging, * binding and unbinding of events and subscriptions with template strings, * rendering of templates * and a destroy behaviour. * * @module @veams/component * @author Sebastian Fitzner */ /** * Imports */ import Base, { BaseConfig } from '@veams/base'; import { Collection } from './helpers/collection'; import getStringValue from './helpers/get-string-value'; import tplEngine from './helpers/template-engine'; import eventHandler from './helpers/event-handler'; export interface ComponentConfig extends BaseConfig { context?: any; // @TODO: Check type } export interface Subscriber { id?: string; delegate?: any; type: any; event: any; handler: any; } const DEFAULT_LIFECYCLES = { create: true, willMount: true, didMount: true, render: false }; /** * Custom Functions */ function buildEvtId(evtKeyArr, fnName) { return evtKeyArr.join('_') + '_' + fnName; } /** * Hidden variables */ // Custom event handler element which will be used in `events()` and `subscribe` let eventElement = null; abstract class Component extends Base { context: any; _events: { [key: string]: string }; _subscribe: { [key: string]: string }; __subscribers: Collection<Subscriber>; private __eventElement: any; // TODO :: Create definition /** * Constructor * * to save standard elements like el and options and * execute initialize as default method. * * @param {Object} obj [{}] - Object which contains el, options from the DOM and namespace. * @param {Object} options [{}] - Object which contains options of the extended class. */ constructor(obj: ComponentConfig, options = {}) { super(obj, options); this.context = obj.context || window['Veams']; this.__eventElement = eventHandler(this.el); if (!this.context) { console.info('@veams/component :: There is no context defined! When you want to use @veams/plugin-vent or any other singleton shared by your Veams instance provide the Veams object as context!'); } this.initialize(obj, options); } // ---------------------------------------------------------- // GETTER & SETTERS // ---------------------------------------------------------- /** * Get and set events object */ set events(obj) { this._events = obj; } get events() { return this._events; } /** * Get and set subscribe object */ set subscribe(obj) { this._subscribe = obj; } get subscribe() { return this._subscribe; } addSubscriber(obj: Subscriber) { if (!this.__subscribers) { this.__subscribers = {}; } this.__subscribers[obj.id] = { delegate: obj.delegate, type: obj.type, event: obj.event, handler: obj.handler }; } get _subscribers(): Collection<Subscriber> { return this.__subscribers; } // ---------------------------------------------------------- // STANDARD METHODS // ---------------------------------------------------------- initialize(...args) { } /** * Private method to create all necessary elements and bindings. * * @private */ create() { this.preRender(); this.registerEvents(this.events, false); this.registerEvents(this.subscribe, true); this.bindEvents(); } /** * Bind local and global events * * @public */ bindEvents() { } /** * Unbind events * * @public */ unbindEvents() { } /** * Pre-Render templates * which can be used to render content into it * * @public */ preRender() { return this; } /** * Render your module * * @public */ render() { return this; } /** * Destroy component by unbinding events and * removing element from DOM */ destroy() { this.unregisterEvents(); this.unbindEvents(); this.el.remove(); } /** * Render template with data * * @param {String} tplName - Template name which gets returned as rendered element. * @param {Object} data - Data which gets handled by the template. */ renderTemplate(tplName: string, data: object) { if (!this.context.templater) { console.error(` @veams/component :: It seems that you haven\'t added the @veams/plugin-templater. In order to work with 'renderTemplate()' you need to add it! `); } else { return this.context.templater.render(tplName, data); } } // ---------------------------------------------------------- // MOUNT PROCESS METHODS // Mount process methods will be handled by the VeamsModules plugin // ---------------------------------------------------------- /** * This method will be executed after initialise */ public willMount(): void { }; /** * This method will be executed before unregistering events */ public willUnmount(): void { }; /** * This method will be executed after render */ public didMount(): void { }; /** * This method will be executed after unregistering events */ public didUnmount(): void { }; // ---------------------------------------------------------- // EVENTS METHODS // ---------------------------------------------------------- /** * Register multiple events which are saved in an object. * * @param {Object} evts - Events object which contains an object with events as key and functions as value. * @param {Boolean} global - Flag to switch between global and local events. * * @private */ registerEvents(evts: object, global = false) { if (evts) { Object.keys(evts).forEach((key) => { this.registerEvent(key, evts[key], global); }); } } /** * Register an event by using a simple template engine and * a key/value pair. * * @param {String} evtKey - Event key which contains event and additionally a delegated element. * @param {String} fn - Function defined as string which will be bound to this. * @param {Boolean} global - Flag if global or local event . * * @public * * @example * this.registerEvent('click .btn', 'render'); * this.registerEvent('click {{this.options.btn}}', 'render'); * this.registerEvent('{{App.EVENTS.custom.event', 'render'); * this.registerEvent('{{App.EVENTS.resize', 'render', true); */ registerEvent(evtKey: string, fn: string, global = false) { if (typeof evtKey !== 'string') { console.error('@veams/component :: Your event is not a string!'); return; } if (typeof fn !== 'string') { console.error('@veams/component :: Your event handler function is not a string!'); return; } let evtKeyArr = evtKey.split(' '); let arrlen = evtKeyArr.length; let evtType = getStringValue.apply(this, [tplEngine(evtKeyArr[0]), this.context]); let bindFn = this[fn].bind(this); let id = buildEvtId(evtKeyArr, fn); if (arrlen > 2) { throw new Error('@veams/component :: It seems like you have more than two strings in your events object!'); } // Bind on this.el if (arrlen === 1 && !global) { this.__eventElement.on(evtType, bindFn); this.addSubscriber({ type: 'event', id: id, event: evtType, handler: bindFn }); } else if (arrlen === 1 && global) { if (!this.context && !this.context.Vent) { console.warn('@veams/component :: There is no context or the Vent object is missing. Subscribing to global events will not work without it!'); return; } this.context.Vent.subscribe(evtType, bindFn); this.addSubscriber({ type: 'globalEvent', id: id, event: evtType, handler: bindFn }); } else { let delegate = getStringValue.apply(this, [tplEngine(evtKeyArr[1])]); this.__eventElement.on(evtType, delegate, bindFn); this.addSubscriber({ type: 'delegatedEvent', delegate: delegate, id: id, event: evtType, handler: bindFn }); } } /** * Delete all registered events. */ unregisterEvents() { for (let key in this._subscribers) { if (this._subscribers.hasOwnProperty(key)) { let obj = this._subscribers[key]; if (obj.type === 'globalEvent') { if (!this.context && !this.context.Vent) { console.warn('@veams/component :: There is no context or the Vent object is missing. Subscribing to global events will not work without it!'); return; } this.context.Vent.unsubscribe(obj.event, obj.handler); } else if (obj.type === 'delegatedEvent') { this.__eventElement.off(obj.event, obj.delegate, obj.handler); } else { this.__eventElement.off(obj.event, obj.handler); } } } } /** * Unregister an event by using the saved subscribers and * a key/value pair. * * @param {String} evtKey - Event key which contains event and additionally a delegated element. * @param {String} fn - Function defined as string which will be unbound to this. * * @public * * @example * this.unregisterEvent('click .btn', 'render'); * this.unregisterEvent('click {{this.options.btn}}', 'render'); * this.unregisterEvent('{{App.EVENTS.custom.event', 'render'); * this.unregisterEvent('{{App.EVENTS.resize', 'render'); */ unregisterEvent(evtKey: string, fn: string) { let evtKeyArr = evtKey.split(' '); let id = buildEvtId(evtKeyArr, fn); if (this._subscribers[id]) { let obj = this._subscribers[id]; if (obj.type === 'globalEvent') { if (!this.context && !this.context.Vent) { console.warn('@veams/component :: There is no context or the Vent object is missing. Subscribing to global events will not work without it!'); return; } this.context.Vent.unsubscribe(obj.event, obj.handler); } else if (obj.type === 'delegatedEvent') { this.__eventElement.off(obj.event, obj.delegate, obj.handler); } else { this.__eventElement.off(obj.event, obj.handler); } } } } function autocreate(lifecycles) { const mergedLifeCycles = {...DEFAULT_LIFECYCLES, ...lifecycles}; return function creatInstance(instance: Component) { if (mergedLifeCycles.create) { instance.create(); } if (mergedLifeCycles.willMount) { instance.willMount(); } if (mergedLifeCycles.didMount) { instance.didMount(); } if (mergedLifeCycles.render) { instance.render(); } return instance; } } export default Component; export { autocreate }