I’ve been meaning to document this for a while now, so here goes:
@stimulus/mutation-observers package provides a tower of classes for handling mutation events at various levels of abstraction. Each class in the tower wraps another observer class, acts as that observer’s delegate, performs some kind of filtering or indexing, and finally exposes the results through its own delegate interface.
ElementObserver class sits at the base of the tower. Construct it with a root DOM element and an object which implements the
ElementObserverDelegate interface. Each instance has its own DOM
MutationObserver configured to observe the root and its child elements. The methods of the delegate interface control which elements are matched (
matchElementsInTree()) and what to do in response (
ElementObserver instances have
start() methods which let you pause and resume matching (during performance-critical code, for example). After a call to
ElementObserver notifies its delegate of all matching elements which have changed since the last call to
On top of
AttributeObserver, which monitors a DOM tree for mutations to attributes with a given name. The
AttributeObserverDelegate interface allows delegates to respond when an attribute is matched or unmatched on an element, or when an already matched attribute’s value has changed.
AttributeObserver to monitor a DOM tree for changes to token list attributes. (A token list is an attribute whose value is a space-separated list of zero or more string tokens, such as the HTML
class attribute.) The TokenListObserverDelegate interface exposes Token objects through
tokenUnmatched() methods. Each token represents a unique string token belonging to an element and attribute at a particular index.
At the top of the tower is
ValueListObserverDelegate interface allows delegates to specify a
parseValueForToken() method which converts a token to an application-defined value type. Then the
elementUnmatchedValue() methods notify the delegate when a value appears in or disappears from the document.
@stimulus/core package has two observer classes of its own, each which sits atop
@stimulus/mutation-observers. The first,
data-action attributes and creates bindings from action descriptor tokens. The second,
data-controller attributes and creates scopes from identifier tokens. (More on the role of those classes below.)
Why do we organize the mutation observer classes this way? It turns out that loosely coupled filter classes are easy to conceive, rearrange, and discard as needs change. Parts of Stimulus were originally designed around a
SelectorObserver class, which is no longer present; an in-development branch adds a new
StringMapObserver class for watching changes to prefixed data attributes.
@stimulus/core package provides all the classes used to implement the Stimulus runtime environment.
At the top of the hierarchy is the
Application class, constructed with a root DOM element and a
Schema. The application creates and manages a router and a dispatcher. It also has methods for associating controller classes with identifiers, and for starting and stopping observation.
Router class creates a
ScopeObserver and acts as its delegate. The role of the router is to connect scopes to modules. A
Scope represents an element-identifier pair; a
Module represents a Stimulus controller class definition and all of its contexts, indexed by scope.
You can think of a
Context as the private backing store of a
Controller. There is a 1:1 mapping between instances of the two classes. Each context has a
BindingObserver, which watches the context’s scope for bindings that match the scope’s identifier. A
Binding represents a context-action pair.
The application’s shared
Dispatcher instance acts as the delegate for every binding observer in the application. The dispatcher installs and uninstalls DOM event listeners for bindings’ actions, and is responsible for invoking those actions in the order they appear in the
The purpose of the
Scope class is to query and filter the tree of elements relevant to a controller. As with contexts, there is a 1:1 mapping between controller and scope instances. Scopes know how to filter out subtrees of elements belonging to other scopes with the same identifier. They also provide target set and data map objects to the controller.
TargetSet class provides a
Set-like interface to a scope’s target elements. The
targets property on a controller points to its scope’s target set. Target properties declared in a controller’s
static targets array delegate to the target set’s
findAll() methods. Similarly, the
DataMap class provides a
Map-like interface to a scope’s namespaced data attributes.
I think this should cover most of the important classes in Stimulus as of version 1.1.1. Thanks for digging in, and please let me know if something above isn’t clear, or if you’re curious about anything else!