UNPKG

frntnd-class-with-plugins

Version:
324 lines (258 loc) 8.69 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: ClassWithPlugins.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: ClassWithPlugins.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import events from 'events'; import _ from 'lodash'; import promiseUtil from 'promise-util'; import _plugins from '../singletons/plugins'; const EventEmitter = events.EventEmitter; /** * * @classdesc A basic class that has can be extended using plugins * * @property plugins {Array&lt;String>} Array of plugin names to use in this class * * @param options {Object} the object the instance will be extended with * * @class ClassWithPlugins * @global * */ class ClassWithPlugins { /** * Registers a plugin * @method registerPlugin * @memberof ClassWithPlugins * @static * @param type {String} The type this is a plugin for, mapped onto a Class using the static get _type property on the class * @param name {String} Name of the plugin, this is how the plugin is identified in the system, the name is local to its type * @param plugin {Object} The plugin itself * @returns {*} */ static registerPlugin(type, name, plugin) { const pluginGroup = _plugins[type] = _plugins[type] || {}; if (pluginGroup[name]) { throw new Error(`Can't register ${name} plugin for ${type}, plugin with that name already exists`); } return pluginGroup[name] = plugin; } /** * Retrieves a plugin * @method retrievePlugin * @memberof ClassWithPlugins * @static * @param type {String} The type this is a plugin for * @param name {String} Name of the plugin * @returns {Object|undefined} */ static retrievePlugin(type, name) { if (_plugins[type]) { return _plugins[type][name]; } } /** * Object containing all plugins for all types, in plugins[type][name] hierarchy * @name plugins * @type Object * @memberof ClassWithPlugins * @instance */ get plugins() { return _plugins; } constructor(options = {}) { options._plugins = options.plugins || []; delete options.plugins; _.extend(this, options); this._emitter = new EventEmitter(); this._bindThisContextForAllMethodsInOptions(options); if (this.autoInitialize) { this._initializePlugins(); } } /** * Public API */ /** * Listens for an event trigger by the {@link ClassWithPlugins#trigger} method * @instance * @method on * @memberof ClassWithPlugins * @param event {String} Event to listen to. * @param cb {Function} Function that should be called when this event is triggered. */ on(event, cb) { return this._emitter.on(event, cb); } /** * Triggers an event with data that can be listed to using the {@link ClassWithPlugins#on} method * @instance * @method trigger * @memberof ClassWithPlugins * @param event {String} Event to trigger. * @param data {*} Data the event should trigger with. */ trigger(event, data) { return this._emitter.emit(event, data); } /** * Checks whether this instance has a certain plugin provided using the plugin parameter as a string. * * @param plugin {String} Plugin to look for * @returns {Boolean} * @instance * @method hasPlugin * @memberof ClassWithPlugins */ hasPlugin(plugin) { return this._plugins.indexOf(plugin) !== -1; } /** * This methods registers a hook callback on an instance of a {@link ClassWithPlugins}, * it is called by the framework. This should never be called manually, if you want to register a hook, * create a plugin and add it to the plugins of the class. * If you want to fire a hook, use that instance (non static) hook method. * @memberof ClassWithPlugins * @method hook * @static * @param event {String} The hook event to hook into * @param cb {Function} Callback function, gets ran when this hook executes * @param context {Object} Context the callbacks should be called with * @param instance {ClassWithPlugins} The instance to hook plugins for */ static hook(event, cb, context, instance) { instance.hooks[event] = instance.hooks[event] || []; if (typeof cb !== 'function') throw new Error(`Can't hook to ${event}, callback is not a function, this is likely caused by a missing hook handler.`); instance.hooks[event].push({cb, context}); } /** * Runs all hook listeners from all plugins active on the class, * returns a promise so plugins can do async stuff and you can wait for the plugins to finish. * * @memberof ClassWithPlugins * @method hook * @instance * @param event {String} Hook event to trigger * @param data {Object} Data to supply the hook callback with, in addition to the instance it's called from * @returns {Promise} */ hook(event, data) { const promises = _.map(this.hooks[event], hook => { let val = hook.cb.call(hook.context, this, data); return promiseUtil.ensurePromise(val); }); return Promise.all(promises); } /** * Private API */ _bindThisContextForAllMethodsInOptions(options) { // get all method names from the options object const bindAllArguments = _.methods(options); if (bindAllArguments.length) { // add this as context argument (the first argument) bindAllArguments.unshift(this); // actually bind the context _.bindAll.apply(_, bindAllArguments); } } /** * * @private */ _initializePlugins() { _.bindAll( this, '_initializePlugin', 'hook' ); this.hooks = this.hooks || {}; this._plugins = this._plugins || []; this._loadedPlugins = []; _.each(this._plugins, (pluginName) => { this._initializePlugin(pluginName); }); } _initializePlugin(pluginName, isDependency) { const plugin = this._getPlugin(pluginName); // dependency already loaded, we don't have to initialize it again if (isDependency &amp;&amp; this._dependencyIsLoaded(plugin)) return; if (!plugin) throw new Error(`Plugin '${pluginName}' not found`); this._initializePluginDependencies(plugin); if (this._loadedPlugins.indexOf(plugin) === -1) { this._loadPlugin(plugin); } } _dependencyIsLoaded(plugin) { return ClassWithPlugins._allLoadedPlugins.indexOf(plugin) !== -1; } _loadPlugin(plugin) { const namespace = this._addPluginNamespace(plugin); this._initializeHooks(plugin, namespace); this._exposeProperties(plugin); this._loadedPlugins.push(plugin); ClassWithPlugins._allLoadedPlugins.push(plugin); } _getPlugin(pluginName) { const [type, plugin] = pluginName.split('.'); if (!plugin) { return this.plugins[this.constructor._type][pluginName]; } else { return this.plugins[type][plugin]; } } _initializePluginDependencies(plugin) { _.each(plugin.dependencies, (pluginName) => { this._initializePlugin(pluginName, true); }); } _initializeHooks(plugin, namespace) { _.each(plugin.hooks, (methodName, event) => { this._initializeHook(event, methodName, plugin, namespace); }); } _initializeHook(event, methodName, plugin, namespace) { this.constructor.hook(event, plugin[methodName], namespace || plugin, this); } _exposeProperties(plugin) { _.extend(this, _.pick(plugin, plugin.expose)); } _addPluginNamespace(plugin) { if (plugin.namespace) { return this[plugin.namespace] = _.clone(plugin); } } } ClassWithPlugins.prototype.autoInitialize = true; ClassWithPlugins._allLoadedPlugins = []; export default ClassWithPlugins; </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ClassWithPlugins.html">ClassWithPlugins</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-README.html">README</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.3.3</a> on Thu Sep 24 2015 22:53:49 GMT+0200 (CEST) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>