Plugins API

The Plugins API

Why do we need plugins? #

The Global State #

The core architecture of ActiveUI is based on a global state, which is a single plain JavaScript object containing all the information needed to render the page. Building an application with such an architecture has a lot of theoretical benefits:

  • Undo/redo history: undo/redo is easily implemented as reloading a previous state object.
  • Save & restore application state: it is possible to reload the application in the same state you left it because the state cab be stored in the browser persisted storage.
  • Dynamic localization/theming: it enables re rendering of the application with a different locale and theme, without having to reload the page.
  • Collaboration: pieces of state can be synchronized through a remote server in order to “screen share” and collaborate with someone else’s session on a specific widget.
  • Bookmarking: each widget become bookmarkable as they are represent a single plain JavaScript object that can be serialized in JSON.
  • Bug reproduction: if something went wrong with the application, the global state can be dumped and attached to the issue along with a screenshot in order to understand and reproduce the issue; ie to reproduce a bug the state is sufficient. No need to explain the steps that led to this situation.
  • Advanced tweaking: it is possible to directly write into the widget’s state to update some parameters that have for instance no corresponding UI yet.

The issue #

So this large plain JavaScript object is convenient because we have a human readable JSON that completely describes the state of the application. Now this has an obvious implication: every piece of information we need to render must be storable in this plain JavaScript object and therefore must be serializable. So it has to be a tree of primitive types like booleans, strings, numbers, arrays, or hashs. This excludes one main type in JavaScript: functions. Functions cannot be converted to a serializable representation because they are more than the string representation of their body: they often refer to external variables (called closures).

An example #

This is painful for us because we are writing a library, with a lot of entry-points to let users customize what happens when a given event arises. So a project would like to override for instance what happens when a user double clicks on a cell of a Tabular View. The natural way of describing this would be to give a function to our Tabular View configuration and we would execute this function when double clicking on a cell. Sadly we can’t do that because when we display the Tabular View the only source of information we can read from is the global state which cannot contain functions.

The solution #

This is why we introduced the concept of Plugins. The principle is that, in your project code, you can register objects with methods by giving them a key, and then these keys can be used inside the global state to refer to the function. This has a few impact regarding the list of benefits of the global state:

  • Collaboration: will work only if both users are using the same project code. Otherwise they might have different implementations for the same plugin key which will lead to inconsistencies.
  • Bug reproduction: we might need to have the code of the plugins in use in the project in addition to the global state if they are referred in a place related to the bug.

As opposed to ActivePivot Server, plugins and serialization are really close concepts. We created plugins to be able to serialize functions. So creating a plugin is similar to defining an object with methods and indicate how it should be serialized.

How to implement a plugin #

In order to register your own plugin implementation, use:

activeUI.plugins.register({
  type: 'cell-editor',
  key: 'my-cell-editor',
  createProperties(parameters) {
    return {
      myProperty(cell) {
        // do stuff
      }
    };
  },
  staticProperties: {
    myStaticProperty: true
  }
});

The register function takes only one argument and it is of type PluginDescription.

The couple (type, key) should be unique among your project otherwise some plugins will override others.

If you deliberately want to override the implementation of a core plugin use activeUI.plugins.override.

How to use a plugin #

The plugins are referenced within the configuration of a widget, for example a tabular view/pivot table. Within that configuration, a plugin can be referenced in two ways: as a string or as an object.

When no arguments are required, there are three equivalent ways of defining a plugin:

// Defining 'my-cell-editor' cellEditor plugin as a string
const tabularViewConfiguration = {
  cellEditors: ['my-cell-editor']
};

// Defining 'my-cell-editor' cellEditor plugin as an object with no parameters
const tabularViewConfiguration = {
  cellEditors: [
    {
      key: 'my-cell-editor'
    }
  ]
};

// Defining 'my-cell-editor' cellEditor plugin as an object with an empty args parameters
const tabularViewConfiguration = {
  cellEditors: [
    {
      key: 'my-cell-editor',
      args: {}
    }
  ]
};

When arguments are provided/required and part of the plugin definition, the plugin is defined as:

// Defining 'my-cell-editor' cellEditor plugin as an object with args parameters
const tabularViewConfiguration = {
  cellEditors: [
    {
      key: 'my-cell-editor',
      args: {arg1: 'myArgument1', arg2: 42}
    }
  ]
};

Note: The two ways are always valid, but only the second one allows to send parameters to the plugin.

List of Public Plugins #

The list of public plugins is available here.