Module development

The basics

An Annotator module is a function that can be passed to include() in order to extend the functionality of an Annotator application.

The simplest possible Annotator module looks like this:

function myModule() {
    return {};
}

This clearly won’t do very much, but we can include it in an application:

app.include(myModule);

If we want to do something more interesting, we have to provide some module functionality. There are two ways of doing this:

  1. module hooks
  2. component registration

Use module hooks unless you are replacing core functionality of Annotator. Module hooks are functions that will be run by the App when important things happen. For example, here’s a module that will say Hello, world! to the user when the application starts:

function helloWorld() {
    return {
        start: function (app) {
            app.notify("Hello, world!");
        }
    };
}

Just as before, we can include it in an application using include():

app.include(helloWorld);

Now, when you run app.start();, this module will send a notification with the words Hello, world!.

Or, here’s another example that uses the HTML5 Audio API to play a sound every time a new annotation is made [2]:

function fanfare(options) {
    options = options || {};
    options.url = options.url || 'trumpets.mp3';

    return {
        annotationCreated: function (annotation) {
            var audio = new Audio(options.url);
            audio.play();
        }
    };
}

Here we’ve added an options argument to the module function so we can configure the module when it’s included in our application:

app.include(fanfare, {
    url: "brass_band.wav"
});

You may have noticed that the annotationCreated() module hook function here receives one argument, annotation. Similarly, the start() module hook function in the previous example receives an app argument. A complete reference of arguments and hooks is covered in the Module hooks section.

Loading custom modules

When you write a custom module, you’ll end up with a JavaScript function that you need to reference when you build your application. In the examples above we’ve just defined a function and then used it straight away. This is probably fine for small examples, but when things get a bit more complicated you might want to put your modules in a namespace.

For example, if you were working on an application for annotating Shakespeare’s plays, you might put all your modules in a namespace called shakespeare:

var shakespeare = {};
shakespeare.fanfare = function fanfare(options) {
    ...
};
shakespeare.addSceneData = function addSceneData(options) {
    ...
};

You get the idea. You can now include() these modules directly from the namespace:

app.include(shakespeare.fanfare, {
    url: "elizabethan_sackbuts.mp3"
});
app.include(shakespeare.addSceneData);

Module hooks

Hooks are called by the application in order to delegate work to registered modules. This is a list of module hooks, when they are called, and what arguments they receive.

It is possible to add your own hooks to your application by invoking the runHook() method on the application instance. The return value is a Promise that resolves to an Array of the results of the functions registered for that hook (the order of which is undefined).

Hook functions may return a value or a Promise. The latter is sometimes useful for delaying actions. For example, you may wish to return a Promise from the beforeAnnotationCreated hook when an asynchronous task must complete before the annotation data can be saved.

configure(registry)

Called when the plugin is included. If you are going to register components with the registry, you should do so in the configure module hook.

Parameters:registry (Registry) – The application registry.
start(app)

Called when start() is called.

Parameters:app (App) – The configured application.
destroy()

Called when destroy() is called. If your module needs to do any cleanup, such as unbinding events or disposing of elements injected into the DOM, it should do so in the destroy hook.

annotationsLoaded(annotations)

Called with annotations retrieved from storage using load().

Parameters:annotations (Array[Object]) – The annotation objects loaded.
beforeAnnotationCreated(annotation)

Called immediately before an annotation is created. Modules may use this hook to modify the annotation before it is saved.

Parameters:annotation (Object) – The annotation object.
annotationCreated(annotation)

Called when a new annotation is created.

Parameters:annotation (Object) – The annotation object.
beforeAnnotationUpdated(annotation)

Called immediately before an annotation is updated. Modules may use this hook to modify the annotation before it is saved.

Parameters:annotation (Object) – The annotation object.
annotationUpdated(annotation)

Called when an annotation is updated.

Parameters:annotation (Object) – The annotation object.
beforeAnnotationDeleted(annotation)

Called immediately before an annotation is deleted. Use if you need to conditionally cancel deletion, for example.

Parameters:annotation (Object) – The annotation object.
annotationDeleted(annotation)

Called when an annotation is deleted.

Parameters:annotation (Object) – The annotation object.

Footnotes

[2]Yes, this might be quite annoying. Probably not an example to copy wholesale into your real application...