Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #84 from ckeditor/t/78b
Browse files Browse the repository at this point in the history
Other: Introduced `PluginInterface`. A plugin doesn't need to inherit directly from the `Plugin` class, as long as it implements some minimal interface. See #78.
  • Loading branch information
Reinmar authored May 23, 2017
2 parents 3e6802a + 1796327 commit f476f34
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 157 deletions.
4 changes: 4 additions & 0 deletions src/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export default class Editor {

function initPlugins( loadedPlugins, method ) {
return loadedPlugins.reduce( ( promise, plugin ) => {
if ( !plugin[ method ] ) {
return promise;
}

return promise.then( plugin[ method ].bind( plugin ) );
}, Promise.resolve() );
}
Expand Down
196 changes: 121 additions & 75 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,98 +13,144 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
/**
* The base class for CKEditor plugin classes.
*
* @mixes module:utils/observablemixin~ObservaleMixin
* @implements module:core/plugin~PluginInterface
* @mixes module:utils/observablemixin~ObservableMixin
*/
export default class Plugin {
/**
* Creates a new Plugin instance. This is the first step of a plugin initialization.
* See also {@link #init} and {@link #afterInit}.
*
* A plugin is always instantiated after its {@link module:core/plugin~Plugin.requires dependencies} and the
* {@link #init} and {@link #afterInit} methods are called in the same order.
*
* Usually, you'll want to put your plugin's initialization code in the {@link #init} method.
* The constructor can be understood as "before init" and used in special cases, just like
* {@link #afterInit} servers for the special "after init" scenarios (e.g. code which depends on other
* plugins, but which doesn't {@link module:core/plugin~Plugin.requires explicitly require} them).
*
* @param {module:core/editor/editor~Editor} editor
* @inheritDoc
*/
constructor( editor ) {
/**
* The editor instance.
*
* @readonly
* @member {module:core/editor/editor~Editor} module:core/plugin~Plugin#editor
* @member {module:core/editor/editor~Editor} #editor
*/
this.editor = editor;
}

/**
* An array of plugins required by this plugin.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* import Image from './image.js';
*
* export default class ImageCaption extends Plugin {
* static get requires() {
* return [ Image ];
* }
* }
*
* @static
* @readonly
* @member {Array.<Function>|undefined} module:core/plugin~Plugin.requires
* @inheritDoc
*/
destroy() {
}
}

/**
* Optional name of the plugin. If set, the plugin will be available in
* {@link module:core/plugincollection~PluginCollection#get} by its
* name and its constructor. If not, then only by its constructor.
*
* The name should reflect the package name + the plugin module name. E.g. `ckeditor5-image/src/image.js` plugin
* should be named `image/image` (the `ckeditor5-` prefix is stripped during compilation). If plugin is kept
* deeper in the directory structure, it's recommended to only use the module file name, not the whole path.
* So, e.g. a plugin defined in `ckeditor5-ui/src/notification/notification.js` file may be named `ui/notification`.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* export default class ImageCaption {
* static get pluginName() {
* return 'image/imagecaption';
* }
* }
*
* @static
* @readonly
* @member {String|undefined} module:core/plugin~Plugin.pluginName
*/
mix( Plugin, ObservableMixin );

/**
* The second stage (after plugin {@link #constructor}) of plugin initialization.
* Unlike the plugin constructor this method can perform asynchronous.
*
* A plugin's `init()` method is called after its {@link module:core/plugin~Plugin.requires dependencies} are initialized,
* so in the same order as constructors of these plugins.
*
* @returns {null|Promise}
*/
init() {}
/**
* The base interface for CKEditor plugins.
*
* In its minimal form it can be a simple function (it will be used as a constructor) which accepts
* {@link module:core/editor/editor~Editor the editor} as a parm.
* It can also implement a few methods which, when present, will be used to properly initialize and destroy the plugin.
*
* // A simple plugin which enables a data processor.
* function MyPlugin( editor ) {
* editor.data.processor = new MyDataProcessor();
* }
*
* In most cases, however, you'll want to inherit from the {@link module:core/plugin~Plugin} class which implements the
* {@link module:utils/observablemixin~ObservableMixin} and is, therefore, more convenient:
*
* class MyPlugin extends Plugin {
* init() {
* // `listenTo()` and `editor` are available thanks to `Plugin`.
* // By using `listenTo()` you'll ensure that the listener will be removed when
* // the plugin is destroyed.
* this.listenTo( this.editor, 'dataReady', () => {
* // Do something when data is ready.
* } );
* }
* }
*
* @interface PluginInterface
*/

/**
* The third (and last) stage of plugin initialization. See also {@link #constructor} and {@link #init}.
*
* @returns {null|Promise}
*/
afterInit() {}
/**
* Creates a new plugin instance. This is the first step of a plugin initialization.
* See also {@link #init} and {@link #afterInit}.
*
* A plugin is always instantiated after its {@link module:core/plugin~PluginInterface.requires dependencies} and the
* {@link #init} and {@link #afterInit} methods are called in the same order.
*
* Usually, you'll want to put your plugin's initialization code in the {@link #init} method.
* The constructor can be understood as "before init" and used in special cases, just like
* {@link #afterInit} servers for the special "after init" scenarios (e.g. code which depends on other
* plugins, but which doesn't {@link module:core/plugin~PluginInterface.requires explicitly require} them).
*
* @method #constructor
* @param {module:core/editor/editor~Editor} editor
*/

/**
* Destroys the plugin.
*
* @returns {null|Promise}
*/
destroy() {}
}
/**
* An array of plugins required by this plugin.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* import Image from './image.js';
*
* export default class ImageCaption {
* static get requires() {
* return [ Image ];
* }
* }
*
* @static
* @readonly
* @member {Array.<Function>|undefined} module:core/plugin~PluginInterface.requires
*/

