Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Plugin refactoring #444

Closed
wants to merge 38 commits into from
Closed

[WIP] Plugin refactoring #444

wants to merge 38 commits into from

Conversation

nickstenning
Copy link
Member

This is another big upset to the organisation of Annotator's internals (sorry!), but one with significant benefits. The important things that change in this PR are:

  • The Annotator object constructs the same old Annotator it has always done, complete with default UI -- but instead of being a mess, it is now a simple wrapper over:
    • AnnotatorCore -- the class responsible for plugin and storage registration, which has a runHook method to run hooks on all plugins (if the associated callbacks exist)
    • the DefaultUI plugin -- where all of the default UI components are instantiated and wired together
  • plugins become simple functions of one argument
    • the one argument plugins accept is a registry object
    • at the moment, the only thing that lives on the registry object is an annotations property, that provides the ability to perform annotation CRUD operations to plugins
    • plugin functions return a "plugin object", which can define function properties which are called by the core for specific events, such as onAnnotationCreated.
  • all UI components have been decoupled from their respective plugins, and some don't even have plugins, because it's not actually useful to instantiate the component as a plugin without explicit configuration. These now live in the Annotator.UI namespace (src/ui/).
  • all Storage components have been moved into the Annotator.Storage namespace. These are not plugins, they are "storages"
    • side note: what was called "AnnotationRegistry" is now "StorageAdapter", since @tilgovi pointed out that it wasn't a registry in any meaningful sense

If you're looking for a place to start when reviewing this code, I suggest a quick glance at src/annotator.coffee, followed by a look at how the default UI is wired together in src/plugins/defaultui.coffee.

This is WIP until I've finished messing with the other plugins (Auth, Markdown, Tags, Filter, etc. etc.)

@@ -321,33 +86,26 @@ if not g.Node?
DOCUMENT_FRAGMENT_NODE: 11
NOTATION_NODE: 12

# Create namespace object for core-provided plugins
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in ./core?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. This is the browser entry point file. Even if you only want AnnotatorCore you're going to refer to it as Annotator.Core.AnnotatorCore so I'd rather keep the Core module side-effect free.

@tilgovi
Copy link
Member

tilgovi commented Sep 25, 2014

It's hard to find anything not to like about this, @nickstenning. Great work!

The default UI plugin turned out very much like what I was thinking about back in my gist but I like it even more with the Annotator.UI namespace and the separation of AnnotatorCore and Annotator.

:shipit:

Here's another attempt (or start of one) to improve on the plugin
architecture of Annotator. I've stripped away a lot of complexity,
including:

- Factory (gone completely)
- Annotator.Plugin.register (Annotator.Plugin is just a plain old object
  again)

But the most important change is the addition of src/core.coffee, which
contains AnnotatorCore. AnnotatorCore is:

- where plugins and storage implementations are registered
- responsible for propagating hook actions ('onAnnotationCreated') to
  plugin instances
- able to tear down an Annotator instance

That's all at the moment, but that leaves the Annotator class itself to
be responsible *only* for setting up a "default Annotator application"
-- the thing that most users have been using throughout the 1.2.x
series.

I've commented out a bunch of plugin tests -- the ones that still rely
on nasty direct inter-plugin dependencies.
This is not a registry of any description. Rather, it is an interface
adapter which wraps a concrete storage plugin implementation.

This commit also bulks up the tests for this component quite
substantially.
Storage plugins can now return values from their methods, so NullStore
doesn't need to mess with Deferreds.
Moved TextSelector and Adder into a UI module, as they are not "plugins"
in any meaningful sense (they do not respond to Annotator lifecycle
events).
This commit shows the way for a couple of new patterns.

First, the way that the DefaultUI plugin is responsible for composing
the new UI components should be beginning to be clear.

