Core concepts
Every Plugster you write is built from the same three primitives: a view, one or more outlets, and a controller. This page defines each of them, the contract between them, and the file layout conventions that hold them together.
The view #
The view is an HTML region that carries a data-controller-name attribute. The attribute value is the PascalCase name of the controller class that owns that region:
<div data-controller-name="ExchangeRatesPlugster">
<!-- view content goes here -->
</div>
Any HTML element can act as a view — most of the time you will use a <div>, but the body or a form element work just as well. A single page can host as many views as it needs.
Views compose by nesting. When one view sits inside another, the inner view defines the boundary for its own controller — an outlet declared inside ChildPlugster is not reachable from ParentPlugster, and vice versa. Each view is its own world.
Outlets #
Inside a view, any element marked with a data-outlet-id attribute becomes an outlet — a named, framework-managed handle that the controller can address as a jQuery component. The attribute value is the outlet's identifier within the controller:
<div data-controller-name="ExchangeRatesPlugster">
<span data-outlet-id="selectedCurrencyLabel"></span>
<ul data-outlet-id="ratesList"></ul>
</div>
Inside the controller, those outlets are exposed as jQuery handles under the this._ namespace:
this._.selectedCurrencyLabel.text('EUR');
this._.ratesList.empty();
The this._ accessor is the framework's contract: it is the only sanctioned way for a controller to reach into its own DOM. Bypassing it with $() or document.querySelector works in the moment but couples the controller to CSS selectors, defeats the encapsulation that views provide, and breaks tests that render minimal HTML.
Outlet naming #
The conventions for outlet names:
-
camelCase —
submitButton, notSubmitButtonorsubmit_button. -
Full words —
submitButton, notsubmitBtn. -
No DOM-entity suffix —
submitButton, notsubmitButtonEl. The fact that an outlet is a DOM element is implied; naming it again is noise.
The root outlet #
In addition to the outlets you declare, every Plugster always exposes a special outlet called root. It is the jQuery handle to the element that carries the data-controller-name attribute — i.e. the view itself. Use it when you need to read view-level attributes (the common case is the active language carried on data-lang) or attach behavior to the view as a whole:
let activeLanguage = this._.root.data('lang');
The controller #
The controller is a JavaScript class that extends Plugster. Its job is to receive any external dependencies, declare the event signatures it publishes (when applicable), and wire DOM behavior on top of the outlets.
import {Plugster} from "https://cdn.jsdelivr.net/gh/paranoid-software/plugster@1.0.14/dist/plugster.min.js";
export class ExchangeRatesPlugster extends Plugster {
constructor(outlets, exchangeRatesSvcs) {
super(outlets);
this.exchangeRatesSvcs = exchangeRatesSvcs;
}
afterInit() {
let self = this;
self._.selectedCurrencyLabel.text('USD');
self._.ratesList.on('click', 'li', function () {
let key = $(this).data('key');
console.log(key);
});
}
}
Two things are worth flagging in this small example:
-
The constructor stores dependencies (here,
exchangeRatesSvcs) and forwards the outlets descriptor up to the base class. It does not touch the DOM — at this point the outlets have not been bound yet. -
All wiring lives in
afterInit(). That method is the framework's signal that outlets are bound and the controller can safely read or attach to them. The full set of rules aroundafterInitis covered in Lifecycle.
File conventions #
A controller lives in its own file, named after the class in kebab-case with the -plugster.js suffix:
exchange-rates-plugster.js → class ExchangeRatesPlugster extends Plugster
For Plugsters that are instantiated statically on page load — the common case — a companion boot file lives next to the class file:
exchange-rates-plugster-boot.js
The boot file is an anonymous async IIFE that instantiates the class, awaits init(), and registers the instance:
import {Plugster} from "https://cdn.jsdelivr.net/gh/paranoid-software/plugster@1.0.14/dist/plugster.min.js";
import {ExchangeRatesPlugster} from "./exchange-rates-plugster.js";
(async function () {
let exchangeRatesPlugster = await new ExchangeRatesPlugster({
selectedCurrencyLabel: {},
ratesList: {}
}).init();
Plugster.plug(exchangeRatesPlugster);
}());
Plugsters that are instantiated dynamically at runtime — created from inside another controller in response to an event, for example — do not have a boot file. The owning controller plays the role of the boot block.
Putting it together #
A working Plugster is the four pieces above, in one place: an HTML region with data-controller-name, one or more data-outlet-id children, a class file that extends Plugster, and a boot file that wires it on page load. Everything else the framework offers — events between Plugsters, list outlets, localization — is layered on top of these primitives.
The next page, Lifecycle, describes the exact sequence the framework runs through when those four pieces meet a real DOM.