Events
Plugsters do not call each other's methods. They publish events and react to events. The framework offers a small, deliberate API for this: every event a Plugster can emit is declared on the class itself, and subscriptions are wired either in HTML (the default) or in JavaScript (for Plugsters that come into existence at runtime). This page covers all three pieces — declaring, dispatching, and subscribing.
Declaring an event #
Every event a Plugster publishes must be declared with registerEventSignature. The declaration is a method whose name is the event name; inside it, the class hands the framework a reference to that name plus a slot for the event's payload shape and an optional callback. The declaration itself does not dispatch anything — it is the contract.
export class ExchangeRatesPlugster extends Plugster {
// ... constructor and afterInit ...
// Declares the 'currencyChanged' event the Plugster can publish
currencyChanged(data, callback) {
this.registerEventSignature(this.currencyChanged.name, data, callback);
}
}
The convention is mechanical and worth internalizing:
-
The method name is the event name. The event in the example above is
currencyChanged; nothing else. -
Always reference the name as
this.currencyChanged.name— the function's own.nameproperty — rather than hardcoding the string'currencyChanged'. Renaming the method then becomes a safe refactor; hardcoded strings drift. -
The
dataandcallbackparameters look like ordinary method arguments because they are — they receive the payload and an optional ack callback when the event is later consumed via the explicit subscription API.
Dispatching an event #
To fire a declared event, call this.dispatchEvent(name, payload). The payload is a plain object whose shape should match what the declared signature documents:
export class ExchangeRatesPlugster extends Plugster {
afterInit() {
let self = this;
self._.currencySelector.on('change', function () {
self._notifyCurrencyChange(this.value);
});
}
_notifyCurrencyChange(currencyCode) {
this.dispatchEvent(this.currencyChanged.name, { currencyCode });
}
currencyChanged(data, callback) {
this.registerEventSignature(this.currencyChanged.name, data, callback);
}
}
Two practical notes about dispatchEvent:
-
It is safe to dispatch from
afterInit()directly — for example, to signal "I am ready" right after binding. If subscribers have not been plugged in yet, the event is queued inPlugster.eventQueueand delivered as soon asPlugster.plug()finishes wiring. - Dispatching an event whose signature has not been declared is a silent contract violation. Subscribers receive nothing useful and the framework offers no warning. Always declare first.
Subscribing — HTML-declared (default) #
When both publisher and subscriber exist in the page HTML at load time — which is the typical case — subscriptions are wired declaratively from the subscriber's view using data-on-{publisher}-{event}. The attribute value is the name of the handler method on the subscribing class.
<div data-controller-name="RatesListPlugster"
data-on-exchangeratesplugster-currencychanged="handleCurrencyChange">
<ul data-outlet-id="ratesList"></ul>
</div>
export class RatesListPlugster extends Plugster {
afterInit() {
// No subscription code here — the wiring lives in the HTML attribute.
}
handleCurrencyChange(data) {
let currencyCode = data.args.currencyCode;
this._refreshList(currencyCode);
}
}
At the moment the page's last Plugster calls Plugster.plug(), the framework walks the registry, reads every data-on-* attribute on every plugged-in view, and connects each subscriber's handler to the matching publisher's event. The handler method MUST exist on the subscriber class — if it does not, the framework throws at wiring time. This is by design: a typo in the attribute or method name should fail loudly.
The payload arrives wrapped: the handler receives data, and the payload object is under data.args. The wrapping is consistent across both subscription modes.
Subscribing — explicit (listenTo)
#
Some Plugsters do not exist in the page HTML at load time — they are created programmatically in response to user actions, factory output, or route changes. For these, HTML-declared subscriptions are not possible (the subscriber does not know at authoring time which dynamic publishers will exist). The framework provides listenTo for this case.
export class DashboardPlugster extends Plugster {
async _mountVisualization(spec, data) {
let runtime = await this._visualizationFactory.mount({
mount: this._.vizContainer[0],
vizSpec: spec,
runResult: data
});
// Subscribe explicitly to events published by the runtime instance
this.listenTo(runtime.instance, runtime.instance.runParamsChanged);
this.listenTo(runtime.instance, runtime.instance.dimensionClicked);
return runtime;
}
// Single router for all explicit subscriptions
onNewMessage(publisherName, eventName, args) {
if (eventName === 'dimensionClicked') {
this._applyFilter(args.field, args.value);
}
if (eventName === 'runParamsChanged') {
this._reloadData(args);
}
}
}
Two things make this mode different from HTML-declared subscriptions:
-
The arguments to
listenToare references, not strings. Pass the publisher instance and a reference to its event-declaration method — the same method you would have wrapped withregisterEventSignatureon the publisher side. The framework readsmethod.nameinternally to match against the dispatched event. -
Delivery goes through
onNewMessage. Unlike HTML-declared subscriptions where each event has its own named handler, explicit subscriptions funnel into a singleonNewMessage(publisherName, eventName, args)method on the subscriber. The router pattern shown above (branching oneventName) is the canonical shape.
When a dynamic Plugster is removed and replaced — common in dashboard / factory patterns — call Plugster.unplug on the old instance before mounting the new one. unplug automatically removes the explicit subscriptions you set up against it. See Lifecycle.
Choosing a mode #
- Publisher and subscriber both exist in the page HTML at load time → HTML-declared. Almost everything.
-
Either side is created at runtime by JavaScript → explicit (
listenTo). Dashboards, factories, route-driven mounting. - Never mix the two for the same subscription relationship. Pick one and stay with it.
About the event queue #
Plugster's wiring is deliberate about ordering. A subscriber is only ready to receive events after both ends have been registered with Plugster.plug(). To handle the awkward window in between — where one Plugster is already live and dispatching while another is still finishing init() — the framework parks pending events in Plugster.eventQueue. As each subsequent plug() completes, the framework re-checks the queue and delivers anything that the new arrival was supposed to receive.
In practice you do not interact with the queue directly. Knowing it exists is enough to explain why dispatching from afterInit() is safe even when not every Plugster on the page has been plugged in yet.