Second, the division of the Highlighter into a domain-specific
Highlighter component in the UI module, as well as a thin wrapper plugin
that can, if needed, be used on its own.
Just as with the Highlighter in a13e72d, this commit moves the bulk of
the Editor functionality into the UI module. There remains a skeletal
Editor plugin, which admittedly isn't terribly useful on its own, but it
is included for completeness.

As part of this work, the #load() method of the Editor was refactored to
return a promise (resolving if the editor is submitted, rejecting if
cancelled) rather than having a "private" event handler on the Editor
itself.
Moves the bulk of the Viewer functionality into a decoupled object that
lives in the UI module. The Viewer plugin becomes a thin wrapper that
wires up aspects of the viewer's behaviour to the relevant registry
functions.
This commit further excises unnecessary couplings from the Adder:

- an Adder no longer needs to know about the internal structure of an
  annotation (such as how many ranges are in it), thus allowing it to be
  used in more contexts (image, video annotation, etc.)

- an Adder no longer knows the internals of the registry: instead an
  explicit onCreate handler can be passed to the constructor to indicate
  what should happen when the adder is clicked while an annotation is
  loaded
This makes it possible to adjust the callback function after
construction.
For consistency with TextSelector#onSelection, we pass the DOM event to
the Adder#onCreate handler.
This cleans up the DefaultUI plugin to take advantage of the preceding
commits further decoupling the UI components from one another and from
Annotator.
This plugin has been replaced with 2 or 3 lines in the DefaultUI plugin.
Move the nasty z-index futzing into the DefaultUI plugin.
Convert the Document plugin to a new-style plugin. This makes the plugin
a lot simpler, and while here I've taken the liberty of "purifying" the
various metadata-extraction functions so they don't all mutate one
enormous object.

In addition, it appears that previously all annotations (before a cycle
through the store plugin) shared a reference to one global metadata
object. This is now fixed -- the metadata object is copied before being
added to an annotation.
Move all storage-related components into src/storage.coffee
The functionality provided by the events library (namely the triggerThen
function) has been superceded by the rather simpler
AnnotatorCore#runHook().
nickstenning and others added 17 commits September 30, 2014 12:47
This is not a great idea. It needs to be replaced with a polyfilling
build of Annotator that can be loaded by the bookmarklet if it detects
missing functionality.
This makes the notifier a new kind of component like storage, that can
be set at startup. The interface of a notification component is very
similar to that of HTML5 Notification.

The default Annotator is now instantiated with the old ("banner")
notification component enabled.
And, for simplicity, use runHook to run the onDestroy handlers.
Moves the Filter component into its own object in the UI module. It's
still implicitly coupled to the Highlighter, but doesn't access
"this.annotator" any more.
Delegator is really not doing an awful lot here, and if anything hinders
readability. I've replaced the use of Delegator with direct calls to
jQuery's event delegation method (".on()"), using an event namespace to
simplify event unbinding.

While it looks like I'm cheating by removing the tests for this event
binding, these kind of tests are ridiculous anyway -- they're testing
the implementation of the Filter plugin, and not its unit behaviour.
They won't be missed.
Just as with the previous commit, there's very little gained by using
Delegator here any more.
Oh Delegator, you have served us well for many years, but now it is time
for you to die.

*2 minutes silence*
There's no need for one-line event handlers.
These handlers (well, really only the mouseup handler) were returning
false, thus stopping propagation of the events and breaking other
handlers, as discussed in #431.

Also incorporating a change in the button check, from

    e.which != 1

to

    e.which > 1

which seems to be necessary for IE8 support (see #436).

Fixes #431.
@nickstenning
Copy link
Member Author

Merged to master and closed. This was getting unwieldy.

@nickstenning nickstenning deleted the plugin-refactoring branch September 30, 2014 10:56
@tilgovi
Copy link
Member

tilgovi commented Sep 30, 2014

\o/
On Sep 30, 2014 3:50 AM, "Nick Stenning" notifications@github.com wrote:

Merged to master and closed. This was getting unwieldy.


Reply to this email directly or view it on GitHub
#444 (comment)
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants