Skip to Main Content
Player Logo
PlayerPlugins

Plugin Implementation

The main purpose of a Plugin is to extend or add new functionality by tapping into Player’s pipeline of components via hooks. In this section we’ll go over the steps to implement a plugin.
We’ll use the stage-revert-data plugin as example. After creating our plugin class, we’ll use the apply method which provides access to the Player instance, which then gives access to the necessary hooks.
export default class StageRevertDataPlugin implements PlayerPlugin {
  name = 'stage-revert-data-plugin';

  apply(player: Player) {
    let dataController: DataController;
    let stageData: String;
    let commitTransitions: String[];
    let commitShadowModel: Boolean = false;

    const GatedDataMiddleware = new ValidationMiddleware(
      () =>
        commitShadowModel
          ? undefined
          : {
              message: 'staging data',
              severity: 'error',
            },
      { shouldIncludeInvalid: () => true }
    );
For this case we needed to define variables that will store references for the scope the hooks will share:
  • dataController: Reference to the data controller hook in the Player instance, can be used to read, update or commit new changes to the data model
  • stageData: View attribute that comes from the view state, used for enabling the staging of data
  • commitTransitions: The list of view names which the shadow model should be committed if transitioned to. Comes from the view state attribute as well.
  • commitShadowModel: Flag that enables committing shadow model into data model after the transition, only if stageData is set to true and the next target view name is included in commitTransitions.
  • GatedDataMiddleware Instance from ValidationMiddleware to be used as a middleware to intercept the data-model pipeline before any data is committed and cache the data instead, only if stageData is set to true.
The next step is to tap into the necessary Player hooks. First we tap into the viewController which we can then intercept the resolveView hook. Player hooks are implemented with the Tapable package, so we can use the interception API’s call method, this triggers everytime the hook is triggered, then we get access to the current view state and read the stageData and commitTransitions attributes.
player.hooks.viewController.tap(this.name, (vc) => {
      vc.hooks.resolveView.intercept({
        call: (view, id, state) => {
          stageData = state?.attributes?.stageData;
          commitTransitions = state?.attributes?.commitTransitions;
        },
      });
    });
Note: notice how each time we tap into a hook, we use the this.name property as the name of the plugin. This is important to avoid conflicts with other plugins.
Next we tap into the dataController, so we can scope the data controller instance for future use. Then we tap into the resolveDataStages plugin in this data controller instance. If the stage property is set to true, we add our GatedDataMiddleware to the data pipeline. If not, we return the data pipeline as is.
player.hooks.dataController.tap(this.name, (dc: DataController) => {
      dataController = dc;

      dc.hooks.resolveDataStages.tap(this.name, (dataPipeline) => {
        return stageData
          ? [...dataPipeline, GatedDataMiddleware]
          : [...dataPipeline];
      });
    });
Finally, we tap into the flowController so we can intercept the flow hook. We then tap into the transition hook, which is called every time the player transitions from one view to another. If the commitTransitions includes the next view name, we set the commitShadowModel flag to true, and commit the data stored in the shadow model through GatedDataMiddleware into the data model. Whether the data was committed from the shadow model or not, we clear the shadow model paths in the GatedDataMiddleware instance and set the commitShadowModel flag to false as final steps.
player.hooks.flowController.tap(this.name, (flowController) => {
      flowController.hooks.flow.tap(this.name, (flow) => {
        flow.hooks.transition.tap(this.name, (from, to) => {
          if (from) {
            if (commitTransitions.includes(to.name)) {
              commitShadowModel = true;
              player.logger.debug(
                'Shadow Model Data to be committed %s',
                GatedDataMiddleware.shadowModelPaths
              );
              dataController.set(GatedDataMiddleware.shadowModelPaths);
            }

            commitShadowModel = false;
            GatedDataMiddleware.shadowModelPaths.clear();
          }
        });
      });
    });
And this is how we implement a plugin that manages the staging of data based on the view state attributes.
Code Snippets Reference: StageRevertDataPlugin