openannotation/annotator — pull request #367 by nickstenning (2014-04-21 21:34:41+00:00)
Title: Extract Annotator user interface into independent plugins

This is a large pull request which addresses the single most-cited concern about Annotator: namely, that the default user interface was too tightly coupled to the core annotation lifecycle management and persistence layer.

The four main components of the default user interface are as follows:

1. The Adder, which is responsible for detecting document selections and presenting a button which the user can click to indicate a wish to create an annotation.
2. The Viewer, which can display an annotation in a tooltip-like component.
3. The Editor, which allows for editing of annotations in a similar component.
4. The Highlighter, which draws highlights in the document where annotations have been created.

I've extracted each of these four components into their own plugins, which ship with Annotator core, but which can be trivially disabled or reconfigured. Specifically, a "legacy instantiation" of Annotator (`new Annotator(element, options)`) will set up and configure the plugins to work together, as they have always done ([see here](https://github.com/openannotation/annotator/blob/6922bba2206060f85719a7c89787394ed3853d44/src/annotator.coffee#L87-L94)), but creating an `Annotator.Factory` manually allows complete customisation.

Here are a few examples of what is now trivially possible:

1. Create a "highlight-only" Annotator, in which clicking the adder instantly highlights the selected text, without prompting for a comment:

```javascript
var elem = document.body;
var annotator = new Annotator.Factory()
.addPlugin(Adder, elem)
.addPlugin(Highlighter, elem)
.getInstance();
annotator.attach(elem);
```

1. Create a read-only Annotator which displays annotations but does not let the user create their own:

```javascript
var elem = document.body;
var factory = new Annotator.Factory()
.addPlugin(Highlighter, elem)
.addPlugin(Viewer, elem)
.getInstance();
annotator.attach(elem);
```

1. Create an Annotator which will load and draw annotations everywhere in the document body, but which will only permit annotations to be added within two specific elements:

```javascript
var elem = document.body;
var zone1 = document.querySelector("#zone1");
var zone2 = document.querySelector("#zone2");
var factory = new Annotator.Factory()
.addPlugin(Adder, zone1)
.addPlugin(Adder, zone2)
.addPlugin(Highlighter, elem)
.addPlugin(Viewer, elem)
.addPlugin(Editor)
.getInstance();
annotator.attach(elem);
```

While the plugins do interact with one another, the places where they do so have been greatly simplified, and can be summarised as falling into one of the following three kinds:

1. Interaction through the annotation lifecycle. For example, the Editor will pop up when the Adder is clicked to create a new annotation, but this is not achieved by any direct communication between the Adder and the Editor. Instead, the Adder simply calls `annotator.annotations.create({...})`, and the Editor, which listens to `beforeAnnotationCreated`, shows itself to allow the annotation to be edited before it is passed back to the storage layer. In order to make this possible, event callbacks such as those passed to `annotator.on(...)` can now (optionally) return promises. The `AnnotationRegistry` requires that all promises returned by `beforeAnnotationXXX` event callbacks have resolved before it proceeds to call the corresponding create/update/delete method on the store plugin. If any of the promises is rejected, the operation is aborted and the store plugin is not notified.
2. Interaction through API extension of the core Annotator instance. For example, the Adder and the Highlighter will set `annotator.interactionPoint` to indicate the position within the document where the user has performed some action (such as making a selection or hovering over a highlight). The Editor and Viewer then check to see if this `interactionPoint` has been set, and if so use it to position themselves appropriately.
3. Interaction through DOM event listeners. The poster-child for this is the Viewer plugin, which by default will listen to `mouseover` events within its element, and will show itself if it detects a user mousing over a Highlighter-drawn highlight.

In short, the four plugins are all coupled to one another loosely enough that they can be used entirely independently of the other three.

There are still numerous issues to clear up: in particular, how the Tags, Permissions and other plugins which use the Viewer and Editor `addField(...)` methods should communicate with these components now they are plugins, but I don't think that should stop this from merging at this stage. We have much better tests of Annotator's behaviour on this branch than we have ever had before.

Any review (either by core contributors or lurking users) would be greatly appreciated.