# Marionette 5.0.0 documentation bundle
Version: 5.0.0
Channel: latest
Publication: stable release (published on npm)
Source revision: 0f2284ad4bbfe8c82ce82714471697a68c2acaeb
Local changes: false
Content SHA-256: 44c0d535fd36d11f31d909246f4ee7c6592eae3f25fb7513c61af65842593a90
Read individual documents for focused tasks. Each section below contains the exact published reading Markdown, including its source identity.
---
Document: docs/api/runtime.md
Canonical URL: https://marionettejs.com/docs/api/runtime/
Markdown URL: https://marionettejs.com/docs/api/runtime.md
Reading SHA-256: 1b655ba7127c2dabbe9cc91618e0ede13589f03a6c5fafc686c07f36c6d39025
# Runtime configuration
[API index](/docs/api.md) · [Rendering and DOM providers](/docs/api/providers/dom.md) · [Data and state providers](/docs/api/providers/data.md)
A Marionette runtime is a family of classes with shared configuration choices and a Radio instance. The named imports from `marionette` use the default family. Configure that family once for an ordinary application; use `createMarionette()` when independently configured parts or tests must coexist. An [Application](/docs/api/application.md) manages a feature lifetime within either family.
## Configure the default runtime
Import the named setters in your application's setup module, before constructing Views. For example, install the Lit DOM adapter once:
```js
import { View, setDomApi } from 'marionette';
import LitDomApi from '@mnjs/adapters/dom/lit-html';
import { html } from 'lit-html';
setDomApi(LitDomApi);
const Heading = View.extend({
template: () => html`
Overview
`,
});
const heading = new Heading().render();
```
The default renderer already calls the template function with its data. Configure a different renderer only when the template engine needs one. The [quick start](/docs/quick-start.md) mounts a View in the page; [setup](/docs/integrations/setup.md) adds observable data when needed.
## Configure an isolated runtime
`createMarionette()` takes no arguments and returns a new runtime. Configure it, then define classes from its constructors. Imports of adapters alone do not install them.
```ts
import { createMarionette } from 'marionette';
import { Model, DataApi } from '@mnjs/data';
import LitDomApi from '@mnjs/adapters/dom/lit-html';
import { html } from 'lit-html';
const runtime = createMarionette();
runtime.setDataApi(DataApi);
runtime.setDomApi(LitDomApi);
const Heading = runtime.View.extend({
template: ({ title }: { title: string }) => html`
${title}
`,
modelEvents: { 'change:title': 'render' },
});
const title = new Model({ title: 'Overview' });
const heading = new Heading({ model: title });
heading.render();
title.set('title', 'Updated overview');
```
The default renderer evaluates the function; Lit inserts its result. DataApi supplies the Model's attributes and subscriptions. Configure StateApi separately if an owner's `stateEvents` or owned-state disposal needs it. The View borrows the Model; its owner manages the Model's lifetime. See [observable data and API access](/docs/integrations/setup.md#observable-data-and-api-access) for the boundary between observation and persistence.
## Choose a configuration scope
| Scope | Effect and return value |
| --- | --- |
| Named `setDomApi`, `setDataApi`, `setStateApi`, `setRenderer`, `setEventDelegator` imports | Configure the affected default classes. Return `undefined`. |
| The same methods on a `createMarionette()` result | Configure only the affected classes in that runtime. Return `undefined`. |
| `SomeClass.set…(...)` | Configure the receiving class's prototype. Return that class for chaining. Descendants inherit unless they have their own override. |
| Setter | Classes configured by a top-level/runtime call | Operation |
| --- | --- | --- |
| `setDomApi(mixin)` | View, CollectionView, Region | Shallow overlay on each class's `Dom`. |
| `setDataApi(mixin)` | View, CollectionView | Shallow overlay on each class's `Data`. |
| `setStateApi(mixin)` | Application, Behavior, View, CollectionView, MnObject | Shallow overlay on each class's `State`. |
| `setRenderer(renderer)` | View, CollectionView | Replace the renderer function. |
| `setEventDelegator(provider)` | View, CollectionView, Behavior | Replace the DOM delegation provider. |
Only the classes listed in each row provide that static setter. Configure before constructing consumers. Setters change prototype configuration, not an instance's already-created element, cached data, or installed subscriptions. They do not rebuild existing instances. A live instance that inherits the slot can use the new provider on its next operation while retaining earlier subscriptions or renderer-owned contents. This can mix incompatible configurations; configuration is not a live migration API. A parent setter can affect subclasses still inheriting that slot; a subclass overlay creates its own provider object and no longer inherits later parent changes to that slot. Configure both a CollectionView and its child View classes when both consume an integration.
Overlays preserve unspecified methods, with the last supplied value winning. They are shallow: nested values and function closures are shared. Explicit `undefined` can remove a required method; an empty overlay does not restore defaults. `setRenderer()` assigns `undefined`, also not a reset. Supply valid providers; setters do not certify runtime compatibility.
## Defaults and exports
| Runtime member | Default and reference |
| --- | --- |
| `Application`, `Behavior`, `CollectionView`, `MnObject`, `Region`, `View` | Constructors in this family. See the [class index](/docs/api.md#core-classes). |
| `DomApi` | Native DOM operations, static descendant queries and HTML-string insertion. [DOM contract](/docs/api/providers/dom.md#domapi). |
| `DataApi` | Plain values for rendering and static arrays for collection membership; compatible `on`/`off` sources for explicit entity events. [Data contract](/docs/api/providers/data.md#dataapi). |
| `StateApi` | No observable-state support or owned disposal by default. `subscribe` throws `MN0037`. [State contract](/docs/api/providers/data.md#stateapi). |
| `Radio` | Default exported Radio for the default family; a new Radio instance for each isolated runtime. [Owner bindings](/docs/api/shared/common.md#declarative-radio-bindings). |
| `VERSION` | The package version string, also available as a named import. |
| `Events`, `extend`, `MarionetteError`, `monitorViewEvents` | Shared utilities, not newly isolated copies. [Events](/docs/api/shared/events.md), [extension](/docs/api/shared/common.md#define-a-class), and [View lifecycle monitoring](/docs/api/view.md#lifecycle-hooks-and-events) describe the relevant contracts; the [utilities reference](/docs/packages/utils.md) and [errors reference](/docs/api/errors.md) cover the standalone exports. |
`createMarionette` is a named export of `marionette`. The package has no default export, and a created runtime does not itself have a `createMarionette` method. `EventDelegator` and `Renderer` are type exports, not provider values. Configure those behaviors through their setters.
`DataApi`, `DomApi`, and `StateApi` expose the initial provider objects. Setters replace class slots with overlays; they do not update these exported objects into a registry of the active configuration. Inspect a class's `prototype.Data`, `prototype.Dom`, or `prototype.State` when needed. Prefer setters over direct mutation.
## Isolation and composition
Each `createMarionette()` starts from copied native defaults, even after configuring the default family. It has separate constructors, provider objects and Radio channels. Changing one family's configured slots or resetting its Radio does not reset another family's configuration/channels. Native Regions created by its Views and Applications, and the CollectionView empty Region, use its own Region class.
Use the appropriate family's constructors for composition:
- A View/Application Region definition cannot borrow a Region or Region class from another runtime (`MN0030`).
- Application child registration requires a child from the same runtime (`MN0031`); a start destination must be a Region from the same runtime (`MN0030`).
- `Region.show(view)` supports a View from another runtime through its supported View lifecycle. It does not convert the View's renderer/data providers or move the Region into that other runtime.
This is configuration isolation. Classes inherit from the library's base classes, utilities and the document remain shared, and providers may close over shared resources. It is not a security boundary or a deep clone of application data. There is no runtime-wide destroy/reset method; destroy the Applications, Regions, Views and other owners you create.
## TypeScript
Use `ReturnType` for the returned runtime type; there is no named public Runtime type. Runtime methods expose the same class/provider contracts as named imports. Types are structural, so they do not brand objects by runtime; ownership checks still run at runtime.
Provider registration accepts concrete source/output types through an intentionally opaque class slot. It does not prove that every model, template, DOM result, child class or supplied state matches that provider. Keep the concrete adapter typed, configure a coherent class family, and test its consumers. See [DOM types](/docs/api/providers/dom.md#typescript) and [data types](/docs/api/providers/data.md#typescript).
[Canonical source](/docs/markdown/docs/api/runtime.md) · [Source identity](/docs/manifest.json)
---
Document: docs/api/providers/dom.md
Canonical URL: https://marionettejs.com/docs/api/providers/dom/
Markdown URL: https://marionettejs.com/docs/api/providers/dom.md
Reading SHA-256: f2e20c19076728f35ff798faac59414dcffdd2ea0740224082a3c749a5665496
# Rendering and DOM providers
[API index](/docs/api.md) · [Runtime configuration](/docs/api/runtime.md) · [View rendering](/docs/api/shared/view-bindings.md#templates-and-data)
Choose an existing integration when it fits. A renderer evaluates a template; DomApi applies the output and performs DOM operations; EventDelegator installs DOM listeners. These independent boundaries let a View keep its lifecycle and ownership rules while changing its integration.
## Renderer
`setRenderer(renderer)` accepts a synchronous function `(template, data) → output`, invoked with the View as `this`. The default calls `template(data)` without binding the template function to the View. It does not compile strings. `template: false` skips template rendering.
Marionette serializes data and applies template context before calling the renderer; a falsy final data value becomes `{}`. It passes every return value to `attachElContent`, whose default calls `Dom.setContents(el, output)`. A Promise is not awaited, and `undefined` does not mean “skip insertion.” If a renderer performs insertion itself, override `attachElContent` to match that strategy and keep the View's fixed root. Usually, putting insertion in DomApi keeps the responsibilities simpler.
The native `setContents` treats strings as HTML. Choose an integration such as [Lit](/docs/integrations/setup.md#configure-once) for templated UI with ordinary text interpolation. This small text-only class demonstrates a typed DomApi overlay without building a new template engine:
```ts
import { View, type DomApiContract } from 'marionette';
const TextDom = {
setContents(el: Element, value: string) {
el.textContent = value;
},
} satisfies Partial, string>>;
const Label = View.extend({
template: ({ label }: { label: string }) => label,
}).setDomApi(TextDom);
const label = new Label({ model: { label: '' } });
label.render();
```
Only content insertion changes; native root creation, queries and placement remain inherited. This class displays literal text, not HTML templates. The [scope rules](/docs/api/runtime.md#choose-a-configuration-scope) explain when to configure a class or a whole family.
## DomApi
`setDomApi(mixin)` overlays methods on `Dom`. Methods are called on that provider object. Implement only the operations being replaced, while keeping their inputs/outputs compatible with the remaining methods. Publicly import the default value as `DomApi`; import the interface as `DomApiContract`.
| Method | Required behavior and native default |
| --- | --- |
| `createElement(tagName) → Element` | Create a root element. Native uses `document.createElement`. |
| `createBuffer() → DocumentFragment` | Create a buffer for batch child insertion. |
| `getDocumentEl(el) → Element \| null` | Return the document root used for attachment checks. Native uses `el.ownerDocument.documentElement`. |
| `findEl(el, selector) → Query` | Return an array-like collection of matching descendants. Native returns a static `NodeList`; excludes the root. |
| `hasEl(el, childEl) → boolean` | Test strict containment. Native checks the child's parent, so an element does not contain itself for this operation. Nullish child returns false. |
| `detachEl(el)` | Remove the element from its parent while preserving the element and listeners for possible reuse. |
| `replaceEl(newEl, oldEl)` | Replace the old element in its parent. Native does nothing for identical elements or a parentless old element. |
| `setContents(el, output)` | Apply renderer output within the root. Native uses `innerHTML`, with nullish output clearing contents. Match this method to the renderer's output. |
| `setAttributes(el, attributes)` | Apply attributes. Native uses own enumerable keys, removes `null` values, ignores `undefined`, and stringifies other values. Unmentioned attributes remain. |
| `appendContents(el, contents)` | Native default: append an Element/DocumentFragment to the parent Element/DocumentFragment. The jQuery adapter also accepts HTML strings, element arrays and jQuery collections. |
| `moveEl(el, parent, before?)` | Insert or move the element before the reference node; append when omitted/null. Native uses `moveBefore` for an existing child of that parent when available, otherwise `insertBefore`. |
| `hasContents(el) → boolean` | Report child-node presence. Native returns false for a nullish element. |
| `detachContents(el)` | Remove contents while retaining the root. Native clears `textContent`. This operation does not establish View destruction by itself. |
| `notifyAttach(el)` | Notify content integration that the View's element has attached. Native does nothing. |
| `notifyDetach(el)` | Notify content integration that the View's element has detached. Native does nothing. |
Marionette owns View/Region lifecycle and child cleanup around these operations. Providers must preserve the root identity and must not destroy unrelated Views or borrowed sources. Preserve renderer markers when moving child elements; a provider must support the actual content it created. The Lit adapter implements content insertion and attachment notifications so its directive connection state follows View attachment.
Attachment notifications follow monitored Marionette lifecycle, including construction on an attached root. `monitorViewEvents: false` on the View class disables that monitoring. Integrations such as Lit rely on these notifications for directive connection state, so disabling monitoring also disables that integration behavior. Arbitrary external DOM movement is not observed.
DOM methods are synchronous; they are not lifecycle preparation hooks. View/Region ownership and notifications remain defined in their class references. `notifyAttach`/`notifyDetach` communicate connection changes, not ownership transfer or a general-purpose resource disposer.
## EventDelegator
`setEventDelegator(provider)` replaces the entire provider. Its required method is:
`delegate({ eventName, selector, handler, rootEl }) → cleanup`
Install the listener on `rootEl` and return a function that releases exactly that subscription. Preserve the event passed to `handler`; the View/Behavior binding layer already selects the callback context. An empty selector represents the root listener. Marionette calls the cleanup on undelegation/destruction; detaching a live View preserves its bindings.
The native provider:
- Uses capture for `focus` and `blur`, and normal bubbling for other events.
- With a selector, walks from the event target toward the root and calls the handler once for the nearest matching descendant. The root is excluded.
- Sets `event.delegateTarget` to that matched descendant. `event.target` retains the original target.
- Without a selector, invokes the handler for events reaching the root listener, without adding a matched delegate target.
A custom provider controls its event matching behavior. Keep [View DOM events and triggers](/docs/api/shared/view-bindings.md#dom-events) compatible with what consumers expect. Behavior has its own configured EventDelegator; changing only a host View subclass's provider does not change its Behavior classes. Family-wide setters configure both.
## TypeScript
Import `Renderer`, `DomApiContract`, `EventDelegator`, `DelegateOptions`, and `DelegatedEvent` from `marionette`.
`Query` extends `ArrayLike`. The default DomApi's direct `findEl` returns `NodeListOf` and also accepts DocumentFragment; the `DomApiContract` interface accepts Element or Document. The content parameter defaults to `never`, keeping an unconfigured content slot opaque. Specify your output type when authoring a concrete provider, as above. Configuration does not infer or enforce a matching renderer/output/query type across all consumers.
`DelegateOptions` supplies a string selector and a handler accepting arbitrary arguments; `DelegatedEvent` adds optional `delegateTarget` to the native Event type. Use the [configuration setters](/docs/api/runtime.md#choose-a-configuration-scope) or the class's current provider when composing integrations.
[Canonical source](/docs/markdown/docs/api/providers/dom.md) · [Source identity](/docs/manifest.json)
---
Document: docs/api/providers/data.md
Canonical URL: https://marionettejs.com/docs/api/providers/data/
Markdown URL: https://marionettejs.com/docs/api/providers/data.md
Reading SHA-256: f0197e04cc9f92d0a7b55017ee8af03e9156f23431315e5225746a1e2874c0cd
# Data and state providers
[API index](/docs/api.md) · [Runtime configuration](/docs/api/runtime.md) · [State ownership](/docs/api/shared/state.md)
DataApi lets Views read and observe supplied data. StateApi lets an owner observe and dispose its own state. Configure them separately: a View's `model`/`collection` and an owner's state may use different sources. Neither contract supplies API requests or persistence. For an optional implementation, see the [`@mnjs/data` providers](/docs/packages/data.md#providers) and [setup guide](/docs/integrations/setup.md).
## DataApi
`setDataApi(mixin)` overlays the provider on View and CollectionView at the selected [configuration scope](/docs/api/runtime.md#choose-a-configuration-scope). Behavior entity bindings use their host's DataApi. A CollectionView's children use their own class configuration; configuring the parent alone does not configure its children.
| Method | Contract | Built-in default |
| --- | --- | --- |
| `key(model) → key` | Stable, non-null unique identity within the observed collection. See [identity](#collection-identity-and-observation). | Returns the model itself. Plain object identity is its key. |
| `get(model, attribute) → value` | Read an attribute used by filtering/comparison and other consumers. | Own-property lookup; missing returns `undefined`. Nullish model is invalid for this method. |
| `has(model, attribute) → boolean` | Test attribute presence. | Own-property existence, independent of truthiness; nullish returns false. |
| `serialize(model) → data` | Return data for template rendering. It need not be a copy. | Returns the source unchanged. |
| `models(collection) → readonly model[]` | Return the current ordered model snapshot. Keep model identities stable when they represent retained entities. | Returns the supplied array itself; array mutation is not observed. |
| `subscribe(source, name, callback, context?) → cleanup` | Subscribe to one literal source event, preserving callback arguments and supplied context. | Requires compatible `on`/`off`, otherwise throws `MN0037`. |
| `observeCollection(collection, callback, context?) → cleanup` | Observe membership/order/update notifications described below, calling the callback with its context. | Accepts only arrays, returns a no-op cleanup; other sources throw `MN0037`. |
The default supports plain values and static arrays. Explicit entity event maps still need observable sources. The default StateApi does not reuse DataApi's `on`/`off` support automatically.
For either subscription method, return cleanup that releases only the registered callback/context and prevents further delivery. Do not destroy shared data or clear other consumers' subscriptions. An owner may have multiple subscriptions to the same source. DataApi has no disposal method: View destruction releases observation while leaving its model/collection alive.
## Collection identity and observation
CollectionView reads a snapshot and subscribes during its first render. The provider must publish notifications **after** the source reflects that change, so `models(collection)` already returns the corresponding order and membership. Subscribe to subsequent changes; do not synchronously emit an initial notification during registration. Use these callback shapes:
| Notification | Required payload and meaning |
| --- | --- |
| Reset | `{ kind: 'reset' }` — destroy all existing children and build from the current snapshot, even when model identities remain. |
| Reorder | `{ kind: 'reorder' }` — source order changed with membership retained. CollectionView follows it according to `sortWithCollection` and its comparator. |
| Update | `{ kind: 'update', added: [...], removed: [...], updated: [{ previous, current }, ...] }` — report additions/removals as actual model values, and changed/replaced models as pairs. All three arrays are required, even when empty. |
`previous` refers to the previously observed model and `current` to the current model for the same stable key. A same-object pair marks its existing child for rendering. A different-object pair replaces and destroys the old child even when the key is unchanged. Added/removed entries must correspond to the before/after snapshots. Emit a coherent change for each structural source change; notifications describe completed changes, not commands for Marionette to mutate the collection.
Every model must occur once. Keys must be non-null, unique and unchanged while that model remains observed; CollectionView raises `MN0039` for these key violations. These key checks are not comprehensive validation of malformed notification payloads. Equality follows JavaScript Map/Set semantics, which compares object keys by identity. A domain ID is suitable only if it meets those lifetime constraints. A retained key does not grant a new model object the previous child's identity.
Keep source events separate from collection notifications. A Model's `change` event can drive a row's `modelEvents`; it does not by itself mean the collection observer emits an update, sorts, or refilters. See [`@mnjs/data` providers](/docs/packages/data.md#providers) for that package's collection event translation. Reporting one mutation both as a same-object update pair and as a row render event can render that row twice. Use the [CollectionView reference](/docs/api/collection-view.md) for display/lifetime operations.
A custom provider may use stable opaque references, such as entity IDs, and read their current attributes from an immutable store. Keeping those references stable allows existing Views to react through source events; retaining a View does not itself preserve its input nodes or drafts. DOM retention depends on that View's renderer and update strategy.
## StateApi
| Method | Contract |
| --- | --- |
| `subscribe(source, name, callback, context?) → cleanup` | Observe one literal state event. Preserve source callback arguments and context; return cleanup that releases only this subscription. |
| `disposeOwned(source)` | Optional synchronous disposal for a source returned by `createState`. Called after that owner's state subscriptions are released. |
The default `StateApi.subscribe` always throws `MN0037`; configure a compatible provider before using `stateEvents`. The default has no `disposeOwned`. Plain default state can still be read and changed without declarative observation.
State returned by `createState` is owned; constructor/prototype `state` is borrowed. Disposal is never called for borrowed state. Unrequested lazy state is not created just to destroy it. Application start/stop/restart retain state, and Marionette gates Application `stateEvents` by activity; the provider does not implement that gate. Other state owners observe throughout their live lifetime. See the shared [state reference](/docs/api/shared/state.md) for owner methods and lifecycle.
Do not use `disposeOwned` to issue server deletion or dispose resources owned elsewhere. It is a local ownership boundary. It is not awaited. DataApi and StateApi can share subscription code while retaining different disposal responsibilities.
## TypeScript
Import `DataApiContract` and `StateApiContract` from `marionette`. Data parameters default to `never`; StateApi's standalone source parameter defaults to `unknown`, while configured class state slots are opaque. Specify concrete source types on a provider you author. `subscribe` and `observeCollection` must return cleanup functions; `models` must return an array.
The public `observeCollection` callback parameter is currently typed `unknown`; the notification shapes above are a runtime contract, not an exported core change union. The optional [`@mnjs/data` package](/docs/packages/data.md#typescript) exports its own `CollectionChange` type. A provider's declaration alone does not validate notification consistency.
Class setters accept concrete partial providers without inferring a universal model/state type for every consumer. Keep direct adapter values typed and validate them through the intended View/CollectionView/state owners. Registering a provider does not change source ownership, add transport, or install it on other independently configured classes.
[Canonical source](/docs/markdown/docs/api/providers/data.md) · [Source identity](/docs/manifest.json)
---
Document: docs/packages/data.md
Canonical URL: https://marionettejs.com/docs/packages/data/
Markdown URL: https://marionettejs.com/docs/packages/data.md
Reading SHA-256: 1dc66c78b9b20c93727780ecdd54cf8a398a298364df5ae53924dd2750e46fd5
# Model and Collection (`@mnjs/data`)
[API index](/docs/api.md) · [Setup](/docs/integrations/setup.md) · [Data and state providers](/docs/api/providers/data.md)
`@mnjs/data` is an optional companion package supplying observable Models, ordered Collections, and Marionette providers. It works independently of Marionette core and the DOM. Fetching, persistence, validation, and server synchronization belong to an API layer or another data solution. It has no `fetch`, `save`, or HTTP deletion methods. Marionette can use a different source through a compatible provider.
## Model
`new Model(attributes?, options?)` makes an observable set of attributes. Construction combines prototype `defaults` with supplied attributes, assigns a unique client ID, and then calls `initialize(attributes, options)`. Supplied attributes win, including an explicit `undefined`. Initial attributes do not emit change events; mutations inside `initialize` do.
```js
import { Model } from '@mnjs/data';
const Preferences = Model.extend({
defaults() { return { compact: false }; },
});
const preferences = new Preferences({ theme: 'light' });
const changes = [];
preferences.on('change:theme', (model, theme) => changes.push(theme));
preferences.set('theme', 'dark');
preferences.get('theme'); // 'dark'
preferences.reset({ theme: 'system' }); // Reapplies compact: false.
```
### Model configuration and properties
| Member | Contract |
| --- | --- |
| `defaults` | Prototype object or function returning initial attributes. A function runs with the Model as `this`, on construction and `reset`. Use it to create fresh nested values per instance. |
| `idAttribute` | Prototype attribute name used for `id`; defaults to `'id'`. Set it before construction. |
| `initialize(attributes, options)` | Optional setup hook after attributes and identity exist. Options are passed through; they are not merged or stored as `this.options`. |
| `attributes` | Current attribute object. Read through `get`/`has` and write through mutation methods so changes notify observers. |
| `changed` | Sparse map for the most recent mutation. An unchanged write clears it. Use an event's change payload when inspecting that particular mutation. |
| `id` | Value of the `idAttribute` attribute, updated by attribute mutations. Change it with `set`, rather than assigning `model.id`. |
| `cid` | Unique client ID, stable for this instance. Used by the native DataApi to identify CollectionView children independently of server IDs. Treat it as read-only. |
Define `defaults` through `Model.extend`, a prototype method, or a getter. A native class field initializes after `super()` and therefore cannot provide construction defaults.
### Read and mutate attributes
| Method | Result and behavior |
| --- | --- |
| `get(key)` | Attribute value, or `undefined` if absent. |
| `has(key)` | Own-property presence, including attributes containing `undefined`, `null`, or `false`. |
| `set(attributes, options?)` / `set(key, value, options?)` | Update supplied attributes; leave others unchanged. Return the Model. |
| `unset(key, options?)` | Remove that attribute. Return the Model. |
| `clear(options?)` | Remove all attributes, including defaults. Return the Model. |
| `reset(attributes?, options?)` | Replace attributes with defaults plus supplied attributes; remove keys absent from that result. Return the Model. |
| `toObject()` | New shallow attribute object. Nested values remain shared references. Use this result for JSON serialization; the Model has no automatic `toJSON` hook. |
| `isDestroyed()` / `destroy(options?)` | Inspect or end the local lifetime; see [destruction](#destruction-and-ownership). |
Values are compared with `Object.is`. A fresh object is a change even when its contents match. Mutating a nested object in place does not notify; set a replacement value to publish its change. Mutation options are event metadata, not validation or transport settings. There is no `silent` mode.
## Model events
All effective mutations finish updating attributes and `id` before publishing synchronously:
| Event | Callback arguments | Hook |
| --- | --- | --- |
| `change:` | `(model, value, change)` | For example, `onChangeTheme(model, value, change)` |
| `change` | `(model, change)` after attribute events | `onChange(model, change)` |
| `destroy` | `(model, options)` once on destruction | `onDestroy(model, options)` |
The `change` argument contains the supplied metadata plus `changed` and `previous` sparse maps. `changed` contains affected attributes and their new values; removal is represented by `undefined`. `previous` contains only affected attributes that existed before the write. It is not a complete snapshot; use `has` to check current presence. A write with no effective changes emits nothing.
Nested writes complete as independent synchronous changes. An event's `change.changed` describes that event; `model.changed` may already reflect a subsequent nested write. Exceptions from listeners or hooks propagate to the caller and stop the remaining delivery.
Models and Collections share the [Events methods](/docs/api/shared/events.md): `on`, `once`, `off`, `trigger`, `triggerMethod`, `listenTo`, `listenToOnce`, and `stopListening`. Event maps contain function callbacks; names are literal strings. `triggerMethod` invokes the named hook before event listeners.
## Collection
`new Collection(models?, options?)` creates ordered membership. Supply one Model/attribute object or an array; omitted or null input starts empty. Raw attribute objects use the configured `model` constructor. Supplied native Model instances retain their identity and subclass, even when that constructor differs.
```js
import { Collection, Model } from '@mnjs/data';
const first = new Model({ id: 'a', label: 'Alpha' });
const items = new Collection([first, { id: 'b', label: 'Beta' }]);
items.add({ id: 'c', label: 'Gamma' }, { at: 1 });
items.move('b', 0);
items.get('a').set('label', 'Updated Alpha'); // Retains the Model.
const labels = items.map(model => model.get('label'));
const plainItems = items.toArray(); // Attribute objects, not Models.
```
### Collection configuration and properties
| Member | Contract |
| --- | --- |
| `model` | Constructor for raw attributes; defaults to `Model`. Set on the prototype or pass `{ model: ModelClass }` as constructor options. The constructor option wins. Supplied Models do not change this factory for later additions. |
| `comparator` | Optional prototype attribute name or two-Model comparison function, used by `sort()` when no comparator is passed. Adding or resetting data does not automatically sort it. |
| `initialize(models, options)` | Optional hook after membership is seeded without mutation events. Options are passed through; the Collection does not create `this.options`. |
| `models` | Current ordered array of Models. Use membership methods to change it. For your own array of references use `[...collection]` or `collection.models.slice()`. |
| `length` | Current number of Models. |
### Read and iterate
| Method | Result |
| --- | --- |
| `at(index)` | Model at the index; negative indexes count from the end. Out of range returns `undefined`. |
| `get(identity)` | Contained Model resolved by exact member instance, then current non-null ID, then client ID. Missing/nullish identity returns `undefined`. |
| `indexOf(model)` | Position of that exact Model instance, or `-1`. |
| `forEach(callback, context?)` | Calls `(model, index, models)` in order; returns `undefined`. |
| `map(callback, context?)` | Calls the same signature and returns an array of results. |
| `[Symbol.iterator]()` | Iterates Model instances, supporting `for...of` and spread. |
| `toArray()` | New array of shallow attribute copies, equivalent to mapping `toObject()`. Use this for JSON serialization. |
### Membership and ordering
| Method | Result and notifications |
| --- | --- |
| `add(modelOrAttributes, options?)` / `add(array, options?)` | Add new membership, optionally at integer `options.at` (clamped to the list); otherwise append. Return the added Model or array of added Models. An ignored single addition returns `undefined`. Emit `add` per Model, then one `update`. |
| `remove(identity, options?)` / `remove(identities, options?)` | Resolve like `get`; remove matching members without destroying them. Return the removed Model or an array in input order. Missing/repeated matches are skipped; a missing single match returns `undefined`. Emit `remove` per Model, then one `update`. |
| `reset(models?, options?)` | Replace all membership; return the Collection and emit `reset`, even for unchanged input. Raw attributes create new Models. Supplied Models are retained as instances. No per-Model `add`/`remove` events. |
| `move(identity, index, options?)` | Move an existing Model; clamp an integer index to the list. Return the Model, or `undefined` if absent. Emit `sort` when its position changes. A noninteger index for an existing Model throws `TypeError`. |
| `sort(comparator?, options?)` | Sort by attribute name or `(left, right) => number`, with the Collection as callback `this`. Defaults to prototype `comparator`. Return the Collection and emit `sort`, even if order is unchanged. Without a valid comparator, do nothing. |
| `isDestroyed()` / `destroy(options?)` | Inspect or end the local lifetime; see [destruction](#destruction-and-ownership). |
`add` ignores existing instances or non-null IDs. Construction and `reset` reject duplicate instances or non-null IDs with `TypeError`, before replacing membership. Keep IDs unique when updating Models: a later ID change does not enforce uniqueness; lookup then selects the first current matching member. ID equality follows Map/Set equality. Nullish IDs do not identify members.
Batch related membership changes with `add([...])` or `remove([...])`. Each call that changes membership emits one aggregate `update`, so CollectionView reconciles once per call instead of once per item. Per-Model `add`/`remove` events still fire for changed members. Empty, duplicate-only additions and unmatched removals emit no `update`.
`reset` is a whole-list replacement. With CollectionView it destroys and rebuilds child Views, including children whose Model instances remain. For retained items, update the existing Model with `set`, change membership with `add`/`remove`, and change order with `move`/`sort`. There is no `Collection.set` merge operation. See [CollectionView](/docs/api/collection-view.md) for display filtering, ordering, and child ownership.
## Collection events
| Event | Callback arguments | Hook |
| --- | --- | --- |
| `add` | `(model, collection, options)` | `onAdd(model, collection, options)` |
| `remove` | `(model, collection, options)` | `onRemove(model, collection, options)` |
| `update` | `(collection, change)` | `onUpdate(collection, change)` |
| `reset` | `(collection, options)` | `onReset(collection, options)` |
| `sort` | `(collection, options)` | `onSort(collection, options)` |
| `destroy` | `(collection, options)` for its own destruction | `onDestroy(collection, options)` |
`update` supplies metadata plus `changes: { kind: 'update', added, removed, updated }`. Native membership operations populate `added` or `removed`; `updated` is empty. Membership reflects the entire mutation before listeners run. No-op additions/removals and unchanged moves emit nothing. Options are metadata except for `add`'s `at`; they do not suppress notifications.
Collections also forward contained Models' events with their original arguments. On a Model's `destroy`, each containing Collection emits its `remove` and `update` events as it removes that Model, then forwards `destroy(model, options)` to its own listeners. Thus a Collection's `destroy` event may refer to a member or the Collection itself; inspect the first argument when that distinction matters. `add`/`remove` originate on the Collection, not the Model.
A Model attribute change does not emit a structural update, resort, or refilter a list. A row can observe it through [modelEvents](/docs/api/view.md); parent filtering and sorting are separate operations. Complete one structural mutation before starting another from its listeners or child lifecycle hooks. Notifications are synchronous, not queued or replayed by the data package.
## Destruction and ownership
`destroy(options?)` returns the source and is idempotent. It marks the source destroyed, emits `destroy`, releases outgoing `listenTo` subscriptions and incoming event callbacks, and makes subsequent data mutations no-ops rather than throwing. It performs no server request. The retained attribute/membership snapshots remain readable.
Destroying a Model removes it from every containing Collection. Destroying a Collection releases its member subscriptions but does not destroy the Models. A Model may belong to multiple Collections. Removing or replacing membership does not transfer ownership or destroy the previous members.
Marionette Views borrow their `model` and `collection`: destroying a View releases observation without destroying its data. State returned by `createState` is owned and disposed through StateApi; supplied state is borrowed. See [state ownership](/docs/api/shared/state.md). Local data without external subscriptions or resources needs no explicit destruction once it becomes unreachable.
## Providers
Import `DataApi` and `StateApi` from `@mnjs/data` and configure Marionette before constructing consumers, as shown in [setup](/docs/integrations/setup.md#configure-once). These optional providers implement the core [DataApi and StateApi contracts](/docs/api/providers/data.md); they are distinct from core's plain-data defaults.
| Provider method | Package behavior |
| --- | --- |
| `DataApi.key(model)` | Returns the Model's client ID (`cid`), preserving child identity when its server ID changes. |
| `DataApi.get(model, attribute)` / `has(model, attribute)` | Use native Model methods; otherwise read/test own properties. Nullish sources return `undefined` / `false`. |
| `DataApi.serialize(model)` | Returns a native Model's live attributes; otherwise returns the source unchanged. |
| `DataApi.models(collection)` | Requires a native Collection and returns a shallow copy of its ordered Models. Arrays and other collection implementations throw `TypeError`. |
| `DataApi.observeCollection(collection, callback, context?)` | Collection `update` events deliver their normalized `changes` payload; `reset` and `sort` become reset/reorder notifications. Calls the callback with the supplied context and returns cleanup. |
| `DataApi.subscribe(...)` / `StateApi.subscribe(...)` | Observe compatible `on`/`off` sources, preserving event arguments and context. Missing event methods throw `TypeError`. |
| `StateApi.disposeOwned(source)` | Calls the source's `destroy()` when present. Native destruction is local cleanup: no server request, and destroying a Collection does not destroy its Models. |
Both providers accept `subscribe(source, name, callback, context?)` and `subscribe(source, eventMap, context?)`. Map values are function callbacks. Both forms return an idempotent cleanup function that removes only those registrations.
Model change events remain separate from structural collection notifications. Use a row's `modelEvents` for attribute changes; changing attributes does not automatically sort or refilter a list. See [Collection events](#collection-events).
Configure DataApi and StateApi separately for the sources each owner uses. Installing these providers does not add persistence or change ownership: Views borrow Models and Collections; only state returned by `createState` is owned. A different data solution can supply its own implementation of the same core contracts.
## TypeScript
```ts
import { Collection, Model } from '@mnjs/data';
import type { CollectionChange, MutationOptions } from '@mnjs/data';
type Preferences = { theme: string; compact: boolean };
const preferences = new Model({ theme: 'light' });
preferences.set('compact', true);
const theme: string | undefined = preferences.get('theme');
const copies: Partial = preferences.toObject();
const selections = new Collection>([preferences]);
const metadata: MutationOptions = { source: 'settings' };
selections.add({ theme: 'dark' }, metadata);
const change: CollectionChange> = { kind: 'reorder' };
```
Attributes and `toObject()` are partial because construction and mutation can leave keys absent. Known `set` keys check value types; dynamic string keys remain open. `toArray()` currently returns `ModelAttributes[]` rather than preserving the attribute generic. Types do not validate server responses.
| Exported type | Meaning |
| --- | --- |
| `Model` / `Collection` | Generic instance types sharing names with the constructors. Collection member types include supplied Models and the factory used for raw attributes. |
| `ModelAttributes` | `Record`. |
| `MutationOptions` | Open metadata object. Event-specific `changed`, `previous`, and `changes` fields are runtime payloads; this type does not infer their contents. |
| `ModelInput` | A member Model or its attributes, used by Collection construction and mutation. |
| `CollectionOptions` | Optional `model` constructor. `comparator` belongs on the prototype or in `sort`, not constructor options. |
| `CollectionChange` | Discriminated union of `{ kind: 'reset' }`, `{ kind: 'reorder' }`, and `{ kind: 'update', added, removed, updated }`, where `updated` contains `{ previous, current }` member pairs. |
| `EventCallback` / `EventSource` | Re-exported callback and event-method types from `@mnjs/utils`. Event names do not statically validate payload types. |
`Model.extend` and `Collection.extend` use the shared [class extension mechanism](/docs/api/shared/common.md#define-a-class), preserving added methods and static properties in their types. Prefer `initialize` for setup. An explicit constructor replaces parent initialization and owns its return contract. Unlike core owners, these classes do not supply `getOption`, binding helpers, or Radio configuration.
The package also re-exports the standalone `triggerMethod` helper. Call it with an event-capable receiver, for example `triggerMethod.call(model, 'selected', model)`. Its hook/event behavior is documented in [Events](/docs/api/shared/events.md#trigger-events-and-hooks). ESM imports and CommonJS `require('@mnjs/data')` have matching declarations; import from the package root rather than internal files.
[Canonical source](/docs/markdown/docs/packages/data.md) · [Source identity](/docs/manifest.json)
---
Document: docs/packages/radio.md
Canonical URL: https://marionettejs.com/docs/packages/radio/
Markdown URL: https://marionettejs.com/docs/packages/radio.md
Reading SHA-256: 0a4a8bbb8070e6eb001b3250f5f5e32184db1ed85983aa2649261b297c5559e7
# @mnjs/radio
Radio provides named channels for events and request/reply messaging. An event
notifies subscribers; a request calls one reply handler and returns its result.
Use a channel when participants need a shared messaging address. Direct method
calls and [object events](/docs/api/shared/events.md) remain useful when the
participants already hold references to one another.
## Imports and registry scope
`marionette` exports the default `Radio` instance from `@mnjs/radio`. Core uses
Radio for Application and MnObject's declarative channel bindings. The package
can also be used without core or a DOM; it depends on `@mnjs/utils`.
```js
import { Radio } from '@mnjs/radio';
const preferences = Radio.channel('preferences');
const changes = [];
preferences.on('theme:changed', theme => changes.push(theme));
preferences.reply('theme', 'dark');
const theme = preferences.request('theme');
preferences.trigger('theme:changed', theme);
```
Within the same installed package copy and module format, importing `Radio` from
core or the companion package reaches the same registry. ESM and CommonJS have
separate default instances. Separate installed copies also do not share state.
`createRadio()` returns an independent `RadioApi`, with its own channels and
logging configuration. Each `createMarionette()` runtime has its own `Radio` too:
use `runtime.Radio` to communicate with objects from that runtime. See
[runtime isolation](/docs/api/runtime.md#isolation-and-composition).
| Registry API | Contract |
| --- | --- |
| `radio.channel(name)` | Returns the existing channel or creates it. Names are nonempty strings. A missing/falsy name throws `MN0017`. |
| `radio.Channel` | The constructor used for new channels in this registry. Channels already created keep their identity. |
| `radio.reset(name)` | Resets an existing registered channel. An unknown name throws `MN0021`; a supplied falsy name (including `undefined`) throws `MN0017`. Returns `undefined`. |
| `radio.reset()` | Resets all registered channels. Returns `undefined`. |
Reset clears handlers but preserves registered channel objects: later
`radio.channel(name)` calls return the same channel.
## Channel events
A channel provides the complete [Events API](/docs/api/shared/events.md): `on`,
`once`, `off`, `listenTo`, `listenToOnce`, `stopListening`, `trigger`, and
`triggerMethod`. Names are literal strings, including spaces; maps register
multiple names. Events run synchronously and ignore callback return values.
`triggerMethod` calls the channel's matching `onEventName` hook before emitting
the event and returns that hook's result.
Every Events and Requests method is also available on the registry, with the
channel name as its first argument. For example,
`radio.trigger('preferences', 'theme:changed', 'dark')` forwards to the named
channel. Forwarded methods create a missing channel and return the channel
method's result. Registration methods return the **channel**, so chaining
`radio.on(...)` continues on that channel rather than on the registry.
Channels expose `channelName`, the name supplied at construction. They have no
Application start/stop lifecycle or automatic destruction. Use the owner and
cleanup rules below when their useful lifetime ends.
## Requests and replies
A request name has one active reply handler. A later `reply` or `replyOnce` for
the same name replaces the earlier registration. Request names, like event
names, are literal strings. Replies and event subscriptions are separate: an
`on('theme', handler)` subscription does not answer `request('theme')`.
| Channel or Requests method | Contract and return value |
| --- | --- |
| `reply(name, callbackOrValue, context?)` / `reply(map, context?)` | Registers a function or a constant value. Returns the receiver. |
| `replyOnce(name, callbackOrValue, context?)` / `replyOnce(map, context?)` | Registers a reply removed before its first invocation, including when it throws. Each mapped name has its own one-time reply. Returns the receiver. |
| `request(name, ...args)` | Calls the matching reply with the supplied arguments and returns its result unchanged. Returns `undefined` when neither a matching nor default reply exists. |
| `request(map, ...extraArgs)` | Requests each own enumerable string key, passing its mapped value first, then the extra arguments. Returns an object keyed by request name. |
| `stopReplying(name?, callbackOrValue?, context?)` / `stopReplying(map, context?)` | Removes replies matching all supplied filters. Omit the name or pass `null` to select all names. With no filters, removes all replies. Returns the receiver. |
Functions use the supplied truthy context, or the receiver as `this`. Arrow
functions keep their lexical context. Registration/removal maps also accept an
explicit third context argument; a truthy third argument takes precedence over
the second. Map values can be functions or constants, including `false`, `0`,
`null`, and `undefined`. In `stopReplying`, falsy callback/context arguments mean
no filter; use the name to remove a reply registered with a falsy constant.
Removing a one-time reply may use its original function reference.
```js
import { createRadio } from '@mnjs/radio';
const catalog = createRadio().channel('catalog');
const titles = new Map([['a', 'Overview'], ['b', 'Release notes']]);
catalog.reply({
title: id => titles.get(id),
count: () => titles.size,
});
const summary = catalog.request({ title: 'b', count: undefined });
catalog.replyOnce('welcome', 'Welcome back');
const welcome = catalog.request('welcome');
const repeatedWelcome = catalog.request('welcome');
```
A reply named `default` handles requests with no exact match. It receives
`(requestedName, ...args)`; an exact match takes precedence. Requesting the name
`default` itself is an exact match, so it receives only the caller's arguments.
Dispatch is synchronous. A thrown exception propagates to the caller. A reply
may return a Promise, which `request` returns without awaiting, cancelling, or
retrying it. A request map returns an object of results, not a Promise combining
them. The owner of an operation remains responsible for its completion and
lifetime.
## Lifetime and cleanup
Use `listenTo(channel, ...)` to tie subscriptions to a Marionette object's
lifetime. Destroying that object removes its event subscriptions. Request
replies have separate ownership: `stopListening()` does not remove them. Pair
manual replies with `stopReplying` or the owner's
[`unbindRequests`](/docs/api/shared/common.md#binding-helpers).
Application and MnObject provide
[`channelName`, `radioEvents`, and `radioRequests`](/docs/api/shared/common.md#declarative-radio-bindings).
Their destruction removes the configured channel's replies whose context is
that owner and releases its subscriptions. Application `stop()` retains those
bindings. The shared channel survives; other participants' handlers remain.
`channel.reset()` calls `off()`, `stopListening()`, and `stopReplying()`, then
returns the channel. It removes incoming event callbacks, outgoing subscriptions,
and replies. Use it when the whole channel's handlers should be cleared, such as
at the end of an isolated test. Selective cleanup is appropriate when other
participants still use the channel. Reset neither cancels returned Promises nor
emits a destruction event.
### Standalone channels and Requests
```js
import { Channel, Requests } from '@mnjs/radio';
const local = new Channel('preview');
local.reply('title', 'Preview');
const previewTitle = local.request('title');
local.reset();
const service = Object.assign({ ready: true }, Requests);
service.reply('ready', function() { return this.ready; });
const ready = service.request('ready');
service.stopReplying();
```
`new Channel(name)` creates an independent channel without registering it with
Radio. Two standalone channels with the same name are different objects, and
`Radio.reset()` does not reach them. The named `Channel` export is the default
`Radio.Channel`; `new radio.Channel(name)` uses that instance's logging
configuration.
`Requests` is a method mixin, not a constructor. Assign it to an object or a
prototype to add `reply`, `replyOnce`, `request`, and `stopReplying`. Each receiver
stores its own replies. It adds no Events API, channel registry, reset method,
or destruction lifecycle. Its warnings use the default Radio configuration.
## Logging
```js
import { createRadio } from '@mnjs/radio';
const radio = createRadio();
const activity = [];
const warnings = [];
radio.log = (channel, name, ...args) => activity.push({ channel, name, args });
radio.debugLog = (warning, name, channel) => warnings.push({ warning, name, channel });
radio.setDebug();
radio.tuneIn('settings');
radio.trigger('settings', 'opened');
radio.request('settings', 'missing');
radio.tuneOut('settings');
```
| Registry member | Contract |
| --- | --- |
| `tuneIn(name)` | Creates/gets the channel and logs its events and requests. Event logging subscribes to `all`. Returns this Radio instance. |
| `tuneOut(name)` | Creates/gets the channel and stops its activity logging. Returns this Radio instance. |
| `setDebug(enabled = true)` | Enables/disables warning delivery for unhandled requests and overwritten replies. Initially disabled. Returns `undefined`. |
| `log(channelName, name, ...args)` | Assignable activity hook; defaults to console logging. Tuning is independent of `setDebug`. |
| `debugLog(warning, name, channelName?)` | Assignable warning hook; defaults to console warnings. Standalone Requests receivers may have no channel name. |
Hooks run with the Radio instance as `this`; existing channels use the current
hook even after replacement. Hook exceptions propagate. Disabling debug also
suppresses automatic calls to custom warning hooks.
Call `tuneIn` once per logging session; repeated calls add repeated event
subscriptions. Pair it with `tuneOut` before resetting a tuned channel: reset
removes the `all` subscription but does not clear the request-logging flag.
## Types and entrypoints
The package root supports ESM named imports and CommonJS destructuring. Its
runtime exports are `Radio`, `createRadio`, `Channel`, and `Requests`; it has no
default package export. Types include `RadioApi`, `ChannelConstructor`, `Channel`,
and `Requests`. The latter two names have both type and runtime meanings.
`marionette` re-exports the `Radio` value and the `RadioApi`, `Channel`, and
`Requests` types; import the other runtime values from `@mnjs/radio`.
```ts
import { createRadio, type RadioApi, type Channel } from '@mnjs/radio';
const radio: RadioApi = createRadio();
const channel: Channel = radio.channel('settings');
channel.reply('theme', 'dark');
const result = channel.request('theme');
const theme = typeof result === 'string' ? result : 'system';
```
Dynamic request names do not define a result schema: `request(name)` returns
`unknown`, and map requests return `Record`. Narrow the result
at the boundary or put a typed function around a known channel contract.
Fluent registration/removal methods preserve the receiver; registry forwarding
returns a Channel rather than Radio. `channel.reset()` returns the channel,
while `radio.reset()` returns `void`.
[Reference index](/docs/api.md) · [Events](/docs/api/shared/events.md) · [Owner bindings](/docs/api/shared/common.md#declarative-radio-bindings)
[Canonical source](/docs/markdown/docs/packages/radio.md) · [Source identity](/docs/manifest.json)
---
Document: docs/packages/utils.md
Canonical URL: https://marionettejs.com/docs/packages/utils/
Markdown URL: https://marionettejs.com/docs/packages/utils.md
Reading SHA-256: 9d275193ee9646cfded96c20ab9bfd9ee337c5690655e013d1486847482c8ec5
# @mnjs/utils
Shared utilities for components that need Marionette's conventions without importing its UI classes. Marionette, Radio, and native data use these same implementations. Add `@mnjs/utils` as a direct dependency when your code imports it, using the same version as core; see [installation](/docs/quick-start.md#install-matching-packages).
## Component methods
`Events` supplies the [shared Events contract](/docs/api/shared/events.md). The other methods below use their receiver as the owner. Mix them into a component or call them with `.call(owner, ...)`. A plain component has no automatic destruction lifecycle: release the subscriptions and replies it owns when it is finished.
```js
import { Events, getOption, triggerMethod, bindEvents, unbindEvents } from '@mnjs/utils';
const source = Object.assign({}, Events);
const component = {
...Events, getOption, triggerMethod, bindEvents, unbindEvents,
options: { label: 'Inbox' },
onOpen() { return this.getOption('label'); },
onChanged(label) { this.options.label = label; }
};
component.bindEvents(source, { changed: 'onChanged' });
source.trigger('changed', 'Archive');
const label = component.triggerMethod('open');
component.unbindEvents(source);
```
| Export | Contract |
| --- | --- |
| `getOption(name)` | [Option lookup](/docs/api/shared/common.md#options-and-initialization): defined values in `this.options` take precedence over `this[name]`; functions are returned, not invoked. Accepts string, number, or symbol keys, including `0` and `''`. A nullish/omitted name returns `undefined`. |
| `mergeOptions(options, keys)` | [Selected option copying](/docs/api/shared/common.md#options-and-initialization). Returns `undefined`; an own enumerable `__proto__` option is copied as a data property without changing the receiver's prototype. |
| `normalizeMethods(bindings)` | [Method-name resolution](/docs/api/shared/common.md#options-and-initialization). Returns a new function map or `undefined` for falsy input; resolves own enumerable entries without binding or subscribing. |
| `triggerMethod(event, ...args)` | Calls the matching `onEvent` hook before triggering the event; returns the hook's result without awaiting it. See [hook dispatch](/docs/api/shared/events.md#trigger-events-and-hooks). |
| `bindEvents(source, bindings)` / `unbindEvents(source, bindings?)` | [Event binding helpers](/docs/api/shared/common.md#binding-helpers), requiring the receiver's `listenTo` / `stopListening`. Return the receiver. |
| `bindRequests(channel, bindings)` / `unbindRequests(channel, bindings?)` | [Request binding helpers](/docs/api/shared/common.md#binding-helpers), registering replies with the receiver as context. Return the receiver; `stopListening` does not remove replies. |
## Value and method resolution
| Export | Arguments and result |
| --- | --- |
| `getValue(object?, key?, fallback?)` | Reads `object[key]`; only `undefined` selects the fallback. If the selected value is a function, calls it with `object` as `this` and no arguments. A nullish object selects the fallback. |
| `resolveMethod(context, method, name)` | Returns a supplied function unchanged, or resolves a string/boxed-string method name on the context/prototype. `name` identifies the binding in the `MN0019` error when resolution fails. Does not bind the function. |
| `normalizeBindings(context, bindings)` | Returns a resolved function map, without subscribing. Like `normalizeMethods.call(context, bindings)`, but rejects an own enumerable `__proto__` event key with `MN0026`. Pass a bindings object. |
These operations are synchronous. Getters and callbacks can throw; errors propagate to the caller.
```js
import { getValue, resolveMethod, normalizeBindings } from '@mnjs/utils';
const settings = { prefix: 'Team', title() { return `${this.prefix} inbox`; } };
const title = getValue(settings, 'title');
const fallback = getValue(settings, 'missing', 'Inbox');
const handler = resolveMethod(settings, 'title', 'read:title');
const bindings = normalizeBindings(settings, { 'read:title': 'title' });
```
## Constructors and errors
`extend.call(Parent, prototypeProperties?, staticProperties?)` creates a child function constructor. See [class extension](/docs/api/shared/common.md#define-a-class) for forwarding, initialization, shared prototype values, and `__super__`. Prototype/static copying evaluates enumerable accessors. Inherited enumerable parent statics are copied too. Extending a native `class` requires an explicit constructor that constructs the parent; the default forwarding uses `Parent.apply`, which native classes do not support. Prefer ordinary `class extends` for native classes.
`MarionetteError` is the same constructor exported by core; see [errors and diagnostics](/docs/api/errors.md).
## Small standalone helpers
| Export | Arguments and result |
| --- | --- |
| `isString(value)` | Returns whether the value has the String tag, including primitive and boxed strings. Does not coerce the value to a string. |
| `setProperty(target, key, value)` | Assigns a property and returns `undefined`. For `__proto__`, defines an own writable, enumerable, configurable data property without replacing the target prototype. |
| `uniqueId(prefix?)` | Returns the next decimal sequence ID as a string, with an optional string prefix. One counter belongs to each loaded utils module instance, independent of other libraries. It is not a persistent or globally unique ID. |
## Event-building helpers
Most components should use `Events` directly. These exports support custom event implementations; they do not register subscriptions or create an owner lifetime.
| Export | Arguments and result |
| --- | --- |
| `buildEventArgs(nameOrMap, callback?, context?, listener?)` | Returns descriptors shaped `{ name, callback, context, listener }`. A literal name produces one descriptor; an object produces one for each own enumerable key in key order. Names are not split on whitespace. For map input, the second argument supplies context when the third is `undefined`. |
| `callHandler(callback, context, args?)` | Calls the function with the supplied receiver and an array or `arguments` object, defaulting to no arguments. Returns its result. |
| `onceWrap(callback, offCallback)` | Returns a wrapper that calls `offCallback(wrapper)` before its first callback invocation, forwarding its receiver and arguments. Later calls return the saved result. It is consumed before cleanup, so recursive calls or calls after cleanup/callback failure do not retry. Errors propagate; until a result is saved, repeated calls return `undefined`. |
```js
import { buildEventArgs, callHandler, onceWrap, uniqueId, setProperty, isString } from '@mnjs/utils';
const receiver = { prefix: 'Hello' };
const greet = function(name) { return `${this.prefix}, ${name}`; };
const descriptors = buildEventArgs({ greet }, receiver);
const greeting = callHandler(greet, receiver, ['Ada']);
const removals = [];
const greetOnce = onceWrap(greet, wrapper => removals.push(wrapper));
const first = greetOnce.call(receiver, 'Ada');
const repeated = greetOnce.call(receiver, 'Grace');
const key = uniqueId('component-');
const values = {};
setProperty(values, key, first);
const isLabel = isString(first);
```
## Types and entrypoints
The package supplies ESM, CommonJS, and TypeScript declarations and has no runtime dependencies. Import from `@mnjs/utils`; there are no public helper subpaths. Bundlers can retain imported helpers because the package declares no side effects.
Type exports include `Bindings`, `EventCallback`, `EventMap`, `EventSource`, `EventMethods`, `EventsContract`, `MarionetteErrorInstance`, `MarionetteErrorConstructor`, and the extension composition types `Merge`, `Constructed`, and `CallableParent`. `Bindings` accepts method-name maps whose handlers are checked at runtime. Value-resolution helpers return `unknown`; narrow their results before using them as application values.
```ts
import { getValue, callHandler, type EventMap } from '@mnjs/utils';
const value: unknown = getValue({ label: 'Inbox' }, 'label');
const label: string = typeof value === 'string' ? value : '';
const handlers: EventMap = { open() {} };
const count: number = callHandler((value: number) => value + 1, undefined, [1]);
void [label, handlers, count];
```
[Canonical source](/docs/markdown/docs/packages/utils.md) · [Source identity](/docs/manifest.json)
---
Document: docs/packages/adapters.md
Canonical URL: https://marionettejs.com/docs/packages/adapters/
Markdown URL: https://marionettejs.com/docs/packages/adapters.md
Reading SHA-256: dc3c36d6c2e96e49ddfc804e02dd0bd5ec453adc6856bd685f007a566041777d
# @mnjs/adapters
Optional integrations for existing data sources and DOM libraries. Import the subpath you use; the package has no root export. Imports do not configure Marionette or load other adapters. Install the matching Marionette version and only the peers needed by your integration; see [package installation](/docs/quick-start.md#install-matching-packages).
| Subpath | Default export | Peer used by the application |
| --- | --- | --- |
| `@mnjs/adapters/backbone` | `BackboneApi` | Backbone `^1.3.1`; TypeScript also needs `@types/backbone` `^1.4.23`. |
| `@mnjs/adapters/xstate` | `createXStateActorApi` | XState v5 actors; the integration is tested with `5.33.2`. It uses structural actor interfaces and does not import XState. |
| `@mnjs/adapters/dom/jquery` | `JQueryDomApi` | jQuery `^4.0.0`; TypeScript also needs `@types/jquery` `^4.0.1`. |
| `@mnjs/adapters/dom/morphdom` | `MorphdomDomApi` | Morphdom `^2.7.8`. |
| `@mnjs/adapters/dom/lit-html` | `LitDomApi` | Lit HTML `^3.3.3`. |
Each subpath has ESM, CommonJS, and TypeScript entrypoints. These adapters implement the existing [data/state](/docs/api/providers/data.md) or [DOM](/docs/api/providers/dom.md) contracts. Configure them before constructing consumers. [Runtime configuration](/docs/api/runtime.md) explains application-wide, subclass, and isolated scopes. The examples use subclasses to make the affected classes explicit.
Data and state configuration are independent. Choosing a data adapter does not require changing state, Radio, or DOM providers. See [observable data and API access](/docs/integrations/setup.md#observable-data-and-api-access) when selecting a data solution.
## Backbone
`BackboneApi` implements both DataApi and StateApi without modifying Backbone sources or prototypes. Configure a CollectionView and its child View class when both consume Backbone data. Configure StateApi separately for owners whose state uses Backbone.
Backbone 1.3.1 is the runtime minimum: collection observation reads the added and removed model arrays from the `update` event's `options.changes` payload. Backbone 1.2.3 emits `update` without that payload and cannot supply this contract. The Backbone data adapter does not require jQuery; the jQuery DOM adapter has its own peer requirement.
```js
import Backbone from 'backbone';
import BackboneApi from '@mnjs/adapters/backbone';
import { View } from 'marionette';
const LabelView = View.extend({
template: ({ label }) => label,
modelEvents: { 'change:label': 'render' }
});
LabelView.setDataApi(BackboneApi);
const model = new Backbone.Model({ label: 'Inbox' });
const view = new LabelView({ model }).render();
model.set('label', 'Archive');
```
| Method | Contract |
| --- | --- |
| `key(model)` | Returns `model.cid`, preserving each model's identity independently of its server ID. |
| `get(model, name)` / `has(model, name)` | Read/check own attributes. `get` returns `undefined` when absent; `has` includes attributes whose value is `undefined`. |
| `serialize(model)` | Returns the current `model.attributes` object, without copying it. |
| `models(collection)` | Returns a shallow copy of the ordered `collection.models` array. |
| `subscribe(source, nameOrMap, callback?, context?)` | Uses native Backbone events and preserves their names, payload arguments, and callback context. Returns an idempotent function that removes only these registrations. |
| `observeCollection(collection, notify, context?)` | Translates native collection events into the structural changes below. Returns an idempotent disposer. |
| `disposeOwned(source)` | Does nothing to the source itself. Owner subscriptions have already been released by StateApi cleanup. |
For event maps, use Backbone's map form with the context as the following argument. Standalone subscriptions do not add framework ownership; call the returned disposer when finished.
A standalone `sort` reports `reorder`, retaining surviving child Views and DOM. `update` reports added/removed models; removals destroy those children. Merges retain the model and do not request a child rerender through collection observation—use the child's model events. `reset` reports `reset` and rebuilds children. The adapter treats sort events with truthy `add`, `remove`, or `merge` options as mutation-related and suppresses them. It relies on the `update` emitted by ordinary non-silent additions, removals and merges; reorder-only `set()` and explicit `sort()` do not provide that update.
**Reorder-only limitation:** `collection.set([second, first])` can reorder existing Model instances without adding, removing, or merging any models. Backbone then emits `sort` with its default mutation options but no `update`, so the adapter leaves the CollectionView in its previous order. This replacement occurs with `add` and `remove` enabled when comparator sorting is inactive: no comparator, `sort: false`, or a supplied `at` option. In particular, `at` does not prevent full order replacement under those conditions; it is not a reliable way to move an existing model to a position. Attribute objects normally count as merges and produce an `update`; with `merge: false`, they can also take the reorder-only path.
After a known reorder-only operation, use `collection.trigger('sort', collection, {})` to notify observing CollectionViews of the already-established order. For example, follow `collection.set([second, first])` with that notification when replacing the order of an unsorted collection. The notification does not sort or mutate the collection itself; with default CollectionView ordering (`sortWithCollection: true`), consumers reconcile the current order while retaining surviving child Views. Pass the explicit empty options object; do not reuse the `set()` mutation options. Do not add this notification after every `set()`, since ordinary additions, removals and merges already reconcile through `update`.
For an explicit comparator sort, call `collection.sort()` without forwarding truthy `add`, `remove`, or `merge` flags. Passing those flags suppresses that standalone sort notification too.
Multiple consumers can share a source. Destroying one consumer removes its subscriptions and children while survivors continue receiving updates. Even factory-owned Backbone state is not destroyed by this adapter: source-wide `off()`, `stopListening()`, and persistence-capable `Model.destroy()` are not called.
## XState actors
`createXStateActorApi(options?)` returns a DataApi/StateApi for actors with synchronous snapshots containing an object `context`.
| Option | Contract |
| --- | --- |
| `snapshotEvent` | Optional nonempty event name mapped to `actor.subscribe`. This name is reserved within that adapter. Other event names use `actor.on` and observe emitted events, not events sent to the actor. |
| `select(snapshot)` | Required for collection use; returns ordered, distinct, non-null child actor references from a parent snapshot. Omit for model/state-only use. |
```js
import { assign, createActor, createMachine } from 'xstate';
import createXStateActorApi from '@mnjs/adapters/xstate';
import { View } from 'marionette';
const actor = createActor(createMachine({
context: { label: 'Inbox' },
on: { rename: { actions: assign({ label: ({ event }) => event.label }) } }
})).start();
const ActorApi = createXStateActorApi({ snapshotEvent: 'actor:snapshot' });
const ActorView = View.extend({
template: ({ label }) => label,
modelEvents: { 'actor:snapshot': 'render' }
});
ActorView.setDataApi(ActorApi);
const view = new ActorView({ model: actor }).render();
actor.send({ type: 'rename', label: 'Archive' });
```
`key(actor)` returns the actor reference. `get` and `has` read own context properties; `serialize` returns the current context object without copying. An already-started actor does not replay its current snapshot when subscribed; initial rendering reads `getSnapshot()` directly. Missing synchronous snapshots or non-object contexts raise `TypeError`.
`subscribe(actor, eventName, callback, context?)` delivers the native snapshot or emitted-event payload, with the supplied callback context, and returns an idempotent unsubscribe function. Configure the reserved snapshot name when `modelEvents` or `stateEvents` should observe state changes.
With `select`, the returned adapter also supplies `models(parentActor)` and `observeCollection(parentActor, notify, context?)`. Replace the selected array when membership or order changes. Reusing its identity skips comparison; a fresh array requires a scan, but identical members/order produce no notification. Reorder retains child Views and DOM; removal destroys the removed child. Child context changes belong to child subscriptions. Respawning an actor with the same `id` creates a new model identity.
Observers are independent. Releasing one removes only its subscription. Supplied data and state actors are borrowed and remain active after consumer destruction. An actor returned by `createState()` is owned: subscriptions are released first, then `disposeOwned(actor)` calls `actor.stop()`. The application must stop borrowed actors when their own lifetime ends.
## DOM contents
Lit and Morphdom update contents synchronously and preserve the View's root element. They do not change template evaluation; [Renderer](/docs/api/providers/dom.md#renderer) produces the value that `setContents` receives. Root attributes remain under View control; use `renderAttributes()` to refresh them.
Parent rendering still destroys its Region children before updating contents. Keep Region placeholders empty in templates so only the Region manages their children. Configure a content adapter before the first render, and do not switch content adapters or independently replace their managed contents afterward.
```js
import { View } from 'marionette';
import { html } from 'lit-html';
import LitDomApi from '@mnjs/adapters/dom/lit-html';
import MorphdomDomApi from '@mnjs/adapters/dom/morphdom';
const LitMessage = View.extend({
template: ({ message }) => html`
'
});
MorphMessage.setDomApi(MorphdomDomApi);
const lit = new LitMessage();
lit.message = 'Hello';
lit.render();
const morph = new MorphMessage().render();
```
### Morphdom
`MorphdomDomApi` supplies `setContents(el, html)` for an HTML string, `null`, or `undefined`; nullish values clear the contents. An empty root receives the HTML directly. Existing contents are reconciled with Morphdom's `childrenOnly` behavior and normal matching rules, including element IDs. Matching elements can survive repeated renders; the root always remains. Use trusted HTML or an application template system that handles untrusted values safely.
### Lit HTML
`LitDomApi` supplies `setContents(el, value)`, `notifyAttach(el)`, and `notifyDetach(el)`. Templates can return Lit values such as `html` results. The first explicit render replaces existing contents; it does not hydrate them. Subsequent renders update the same marked range.
The notification methods connect/disconnect Lit directives through the stored root part. Marionette's attachment monitoring calls them when managed through Regions; keep `monitorViewEvents` enabled on the View and its ancestors. If monitoring is disabled or its handlers are removed, the application must deliver these notifications itself. Overrides of lifecycle methods must preserve parent behavior.
Detach and destruction disconnect directive resources. Destruction can also release resources from a failed explicit render; failed construction does not roll back initialization. Release the previous View owner before adopting its element in another View. Lit event handlers use Lit's element receiver; use a closure when a handler needs View or application state.
## jQuery
`JQueryDomApi` overlays these DOM operations; it does not create `$el` or change View ownership. `getUI(name)` returns a jQuery collection under this adapter. If the application needs `$el`, initialize it with `$(this.el)`; the root is fixed for the View's lifetime.
| Method | Contract |
| --- | --- |
| `findEl(el, selector)` | Returns a jQuery collection of descendants. Supports `Element` or `Document` roots. |
| `detachEl(el)` | Detaches the element while retaining jQuery data and listeners. |
| `setContents(el, html)` | Uses jQuery `.html()`; a nullish value clears contents. |
| `appendContents(el, contents)` | Appends accepted jQuery HTML/node/collection content to an Element or DocumentFragment. |
| `detachContents(el)` | Detaches children while retaining their jQuery data and listeners; keeps the root. |
```js
import { View } from 'marionette';
import $ from 'jquery';
import JQueryDomApi from '@mnjs/adapters/dom/jquery';
const Controls = View.extend({
template: () => '',
ui: { open: 'button' }
});
Controls.setDomApi(JQueryDomApi);
const view = new Controls().render();
const button = view.getUI('open');
button.attr('aria-label', 'Open details');
```
To combine jQuery query/attachment operations with Lit or Morphdom contents, call `setDomApi` with jQuery first and the content adapter second. Setters overlay supplied methods; the last supplied implementation wins. Other DOM operations retain their configured defaults.
## TypeScript
The default exports carry their provider method types. XState additionally exports `XStateActorEventOptions` and `XStateActorApiOptions`; adding a typed selector exposes the collection methods in the factory result. Actor/source lifetimes still require runtime ownership decisions.
```ts
import BackboneApi from '@mnjs/adapters/backbone';
import createXStateActorApi, { type XStateActorEventOptions } from '@mnjs/adapters/xstate';
import JQueryDomApi from '@mnjs/adapters/dom/jquery';
import { View } from 'marionette';
const LabelView = View.extend();
LabelView.setDataApi(BackboneApi);
const options: XStateActorEventOptions = { snapshotEvent: 'actor:snapshot' };
const actorApi = createXStateActorApi(options);
const root = document.createElement('div');
root.innerHTML = '';
const buttons: JQuery = JQueryDomApi.findEl(root, 'button');
void actorApi;
```
[Canonical source](/docs/markdown/docs/packages/adapters.md) · [Source identity](/docs/manifest.json)
---
Document: docs/integrations/setup.md
Canonical URL: https://marionettejs.com/docs/integrations/setup/
Markdown URL: https://marionettejs.com/docs/integrations/setup.md
Reading SHA-256: d5a3e20dd05c2a25a97e3d74d803d371fe45aff722a8a993f479021d78a56266
# Setup and data
Continue from the [quick start](/docs/quick-start.md). This page adds `@mnjs/data` for observable data and retains the Lit rendering configuration; those are independent integration choices. Core framework methods are covered in the [API reference](/docs/api.md).
## Add observable data
From the example project, install the data package at the same version as core:
```sh
npm install --ignore-scripts --save-exact @mnjs/data@5.0.0
```
Use a different source with its compatible provider when it better fits your application. Views using plain template data do not require this package.
## Configure once
Import a setup module before constructing Applications or Views:
```js
// setup.js
import { setDataApi, setDomApi, setStateApi } from 'marionette';
import { DataApi, StateApi } from '@mnjs/data';
import LitDomApi from '@mnjs/adapters/dom/lit-html';
setDataApi(DataApi);
setStateApi(StateApi);
setDomApi(LitDomApi);
```
These setters configure the default class family. Standalone reference examples instead use `View.extend(...).setDomApi(...)` or `.setDataApi(...)` to keep their configuration local to a subclass. Both scopes are supported; configure the chosen scope before constructing instances. Use [runtime configuration](/docs/api/runtime.md) for class-specific or isolated setup, and the [provider references](/docs/api/providers/data.md) when connecting a different data solution. `DataApi` connects Models and Collections to Views; `StateApi` connects observable state to [state owners](/docs/api/runtime.md#choose-a-configuration-scope). The renderer calls a template function with its data, and the Lit DOM adapter places the result in the View's element.
```js
import './setup.js';
import { View } from 'marionette';
import { html } from 'lit-html';
export const DetailView = View.extend({
template: ({ title, description }) => html`
${title}
${description}
`,
});
```
Ordinary Lit interpolation renders these values as text, including characters such as `<`. No HTML escaping helper is needed. A View supplied with a Model receives its attributes as template data.
A **provider** implements a Marionette contract such as DataApi, StateApi or DomApi. An **adapter** connects a particular library to that contract; for example, `LitDomApi` adapts Lit rendering. Installing an adapter does not configure it.
## Observable data and API access
`@mnjs/data` provides local observable Models and Collections. Use it alongside an API layer for fetching and persistence, or replace it with another data solution and the corresponding Marionette integration. Models and Collections do not provide an HTTP client, server synchronization, or a complete application data architecture.
For [initial feature data](/docs/api/application.md#prepare-before-showing-ui), an Application can import its API module directly, await the request in `prepareStart`, and return a Collection built from the response attributes. The successful result reaches `onStart`. If the API layer already supplies a compatible observable source, use it directly.
```js
import { Model, Collection } from '@mnjs/data';
const item = new Model({ id: 'alpha', title: 'First item' });
const items = new Collection([item]);
item.set('title', 'Updated item');
```
Use [Model and Collection](/docs/packages/data.md) for attribute updates, membership, identity, events, and local disposal. A View can observe Model changes through `modelEvents`; CollectionView observes membership changes through DataApi. Reading data into a template alone does not subscribe to later attribute changes.
Use [state ownership](/docs/api/shared/state.md) when an Application or View needs its own observable state. With StateApi configured, `createState` can return a Model whose disposal belongs to the owner. Passing `{ state }` borrows an existing source instead.
[Canonical source](/docs/markdown/docs/integrations/setup.md) · [Source identity](/docs/manifest.json)