mix( Plugin, ObservableMixin );
/**
* Optional name of the plugin. If set, the plugin will be available in
* {@link module:core/plugincollection~PluginCollection#get} by its
* name and its constructor. If not, then only by its constructor.
*
* The name should reflect the package name + the plugin module name. E.g. `ckeditor5-image/src/image.js` plugin
* should be named `image/image`. If plugin is kept deeper in the directory structure, it's recommended to only use the module file name,
* not the whole path. So, e.g. a plugin defined in `ckeditor5-ui/src/notification/notification.js` file may be named `ui/notification`.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* export default class ImageCaption {
* static get pluginName() {
* return 'image/imagecaption';
* }
* }
*
* @static
* @readonly
* @member {String|undefined} module:core/plugin~PluginInterface.pluginName
*/

/**
* The second stage (after plugin {@link #constructor}) of plugin initialization.
* Unlike the plugin constructor this method can be asynchronous.
*
* A plugin's `init()` method is called after its {@link module:core/plugin~PluginInterface.requires dependencies} are initialized,
* so in the same order as constructors of these plugins.
*
* **Note:** This method is optional. A plugin instance does not need to have to have it defined.
*
* @method #init
* @returns {null|Promise}
*/

/**
* The third (and last) stage of plugin initialization. See also {@link #constructor} and {@link #init}.
*
* **Note:** This method is optional. A plugin instance does not need to have to have it defined.
*
* @method #afterInit
* @returns {null|Promise}
*/

/**
* Destroys the plugin.
*
* **Note:** This method is optional. A plugin instance does not need to have to have it defined.
*
* @method #destroy
* @returns {null|Promise}
*/
32 changes: 7 additions & 25 deletions src/plugincollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
* @module core/plugincollection
*/

import Plugin from './plugin';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import log from '@ckeditor/ckeditor5-utils/src/log';

