Conceptos básicos
Cada Plugster que escribes se construye sobre las mismas tres primitivas: una vista, uno o más outlets, y un controlador. Esta página define cada una de ellas, el contrato entre ellas, y las convenciones de organización de archivos que las mantienen juntas.
La vista #
La vista es una región HTML que lleva un atributo data-controller-name. El valor del atributo es el nombre PascalCase de la clase controladora que posee esa región:
<div data-controller-name="ExchangeRatesPlugster">
<!-- el contenido de la vista va aquí -->
</div>
Cualquier elemento HTML puede actuar como vista — la mayoría del tiempo usarás un <div>, pero el body o un elemento de formulario funcionan igual de bien. Una sola página puede albergar tantas vistas como necesite.
Las vistas componen por anidamiento. Cuando una vista vive dentro de otra, la vista interna define el límite para su propio controlador — un outlet declarado dentro de ChildPlugster no es alcanzable desde ParentPlugster, y viceversa. Cada vista es su propio mundo.
Outlets #
Dentro de una vista, cualquier elemento marcado con un atributo data-outlet-id se convierte en un outlet — un handle con nombre, gestionado por el framework, que el controlador puede direccionar como un componente jQuery. El valor del atributo es el identificador del outlet dentro del controlador:
<div data-controller-name="ExchangeRatesPlugster">
<span data-outlet-id="selectedCurrencyLabel"></span>
<ul data-outlet-id="ratesList"></ul>
</div>
Dentro del controlador, esos outlets se exponen como handles de jQuery bajo el namespace this._:
this._.selectedCurrencyLabel.text('EUR');
this._.ratesList.empty();
El accesor this._ es el contrato del framework: es la única forma sancionada para que un controlador alcance su propio DOM. Saltearse esto con $() o document.querySelector funciona en el momento pero acopla el controlador a selectores CSS, deshace la encapsulación que las vistas proveen, y rompe tests que renderizan HTML mínimo.
Nombres de outlets #
Las convenciones para los nombres de outlets:
-
camelCase —
submitButton, noSubmitButtonnisubmit_button. -
Palabras completas —
submitButton, nosubmitBtn. -
Sin sufijo de entidad DOM —
submitButton, nosubmitButtonEl. El hecho de que un outlet sea un elemento DOM está implícito; nombrarlo de nuevo es ruido.
El outlet root
#
Además de los outlets que declaras, cada Plugster siempre expone un outlet especial llamado root. Es el handle de jQuery al elemento que lleva el atributo data-controller-name — es decir, la vista en sí. Úsalo cuando necesites leer atributos del nivel de la vista (el caso común es el idioma activo cargado en data-lang) o adjuntar comportamiento a la vista como un todo:
let activeLanguage = this._.root.data('lang');
El controlador #
El controlador es una clase JavaScript que extiende Plugster. Su trabajo es recibir cualquier dependencia externa, declarar las firmas de eventos que publica (cuando aplica), y cablear el comportamiento del DOM sobre los 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);
});
}
}
Dos cosas vale la pena destacar en este pequeño ejemplo:
-
El constructor guarda dependencias (aquí,
exchangeRatesSvcs) y reenvía el descriptor de outlets a la clase base. No toca el DOM — en este punto los outlets aún no se han enlazado. -
Todo el cableado vive en
afterInit(). Ese método es la señal del framework de que los outlets están enlazados y el controlador puede leer o adjuntarse a ellos con seguridad. El conjunto completo de reglas alrededor deafterInitse cubre en Ciclo de vida.
Convenciones de archivos #
Un controlador vive en su propio archivo, nombrado según la clase en kebab-case con el sufijo -plugster.js:
exchange-rates-plugster.js → class ExchangeRatesPlugster extends Plugster
Para Plugsters que se instancian estáticamente al cargar la página — el caso común — un archivo de arranque acompañante vive junto al archivo de la clase:
exchange-rates-plugster-boot.js
El archivo de arranque es una IIFE async anónima que instancia la clase, hace await al init(), y registra la instancia:
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);
}());
Los Plugsters que se instancian dinámicamente en tiempo de ejecución — creados desde dentro de otro controlador en respuesta a un evento, por ejemplo — no tienen archivo de arranque. El controlador dueño cumple el rol del bloque de arranque.
Todo junto #
Un Plugster funcionando son las cuatro piezas de arriba, en un mismo lugar: una región HTML con data-controller-name, uno o más hijos con data-outlet-id, un archivo de clase que extiende Plugster, y un archivo de arranque que lo cablea al cargar la página. Todo lo demás que el framework ofrece — eventos entre Plugsters, outlets de lista, localización — se construye sobre estas primitivas.
La siguiente página, Ciclo de vida, describe la secuencia exacta que el framework recorre cuando esas cuatro piezas se encuentran con un DOM real.