mirror of
https://github.com/videojs/video.js.git
synced 2025-07-15 01:34:23 +02:00
feat: Add 'beforepluginsetup' event and named plugin setup events (e.g. 'pluginsetup:foo') (#4255)
This adds a beforepluginsetup event as well as beforepluginsetup:$name and pluginsetup:$name events. The drive behind this is improving the ability for people to make cross-plugin dependencies in a more robust manner.
This commit is contained in:
committed by
Gary Katsevman
parent
da1d8613d7
commit
0a19cf0d6a
@ -176,6 +176,14 @@ Like components, advanced plugins offer an implementation of events. This includ
|
||||
|
||||
By offering a built-in events system, advanced plugins offer a wider range of options for code structure with a pattern familiar to most web developers.
|
||||
|
||||
##### Extra Event Data
|
||||
|
||||
All events triggered by plugins include an additional data object as a second argument. This object has three properties:
|
||||
|
||||
- `name`: The name of the plugin (e.g. `"examplePlugin"`) as a string.
|
||||
- `plugin`: The plugin constructor (e.g. `ExamplePlugin`).
|
||||
- `instance`: The plugin constructor instance.
|
||||
|
||||
#### Statefulness
|
||||
|
||||
A new concept introduced for advanced plugins is _statefulness_. This is similar to React components' `state` property and `setState` method.
|
||||
@ -307,6 +315,19 @@ player.examplePlugin({customClass: 'example-class'});
|
||||
|
||||
These two methods are functionally identical - use whichever you prefer!
|
||||
|
||||
### Plugin Setup Events
|
||||
|
||||
Occasionally, a use-case arises where some code needs to wait for a plugin to be initialized. As of Video.js 6, this can be achieved by listening for `pluginsetup` events on the player.
|
||||
|
||||
For any given plugin initialization, there are four events to be aware of:
|
||||
|
||||
- `beforepluginsetup`: Triggered immediately before any plugin is initialized.
|
||||
- `beforepluginsetup:examplePlugin` Triggered immediately before the `examplePlugin` is initialized.
|
||||
- `pluginsetup`: Triggered after any plugin is initialized.
|
||||
- `pluginsetup:examplePlugin`: Triggered after he `examplePlugin` is initialized.
|
||||
|
||||
These events work for both basic and advanced plugins. They are triggered on the player and each includes an object of [extra event data](#extra-event-data) as a second argument to its listeners.
|
||||
|
||||
## References
|
||||
|
||||
* [Player API][api-player]
|
||||
|
@ -75,6 +75,27 @@ const markPluginAsActive = (player, name) => {
|
||||
player[PLUGIN_CACHE_KEY][name] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers a pair of plugin setup events.
|
||||
*
|
||||
* @private
|
||||
* @param {Player} player
|
||||
* A Video.js player instance.
|
||||
*
|
||||
* @param {Plugin~PluginEventHash} hash
|
||||
* A plugin event hash.
|
||||
*
|
||||
* @param {Boolean} [before]
|
||||
* If true, prefixes the event name with "before". In other words,
|
||||
* use this to trigger "beforepluginsetup" instead of "pluginsetup".
|
||||
*/
|
||||
const triggerSetupEvent = (player, hash, before) => {
|
||||
const eventName = (before ? 'before' : '') + 'pluginsetup';
|
||||
|
||||
player.trigger(eventName, hash);
|
||||
player.trigger(eventName + ':' + hash.name, hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a basic plugin function and returns a wrapper function which marks
|
||||
* on the player that the plugin has been activated.
|
||||
@ -91,15 +112,20 @@ const markPluginAsActive = (player, name) => {
|
||||
*/
|
||||
const createBasicPlugin = function(name, plugin) {
|
||||
const basicPluginWrapper = function() {
|
||||
|
||||
// We trigger the "beforepluginsetup" and "pluginsetup" events on the player
|
||||
// regardless, but we want the hash to be consistent with the hash provided
|
||||
// for advanced plugins.
|
||||
//
|
||||
// The only potentially counter-intuitive thing here is the `instance` in
|
||||
// the "pluginsetup" event is the value returned by the `plugin` function.
|
||||
triggerSetupEvent(this, {name, plugin, instance: null}, true);
|
||||
|
||||
const instance = plugin.apply(this, arguments);
|
||||
|
||||
markPluginAsActive(this, name);
|
||||
triggerSetupEvent(this, {name, plugin, instance});
|
||||
|
||||
// We trigger the "pluginsetup" event on the player regardless, but we want
|
||||
// the hash to be consistent with the hash provided for advanced plugins.
|
||||
// The only potentially counter-intuitive thing here is the `instance` is the
|
||||
// value returned by the `plugin` function.
|
||||
this.trigger('pluginsetup', {name, plugin, instance});
|
||||
return instance;
|
||||
};
|
||||
|
||||
@ -133,11 +159,15 @@ const createPluginFactory = (name, PluginSubClass) => {
|
||||
PluginSubClass.prototype.name = name;
|
||||
|
||||
return function(...args) {
|
||||
triggerSetupEvent(this, {name, plugin: PluginSubClass, instance: null}, true);
|
||||
|
||||
const instance = new PluginSubClass(...[this, ...args]);
|
||||
|
||||
// The plugin is replaced by a function that returns the current instance.
|
||||
this[name] = () => instance;
|
||||
|
||||
triggerSetupEvent(this, instance.getEventHash());
|
||||
|
||||
return instance;
|
||||
};
|
||||
};
|
||||
@ -147,7 +177,10 @@ const createPluginFactory = (name, PluginSubClass) => {
|
||||
*
|
||||
* @mixes module:evented~EventedMixin
|
||||
* @mixes module:stateful~StatefulMixin
|
||||
* @fires Player#beforepluginsetup
|
||||
* @fires Player#beforepluginsetup:$name
|
||||
* @fires Player#pluginsetup
|
||||
* @fires Player#pluginsetup:$name
|
||||
* @listens Player#dispose
|
||||
* @throws {Error}
|
||||
* If attempting to instantiate the base {@link Plugin} class
|
||||
@ -164,12 +197,12 @@ class Plugin {
|
||||
* A Video.js player instance.
|
||||
*/
|
||||
constructor(player) {
|
||||
this.player = player;
|
||||
|
||||
if (this.constructor === Plugin) {
|
||||
throw new Error('Plugin must be sub-classed; not directly instantiated.');
|
||||
}
|
||||
|
||||
this.player = player;
|
||||
|
||||
// Make this object evented, but remove the added `trigger` method so we
|
||||
// use the prototype version instead.
|
||||
evented(this);
|
||||
@ -184,7 +217,6 @@ class Plugin {
|
||||
|
||||
// If the player is disposed, dispose the plugin.
|
||||
player.on('dispose', this.dispose);
|
||||
player.trigger('pluginsetup', this.getEventHash());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -432,6 +464,21 @@ Player.prototype.hasPlugin = function(name) {
|
||||
|
||||
export default Plugin;
|
||||
|
||||
/**
|
||||
* Signals that a plugin is about to be set up on a player.
|
||||
*
|
||||
* @event Player#beforepluginsetup
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a plugin is about to be set up on a player - by name. The name
|
||||
* is the name of the plugin.
|
||||
*
|
||||
* @event Player#beforepluginsetup:$name
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a plugin has just been set up on a player.
|
||||
*
|
||||
@ -439,6 +486,14 @@ export default Plugin;
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a plugin has just been set up on a player - by name. The name
|
||||
* is the name of the plugin.
|
||||
*
|
||||
* @event Player#pluginsetup:$name
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Plugin~PluginEventHash
|
||||
*
|
||||
|
@ -67,24 +67,35 @@ QUnit.test('setup', function(assert) {
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('"pluginsetup" event', function(assert) {
|
||||
QUnit.test('all "pluginsetup" events', function(assert) {
|
||||
const setupSpy = sinon.spy();
|
||||
const events = [
|
||||
'beforepluginsetup',
|
||||
'beforepluginsetup:mock',
|
||||
'pluginsetup',
|
||||
'pluginsetup:mock'
|
||||
];
|
||||
|
||||
this.player.on('pluginsetup', setupSpy);
|
||||
this.player.on(events, setupSpy);
|
||||
|
||||
const instance = this.player.mock();
|
||||
const event = setupSpy.firstCall.args[0];
|
||||
const hash = setupSpy.firstCall.args[1];
|
||||
|
||||
assert.strictEqual(setupSpy.callCount, 1, 'the "pluginsetup" event was triggered');
|
||||
assert.strictEqual(event.type, 'pluginsetup', 'the event has the correct type');
|
||||
events.forEach((type, i) => {
|
||||
const event = setupSpy.getCall(i).args[0];
|
||||
const hash = setupSpy.getCall(i).args[1];
|
||||
|
||||
assert.strictEqual(event.type, type, `the "${type}" event was triggered`);
|
||||
assert.strictEqual(event.target, this.player.el_, 'the event has the correct target');
|
||||
|
||||
assert.deepEqual(hash, {
|
||||
name: 'mock',
|
||||
instance,
|
||||
|
||||
// The "before" events have a `null` instance and the others have the
|
||||
// return value of the plugin factory.
|
||||
instance: i < 2 ? null : instance,
|
||||
plugin: this.MockPlugin
|
||||
}, 'the event hash object is correct');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('defaultState static property is used to populate state', function(assert) {
|
||||
|
@ -47,23 +47,35 @@ QUnit.test('setup', function(assert) {
|
||||
assert.ok(this.player.hasPlugin('basic'), 'player has the plugin available');
|
||||
});
|
||||
|
||||
QUnit.test('"pluginsetup" event', function(assert) {
|
||||
QUnit.test('all "pluginsetup" events', function(assert) {
|
||||
const setupSpy = sinon.spy();
|
||||
const events = [
|
||||
'beforepluginsetup',
|
||||
'beforepluginsetup:basic',
|
||||
'pluginsetup',
|
||||
'pluginsetup:basic'
|
||||
];
|
||||
|
||||
this.player.on('pluginsetup', setupSpy);
|
||||
this.player.on(events, setupSpy);
|
||||
|
||||
const instance = this.player.basic();
|
||||
const event = setupSpy.firstCall.args[0];
|
||||
const hash = setupSpy.firstCall.args[1];
|
||||
|
||||
assert.strictEqual(setupSpy.callCount, 1, 'the "pluginsetup" event was triggered');
|
||||
assert.strictEqual(event.type, 'pluginsetup', 'the event has the correct type');
|
||||
events.forEach((type, i) => {
|
||||
const event = setupSpy.getCall(i).args[0];
|
||||
const hash = setupSpy.getCall(i).args[1];
|
||||
|
||||
assert.strictEqual(event.type, type, `the "${type}" event was triggered`);
|
||||
assert.strictEqual(event.target, this.player.el_, 'the event has the correct target');
|
||||
|
||||
assert.deepEqual(hash, {
|
||||
name: 'basic',
|
||||
instance,
|
||||
|
||||
// The "before" events have a `null` instance and the others have the
|
||||
// return value of the plugin factory.
|
||||
instance: i < 2 ? null : instance,
|
||||
plugin: this.basic
|
||||
}, 'the event hash object is correct');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('properties are copied', function(assert) {
|
||||
|
Reference in New Issue
Block a user