Expand All @@ -21,7 +20,7 @@ export default class PluginCollection {
*
* @param {module:core/editor/editor~Editor} editor
* @param {Array.<Function>} [availablePlugins] Plugins (constructors) which the collection will be able to use
* when {@link module:core/plugin~PluginCollection#load} is used with plugin names (strings, instead of constructors).
* when {@link module:core/plugincollection~PluginCollection#load} is used with plugin names (strings, instead of constructors).
* Usually, the editor will pass its built-in plugins to the collection so they can later be
* used in `config.plugins` or `config.removePlugins` by names.
*/
Expand Down Expand Up @@ -69,8 +68,8 @@ export default class PluginCollection {
/**
* Gets the plugin instance by its constructor or name.
*
* @param {Function|String} key The plugin constructor or {@link module:core/plugin~Plugin.pluginName name}.
* @returns {module:core/plugin~Plugin}
* @param {Function|String} key The plugin constructor or {@link module:core/plugin~PluginInterface.pluginName name}.
* @returns {module:core/plugin~PluginInterface}
*/
get( key ) {
return this._plugins.get( key );
Expand All @@ -79,14 +78,14 @@ export default class PluginCollection {
/**
* Loads a set of plugins and adds them to the collection.
*
* @param {Array.<Function|String>} plugins An array of {@link module:core/plugin~Plugin plugin constructors}
* or {@link module:core/plugin~Plugin.pluginName plugin names}. The second option (names) work only if
* @param {Array.<Function|String>} plugins An array of {@link module:core/plugin~PluginInterface plugin constructors}
* or {@link module:core/plugin~PluginInterface.pluginName plugin names}. The second option (names) work only if
* `availablePlugins` were passed to the {@link #constructor}.
* @param {Array.<String|Function>} [removePlugins] Names of plugins or plugin constructors
* which should not be loaded (despite being specified in the `plugins` array).
* @returns {Promise} A promise which gets resolved once all plugins are loaded and available into the
* collection.
* @returns {Promise.<Array.<module:core/plugin~Plugin>>} returns.loadedPlugins The array of loaded plugins.
* @returns {Promise.<Array.<module:core/plugin~PluginInterface>>} returns.loadedPlugins The array of loaded plugins.
*/
load( plugins, removePlugins = [] ) {
const that = this;
Expand Down Expand Up @@ -150,8 +149,6 @@ export default class PluginCollection {
return new Promise( resolve => {
loading.add( PluginConstructor );

assertIsPlugin( PluginConstructor );

if ( PluginConstructor.requires ) {
PluginConstructor.requires.forEach( RequiredPluginConstructorOrName => {
const RequiredPluginConstructor = getPluginConstructor( RequiredPluginConstructorOrName );
Expand Down Expand Up @@ -191,21 +188,6 @@ export default class PluginCollection {
return that._availablePlugins.get( PluginConstructorOrName );
}

function assertIsPlugin( PluginConstructor ) {
if ( !( PluginConstructor.prototype instanceof Plugin ) ) {
/**
* The loaded plugin module is not an instance of {@link module:core/plugin~Plugin}.
*
* @error plugincollection-instance
* @param {*} plugin The constructor which is meant to be loaded as a plugin.
*/
throw new CKEditorError(
'plugincollection-instance: The loaded plugin module is not an instance of Plugin.',
{ plugin: PluginConstructor }
);
}
}

function getMissingPluginNames( plugins ) {
const missingPlugins = [];

Expand All @@ -230,7 +212,7 @@ export default class PluginCollection {
*
* @protected
* @param {Function} PluginConstructor The plugin constructor.
* @param {module:core/plugin~Plugin} plugin The instance of the plugin.
* @param {module:core/plugin~PluginInterface} plugin The instance of the plugin.
*/
_add( PluginConstructor, plugin ) {
this._plugins.set( PluginConstructor, plugin );
Expand Down
Loading

0 comments on commit f476f34

Please sign in to comment.