# Marionette 5.0.0-beta.1 documentation bundle
Version: 5.0.0-beta.1
Channel: next
Publication: beta (published on npm)
Source revision: b06750c507494441f0b2298766b70087e45346a2
Local changes: false
Content SHA-256: 3fe8788a771994d9effd124fee94d7444637a27ec974a5b520ccbf43b55bbec9
Read individual documents for focused tasks. Each section below contains the exact published reading Markdown, including its source identity.
---
Document: docs/choosing-integrations.md
Canonical URL: https://marionettejs.com/docs/choosing-integrations/
Markdown URL: https://marionettejs.com/docs/choosing-integrations.md
Reading SHA-256: 8631640f38413a9035f386b1fb77c162b7c90b5777d8ebc7584b57dca004d684
# Choose integrations without changing the whole stack
An adapter connects a specific capability to Marionette. It does not select the
rest of your application architecture. Keep integrations that already satisfy
the task; add a dependency only when the required behavior needs it.
## Make the decision in this order
1. **Inspect the project.** Read its package versions, initialization code, View
subclasses, and existing adapter configuration. Follow its established
integrations unless the requested change includes replacing them.
2. **Name the missing capability.** Examples: observe model changes, retain DOM
contents across a render, or subscribe to an actor snapshot. “Use an adapter”
is not itself a requirement.
3. **Use the smallest matching integration.** Keep Marionette's defaults for
capabilities that do not need to change. Prefer an existing, verified adapter
over introducing a custom implementation of the same contract.
4. **Check ownership and verification.** Identify who creates the source, who
releases subscriptions, and which behavior demonstrates that the integration
works. Configure it before constructing the affected owners.
For a new application with no integration requirements, start with plain
objects, arrays, native DOM operations, and template functions. Plain data is
not observable: explicitly update the UI when it changes. If the task requires
observable models and ordered collections without an existing provider,
use [`@mnjs/data`](/docs/data-package.md) as the starting choice.
Backbone models and collections are also observable: keep them and select
`BackboneApi` when the application already uses Backbone. “Optional” means
Backbone is not required by core, not that its data is static.
`@mnjs/data` includes DataApi and StateApi implementations; it does not add persistence
or REST synchronization. Choose another provider when a requirement calls for
its additional behavior, such as state-machine actors or an existing persistence
layer.
## Select each capability independently
| Capability | Default | Change it when | Contract |
| --- | --- | --- | --- |
| Read models, serialize data, track collection identity/order, observe entity changes | Plain objects and array snapshots | Views consume another provider's models or collections | [DataApi](/docs/data-api.md) |
| Subscribe to an owner's state and dispose owned state sources | Plain objects with no subscriptions | `stateEvents` must observe a provider, or owned sources need disposal | [StateApi](/docs/state.md#stateapi) |
| Create, query, attach, and update DOM elements | Native browser APIs | Required DOM operations or content updates differ | [DomApi](/docs/dom-api.md) |
| Evaluate a template with serialized data | Call a template function | A template engine needs another evaluation function | [Renderer](/docs/rendering.md#using-a-custom-renderer) |
| Bind View/Behavior `events` and `triggers` declarations | Native delegated DOM events | The binding mechanism itself needs replacement | [EventDelegator](/docs/dom-interactions.md#eventdelegator-adapter) |
| Match URLs and control browser history | None | The application requires routing | [Router integration](/docs/routing.md) |
A state source and a View's model can use different providers. A single provider
may implement both DataApi and StateApi, but configuring one does not configure
the other. Changing DomApi does not change EventDelegator. A template renderer
produces a value; DomApi applies that value to the element.
## Match an existing provider
These entrypoints are supplied by this repository. Check their package and peer
versions against the source revision or release you are using; an older alpha
package may not contain an entrypoint described by current source docs.
| Existing requirement | Integration | Scope and consequence |
| --- | --- | --- |
| Backbone models or collections | `@mnjs/adapters/backbone` as DataApi | Observes Backbone model and collection events while preserving their native vocabulary |
| Backbone state | The same `BackboneApi` object as StateApi | A separate configuration decision from model/collection data |
| XState actor data or state | `@mnjs/adapters/xstate` | Select the actor snapshot event explicitly; collection selectors return stable child actor references |
| jQuery DOM queries or attachment operations | `@mnjs/adapters/dom/jquery` | Does not install jQuery event delegation or create `$el` |
| Morphdom updates to a View's HTML contents | `@mnjs/adapters/dom/morphdom` | Keeps the View root; does not preserve child Views owned by Regions across parent render |
| Lit template results | `@mnjs/adapters/dom/lit-html` | Applies Lit results through DomApi; requires attachment monitoring for directive connection cleanup |
Read the [adapter package guide](/docs/adapters-package.md) for exact
imports, provider constraints, ownership, and setup examples. There is no root
`@mnjs/adapters` export. Import the subpath you use; importing it does not
configure Marionette or select any other adapter.
For example, a View may use Backbone data with native DOM operations and a
plain template function. Adding Morphdom to its content updates would not
require changing its models, state, or router.
## Configure the narrowest appropriate scope
Configure a View subclass when the integration belongs to that component:
```javascript
import { View } from 'marionette';
import BackboneApi from '@mnjs/adapters/backbone';
const AccountView = View.extend({
template: () => '',
modelEvents: { change: 'render' },
onRender() {
this.el.querySelector('.name').textContent = this.model.get('name');
}
});
AccountView.setDataApi(BackboneApi);
```
This configures DataApi for `AccountView` and its subclasses. It does not choose
StateApi or change sibling View classes. Use top-level setters when the whole
application intentionally shares that configuration. Use an
[isolated runtime](/docs/runtime-isolation.md) when independently configured
application surfaces must coexist.
Setters overlay supplied adapter methods. When composing DOM operations,
configure a general adapter such as jQuery before an adapter that replaces
content updates, such as Morphdom. Do not switch content adapters after a View
has rendered. Changing a live object's source contract is an application
migration, not a configuration shortcut.
## Add a custom adapter only for an unmet contract
Before implementing one, write down:
- The required methods and source event payloads, using the relevant contract.
- Stable model identity and ordered collection snapshots, if it is a DataApi.
- Borrowed versus owned sources, idempotent subscription cleanup, and which owner disposes each registration.
- Failure behavior when subscription setup, rendering, or source updates throw.
- A test with two consumers of one source, followed by destruction of one
consumer. The surviving consumer must keep working.
For editable collection children, also verify draft/focus retention when a
model stays the same, and the documented destruction/recreation behavior when
an immutable replacement supplies a different model with the same key.
An adapter's type declaration alone does not establish these runtime behaviors.
## Explain choices to an agent or reviewer
Record the chosen integration once where the application configures it. A useful
decision states the capability, existing constraint, configuration scope, and
verification, for example:
> This feature already uses Backbone models. Configure BackboneApi on its View
> subclasses, retain native DOM events, and verify that model changes render
> once and that destroying one View leaves another observer subscribed.
When several providers meet the same requirements, preserve the existing one.
For a new project, choose the simplest option that meets the stated capability;
ask for a preference only when the choice changes a meaningful product or
maintenance constraint. Do not introduce additional providers just because
examples for them appear beside each other in this guide.
[Canonical source](/docs/markdown/docs/choosing-integrations.md) · [Source identity](/docs/manifest.json)
---
Document: docs/marionette.state.md
Canonical URL: https://marionettejs.com/docs/state/
Markdown URL: https://marionettejs.com/docs/state.md
Reading SHA-256: 4fc7669d0f83247424ba50fefdc741db151703aec66c96d92284d1a51ea3471d
# State sources and StateApi
Keep state with the part of the application that uses it. `Application`,
`MnObject`, `View`, `CollectionView`, and `Behavior` can each hold one state
source. Marionette manages subscriptions and the cleanup described below; the
source provides its own values and mutation API. `Region` does not compose state.
`getState()` always returns the exact source. Core never converts a plain object
into a model, record, Proxy, or observable object.
```javascript
import { Application } from 'marionette';
const App = Application.extend({
createState() {
return { filter: '', selectedId: null };
}
});
const app = new App();
app.getState().filter = 'active';
```
Without a supplied source or custom factory, the first `getState()` call lazily
creates an empty plain object. An owner that never supplies, declares, or asks
for state has no state-source property, subscription, or cleanup registration.
## Borrowed and owned sources
Choose how the state source is created and who disposes it:
- `state` is an already-created, borrowed source. Several owners may borrow the
same source. Destroying one owner releases only its subscriptions and never
disposes the source.
- `createState(options)` is a factory called with the owner as `this` and the
constructor options as its argument. Its result is owned. Owner destruction
releases subscriptions and then calls the selected StateApi's optional
`disposeOwned(source)` hook.
A supplied function is a source, not a factory. Use `createState()` when a
function must be invoked to create a source.
State persists across View and CollectionView render. Application state
persists across stop and restart. Behavior state lasts until that Behavior is
destroyed, and MnObject state lasts until the object is destroyed.
## Plain-object state
Plain objects are the dependency-free default and are intentionally
non-observable. Mutate them with ordinary JavaScript and explicitly render or
call an application method when the UI must update.
```javascript
import { View } from 'marionette';
export const label = new View({
el: document.querySelector('#label'),
model: { name: 'Account' },
template: model => model.name
}).render();
const Disclosure = View.extend({
el() { return document.querySelector('#disclosure'); },
template: () => '',
events: { 'click .toggle': 'toggle' },
createState() { return { open: false }; },
toggle() {
const state = this.getState();
state.open = !state.open;
this.render();
},
onRender() { this.el.dataset.open = String(this.getState().open); }
});
export const disclosure = new Disclosure().render();
```
## StateApi
The public adapter contract is deliberately small:
```javascript
StateApi.subscribe(source, eventName, callback, context);
// returns a cleanup function
StateApi.disposeOwned?.(source);
```
`subscribe` registers handlers for future events. It receives each `stateEvents`
name unchanged and calls the provided callback with the source's native payload.
Every call must return an idempotent cleanup function. Marionette retains it
outside the owner's public event registry and invokes it during destruction.
Therefore calling `owner.off()` cannot disable state-source cleanup.
Subscription setup errors propagate to the caller; event-map registration is
not rolled back.
`disposeOwned` is called only for a `createState()` result, after subscriptions
are released. It is never called for a supplied or declared `state` source.
The default StateApi does not pretend a plain object is observable. Declaring
`stateEvents` for a source it cannot observe throws `MN0037`.
Configure StateApi on the default runtime before constructing its consumers:
```javascript
import { setStateApi } from 'marionette';
setStateApi({
subscribe(source, eventName, callback, context) {
return source.subscribe(eventName, (...args) => callback.apply(context, args));
},
disposeOwned(source) {
source.dispose();
}
});
```
`Application.setStateApi()`, `MnObject.setStateApi()`, `View.setStateApi()`,
`CollectionView.setStateApi()`, and `Behavior.setStateApi()` configure a class
or subclass independently. Repeated configuration overlays only that receiving
class; it does not mutate its parent or sibling classes. StateApi selection is
independent of DataApi selection, though one object may implement both.
## stateEvents
`stateEvents` retains Marionette's declarative event-map shape. Handler names
are resolved on the owner, while event vocabulary and callback arguments belong
to the selected adapter.
```javascript
import { View } from 'marionette';
// Fragment: provide an actor source and its matching StateApi at construction.
const ActorView = View.extend({
stateEvents: {
'actor.transition': 'onTransition'
},
onTransition(snapshot) {
this.el.dataset.phase = snapshot.value;
}
});
```
Changing from one state provider to another may require changing event names.
Marionette does not add universal `get`, `set`, `reset`, `dispatch`, or `send`
methods to state owners.
## Application lifetime
```javascript
import { Application } from 'marionette';
const Session = Application.extend({
createState() { return { phase: 'stopped' }; },
onStart() { this.getState().phase = 'ready'; },
onStop() { this.getState().phase = 'stopped'; }
});
export const session = new Session();
export const sessionState = session.getState();
export const started = await session.start();
export const stopped = await session.stop();
export const restarted = await session.restart();
```
Application readiness remains the only asynchronous lifecycle boundary. Code
that mutates a state source after awaited work must still check the readiness
`AbortSignal` before committing stale results.
## Behavior lifetime
```javascript
import { Behavior, View } from 'marionette';
const Disclosure = Behavior.extend({
events: { 'click .disclosure': 'toggleDisclosure' },
createState() { return { open: false }; },
toggleDisclosure() {
const state = this.getState();
state.open = !state.open;
this.view.render();
},
onRender() {
this.view.el.dataset.disclosureOpen = String(this.getState().open);
}
});
const Settings = View.extend({
el() { return document.querySelector('#settings'); },
behaviors: [Disclosure],
events: { 'click .selection': 'toggleSelection' },
template: () => '',
createState() { return { selected: false }; },
toggleSelection() {
const state = this.getState();
state.selected = !state.selected;
this.render();
},
onRender() {
this.el.dataset.selected = String(this.getState().selected);
}
});
export const settings = new Settings().render();
```
A Behavior that receives its View's source through `state` borrows it. A
Behavior-private `createState()` result is owned only by that Behavior.
## Migration from the v5 alpha State
The experimental concrete `Marionette.State` export was removed from core. For
non-observable local values, return a plain object from `createState()` and use
property access. For reactive values, supply the provider's real source and a
matching StateApi. Do not alias the removed State to another model type.
```javascript
// Before
const state = owner.getState();
state.set('open', true);
```
```javascript
// Plain-object source
const state = owner.getState();
state.open = true;
```
[Canonical source](/docs/markdown/docs/marionette.state.md) · [Source identity](/docs/manifest.json)
---
Document: docs/data.api.md
Canonical URL: https://marionettejs.com/docs/data-api/
Markdown URL: https://marionettejs.com/docs/data-api.md
Reading SHA-256: 1c38638bd95125841e3567665898932994feba558e097e09d0e5a567fd034378
# Data API
Display plain objects and arrays directly, or connect your data library through
`DataApi`. The adapter tells Marionette how to read models, obtain collection
order, and observe changes. Core does not require Backbone-shaped `cid`,
`attributes`, `get`, `models`, or collection event payloads.
The default adapter treats models as plain objects and collections as ordered
arrays. Plain arrays are static snapshots: mutating one does not notify
Marionette. Call `render()` after changing a plain array. Declaring
`modelEvents` or `collectionEvents` for an unobservable plain value throws
`MN0037` instead of manufacturing an event system. Both Backbone models and
collections (through `BackboneApi`) and `@mnjs/data` models and collections
are observable alternatives; preserve an existing provider that meets the task.
```javascript
import { CollectionView, View } from 'marionette';
const ChildView = View.extend({
tagName: 'li',
template: model => model.name
});
const ListView = CollectionView.extend({ childView: ChildView });
const models = [{ name: 'one' }, { name: 'two' }];
const list = new ListView({ collection: models });
list.render();
```
## Adapter contract
An adapter supplies seven methods:
| Method | Purpose |
| --- | --- |
| `key(model)` | Return a stable `Map` key used to associate a model with its child View. |
| `get(model, attribute)` | Read one named value for string comparators and filters. |
| `has(model, attribute)` | Distinguish a missing value from a present value of `undefined`. |
| `serialize(model)` | Return the data passed to a template. |
| `models(collection)` | Return the collection's current ordered model snapshot. |
| `subscribe(entity, eventName, callback, context)` | Subscribe to an application entity event and return an idempotent cleanup function. |
| `observeCollection(collection, callback, context)` | Observe structural collection changes and return an idempotent cleanup function. |
`key()` must remain stable while a model belongs to a CollectionView and must be
unique among the models currently owned by that CollectionView. The default
adapter uses object identity. Adapters for immutable sources may use a stable
source identity instead.
`models()` must return an ordered model snapshot after the source mutation is complete.
Marionette does not mutate that array.
`subscribe()` registers handlers for future events and preserves the source event's
arguments. It must return an idempotent cleanup function. Marionette invokes
that function during explicit undelegation or owner destruction. Subscription
setup errors propagate to the caller; event-map registration is not rolled back.
`observeCollection()` also returns an idempotent cleanup function. Adapters are
responsible for fulfilling these contracts; core does not wrap or validate each
returned cleanup.
`model` and `collection` are opaque adapter references. Only `null` and
`undefined` mean no source; values such as `0`, `false`, and `''` can identify a
source when the configured adapter supports them. Prefer a stable reference
whose `get` and `serialize` methods read current values. Item changes can then
notify existing Views through `subscribe` without replacing their identity.
## Collection observations
`observeCollection()` reports one of three normalized records:
```javascript
{ kind: 'reorder' }
{ kind: 'reset' }
{
kind: 'update',
added: [],
removed: [],
updated: [
{ previous: previousModel, current: currentModel }
]
}
```
`reorder` means model order changed without membership changing. `reset` means
Marionette must rebuild every child. `update` supplies exact added and removed
model instances. Each `updated` entry contains the previous and current model for
one stable key. For an in-place update, `previous === current`. For an immutable
same-key replacement, they are different objects. This distinction lets core
distinguish a safe in-place render from an identity replacement. Marionette
destroys and recreates the child View for an immutable same-key replacement so
constructor options, `initialize`, Behaviors, entity events, and other
model-dependent state all belong to the current object. Marionette constructs
every same-key replacement View before removing any existing child. A
replacement-construction or rendering failure propagates to the caller. Core
does not undo a partial update or promise recovery on the next notification. See
[synchronous failures](/docs/lifecycle.md#synchronous-failures).
An in-place `updated` entry requests a child render. Adapters for mutable models
with their own change events can leave `updated` empty and let child
`modelEvents` handle rendering. The Backbone adapter follows this approach:
merges still update collection order and filtering, without rendering children
again after their model events have run.
If a child was removed, detached, or destroyed while its model remained in the
source, updates for that model do not recreate its View. Other children continue
to update. Rendering the CollectionView again or a source reset recreates children
from the current source.
An immutable same-key replacement belongs only in `updated`, not in `removed`
and `added`. Replacing a model with one that has a different stable key is a
removal plus an addition; changing the key of a retained model is invalid. The
post-mutation `models()` snapshot is authoritative and must agree with the
record. Missing, duplicate, or unstable snapshot keys throw `MN0039`. Adapters
must supply correct change records; core uses those records directly instead of
recalculating the change to validate them. Added children follow the current
snapshot order; removed children follow the previous snapshot order, regardless
of their order in the change record.
Observers may notify synchronously from CollectionView lifecycle hooks. Core
captures each source snapshot and drains nested notifications in order, so each
queued update uses the source state that accompanied it.
All three record types enter one CollectionView reconciliation path. Additions
create only their child Views; removals destroy only theirs; reorder moves
survivor elements without rerendering them; and reset is the explicitly
destructive whole-list operation. Presentation comparators may sort the child
Views independently of the source's canonical order.
## Configuring an adapter
Configure the application before constructing Views. In this configuration
fragment, `MyDataApi` is the adapter your application supplies:
```javascript
import { setDataApi } from 'marionette';
setDataApi(MyDataApi);
```
`setDataApi()` overlays the supplied own enumerable methods onto both `View`
and `CollectionView`. `View.setDataApi()` and `CollectionView.setDataApi()` can
configure a subclass independently. A CollectionView and its child View class
must use compatible adapters.
Behaviors use their owning View's adapter. Views and Behaviors work with the
original model or collection, and event callbacks receive the source's native
arguments. DataApi does not wrap application sources. Templates receive the
data prepared by `serializeModel()` or `serializeCollection()`; see
[Rendering](/docs/rendering.md).
DataApi and [StateApi](/docs/state.md#stateapi) are selected
independently. One adapter object may implement both contracts, but configuring
one role never selects the other.
## XState actors
`@mnjs/adapters/xstate` supports a parent XState v5 actor whose selected
ordered collection contains stable child actor references. The adapter uses
the actor reference itself as `DataApi.key()`, reads and serializes the child
actor's current `snapshot.context`, and observes the parent through its snapshot
subscription. A stopped and respawned actor is therefore a new model identity,
even if it uses the same actor `id`.
The following configuration fragment assumes `parentActor` is an already-created
actor whose `context.children` contains stable child actor references. The
application owns actor creation, startup, and eventual shutdown.
```javascript
import createXStateActorApi from '@mnjs/adapters/xstate';
import { CollectionView, View } from 'marionette';
const XStateActorApi = createXStateActorApi({
select: snapshot => snapshot.context.children,
snapshotEvent: 'actor:snapshot'
});
const ChildView = View.extend({
template: context => context.label,
modelEvents: {
'actor:snapshot': 'render',
announced: 'onAnnounced'
},
onAnnounced(event) {
console.log(event.label);
}
});
const ListView = CollectionView.extend({ childView: ChildView });
ChildView.setDataApi(XStateActorApi);
ListView.setDataApi(XStateActorApi);
const view = new ListView({ collection: parentActor }).render();
```
`snapshotEvent` is optional and has no implicit default. When configured, that
exact event-map name observes `actor.subscribe()` snapshots. Every other name
is passed unchanged to `actor.on()` and observes an explicitly emitted event;
events sent to the actor are not surfaced automatically. The selected snapshot
array should retain its reference for unrelated parent transitions. A newly
subscribed observer does not receive an already-started actor's current
snapshot, so initial template data comes from `getSnapshot()`.
`select` is required when the result configures a CollectionView. Omit it when
only actor model reads, `modelEvents`, or `stateEvents` are needed; that result
does not define the collection-only `models()` and `observeCollection()` methods.
Set the same adapter on `StateApi` when `stateEvents` should use this event
vocabulary. Supplied actors are borrowed and never stopped by Marionette. An
actor returned from `createState()` is owned and is stopped only after its
Marionette-managed subscriptions are released. The adapter never traverses or
stops child actors.
## Optional `@mnjs/data` sources
Install `@mnjs/data` with `marionette` when an application wants a small
first-party observable Model and ordered Collection without Backbone:
```sh
npm install marionette@5.0.0-beta.1 @mnjs/data@5.0.0-beta.1
```
```javascript
import { CollectionView, setDataApi, setStateApi, View } from 'marionette';
import { Collection, DataApi, Model, StateApi } from '@mnjs/data';
setDataApi(DataApi);
setStateApi(StateApi);
const RowView = View.extend({
tagName: 'li',
template: () => '',
modelEvents: { change: 'render' },
onRender() {
this.el.textContent = this.model.get('label');
}
});
const state = new Model({ selectedId: null });
const collection = new Collection([{ id: 1, label: 'one' }]);
const list = new CollectionView({
tagName: 'ul', childView: RowView, collection, state
}).render();
// Mount list.el in the application's chosen container.
collection.get(1).set('label', 'updated'); // The existing row now shows "updated".
```
Unless `{ silent: true }` is passed, the package Collection emits synchronous
`update`, `sort`, and `reset` events. The adapter translates them directly to
normalized records. There is no separate observer queue, coalescing, or replay.
Finish one structural mutation before starting another; schedule mutations from
collection listeners or child lifecycle handlers after the current notification
returns. Listener errors propagate and abort delivery.
`move(modelOrId, index)` supports explicit list ordering without remove/add
notifications or child View recreation. It and `sort` emit `sort`. Ordinary
attribute changes use `model.set()` and child `modelEvents` bindings.
The native adapter keys models by stable `cid`, so changing an application id
does not replace its child View. Collection lookup uses current ids. Reset
rejects duplicate instances and ids before changing membership; applications
should keep ids unique when changing them.
Lookup precedence is exact member instance, application id, then cid, regardless
of collection order. Supplied native Model instances retain their identity even
when the Collection configures a different model constructor; only raw attributes
use that constructor. Bulk removal resolves all identities against one current
snapshot, including ids changed with `{ silent: true }`.
`Model.destroy()` and `Collection.destroy()` always emit their `destroy`
lifecycle events, including with `{ silent: true }`. A destroyed model removes
itself from each containing Collection through ordinary event subscriptions.
Destroying a Collection releases its subscriptions without destroying its models.
Use `Model.toObject()` for a shallow attribute copy and `Collection.toArray()`
for an array of plain attribute objects. Template serialization reads attributes
independently. The native package does not implement `toJSON`; pass these plain
values to `JSON.stringify` explicitly.
Define Model subclass `defaults` on the prototype with `Model.extend`, a prototype
method, or a prototype getter; a native class field initializes too late to seed
the base constructor. The package does not provide persistence, REST
synchronization, validation, or implicit Backbone behavior.
Native Model writes use `Object.is` equality and report sparse `changed` and
`previous` maps on their event options. Nested writes are independent synchronous
changes; use `options.changed` for the event being handled, since `model.changed`
may already describe a nested write. `has` tests own-property presence, including
undefined values. Native collection sorting is explicit and `reset` rebuilds
children; there is no automatic merge/reconcile operation. See the package's
[mutation semantics](/docs/data-package.md#mutation-semantics) for details.
Applications using Backbone should import the bundled integration instead of
configuring these methods individually. See [Optional Backbone](/docs/backbone.md).
[Canonical source](/docs/markdown/docs/data.api.md) · [Source identity](/docs/manifest.json)
---
Document: docs/dom.api.md
Canonical URL: https://marionettejs.com/docs/dom-api/
Markdown URL: https://marionettejs.com/docs/dom-api.md
Reading SHA-256: dea982ba190a485d792abec23e715bacab1a0bdd7dc1789dbff14785557c9709
# The DOM API
Marionette uses a small DOM adapter for element creation, selection, attributes,
content, and attachment operations. The default `DomApi` uses native browser
APIs and does not require Backbone or jQuery.
`View`, `CollectionView`, and `Region` expose their adapter as `Dom`. A custom
adapter can replace only the operations an application needs; all omitted
methods continue to use the inherited adapter.
A renderer evaluates templates; `Dom.setContents` applies their output. The optional
[Morphdom and Lit HTML DOM adapters](/docs/rendering.md#rendering-to-dom)
preserve the selected DomApi; installing one does not select a data or state
adapter.
## Element and selector boundaries
`View` and `CollectionView` own a concrete DOM element. Their `el` option must
be a DOM element. Resolve a selector
at the call site when a View should reuse existing markup:
```javascript
import { View } from 'marionette';
const view = new View({
el: document.querySelector('#content')
});
```
`Region` retains selector resolution because a Region locates its managed
element relative to its `parentEl` or the document. `View#$()` and Region
selector lookup both delegate to `DomApi.findEl`. With the native adapter,
`View#$()` returns a `NodeList`. `Region#getEl` selects the first result and
returns that native DOM element. This Region return contract does not change
when `findEl` is supplied by the optional jQuery adapter.
The v4 `DomApi#getEl` method is removed. DOM adapter overrides should implement
`findEl(context, selector)` with an array-like result. Region `getEl` overrides
are a separate extension point and must return one native DOM element.
## Native API methods
The exported `DomApi` contains the following methods. This list is checked
against the shipped package in CI.
### `createElement(tagName)`
Creates and returns a DOM element with `document.createElement(tagName)`.
Marionette uses it when a View does not receive an `el`.
### `createBuffer()`
Creates and returns a `DocumentFragment` for collecting DOM nodes before one
append operation.
### `getDocumentEl(el)`
Returns `el.ownerDocument.documentElement`. Marionette uses that document root
when determining whether a View is attached. Elements inside template content may
have an owner document without a document element; Marionette treats that missing
root as detached.
### `findEl(el, selector)`
Finds descendants of `el` matching `selector`. The native adapter returns the
`NodeList` produced by `el.querySelectorAll(selector)`.
### `hasEl(el, childEl)`
Reports whether `childEl` is attached beneath `el`. Marionette uses this for
attachment-state checks.
### `detachEl(el)`
Removes `el` from its parent when it has one. Native listeners attached to the
element remain on the detached element.
### `replaceEl(newEl, oldEl)`
Replaces `oldEl` with `newEl` when `oldEl` has a parent. Passing the same
element twice or an unattached `oldEl` is a no-op.
### `moveEl(el, parent, before)`
Moves `el` within `parent` before the optional reference node. The native
adapter uses `moveBefore` for already-attached children when available so
CollectionView reordering and swapping preserve focus, selection, media, and custom-element
connection state. It falls back to `insertBefore` for initial attachment and
older DOM implementations; the CollectionView render pass restores focused text selection after
that fallback, while older platforms may still run custom-element connection
callbacks for the move. `swapChildViews()` does not restore focus or selection
when it uses the `insertBefore` fallback without a child-render pass.
### `setContents(el, html)`
Replaces the contents of `el` by assigning `html` to `el.innerHTML`.
`null` and `undefined` produce empty contents.
### `setAttributes(el, attrs)`
Applies own enumerable string keys from `attrs` as DOM attributes using
`setAttribute`. Use attribute names such as `class` and `for`. View-level
`className` is converted to `class` before this method is called.
An explicit `null` removes an attribute. An `undefined` value or omitted key
leaves the existing attribute untouched. Other values use the browser's string
conversion, including `false`, `0`, and an empty string. For boolean HTML
attributes, use `disabled: isDisabled ? '' : null`: the string `"false"` still
means the attribute is present. ARIA and data attributes can use `false` to set
`"false"`.
This method does not assign JavaScript properties. Set live form values or
custom element properties explicitly on the element; `value` and `checked`
attributes describe input defaults. Attribute changes still have the browser's
normal effects on reflected properties.
When `View` or `CollectionView` creates an element, `id` and `className`
declarations override matching entries in `attributes`.
[`View#renderAttributes()`](/docs/view.md#refreshing-root-attributes)
applies the current declarations to an existing element without tracking prior
keys. Custom DomApi adapters must preserve explicit-null removal and leave
undefined and omitted entries untouched.
### `appendContents(el, contents)`
Appends the DOM node or `DocumentFragment` in `contents` to `el`.
### `hasContents(el)`
Returns whether `el` exists and has child nodes.
### `detachContents(el)`
Removes all children by assigning an empty string to `el.textContent`. This is
the fast, jQuery-free default.
### `notifyAttach(el)`
Notify the adapter that its element's contents are active. Called through View
attachment monitoring and when construction adopts an attached root. The
native implementation does nothing; Lit reconnects its directives.
### `notifyDetach(el)`
Notify the adapter that its element's contents are inactive. Called through View
detachment monitoring. This notification does not remove or empty the element.
The native implementation does nothing; Lit disconnects its directives while retaining its rendered contents.
These hooks receive only the element. They follow the existing attachment
monitoring opt-out: with `monitorViewEvents: false` or monitoring handlers
removed, applications must deliver the notifications they need themselves.
This includes destruction: `destroy()` still removes the View and its owned
resources, but does not separately disconnect adapter-managed contents when
attachment monitoring is disabled. An application rendering Lit into an attached
root with monitoring disabled must notify `notifyDetach(el)` when releasing that root.
`detachContents(el)` remains the operation for physically emptying an element.
## Using the default API
The native adapter is exported for direct use and for restoring native methods
inside a customized class:
```javascript
import { DomApi, View } from 'marionette';
const NativeView = View.extend();
NativeView.setDomApi(DomApi);
```
## Providing a custom API
The root `setDomApi` function overlays methods for `View`, `CollectionView`,
and `Region`:
```javascript
import { setDomApi } from 'marionette';
import MyDomApi from './my-dom-api.js';
setDomApi(MyDomApi);
```
Use a class setter when only one class or subclass needs the override. The
setter creates a shallow adapter overlay for that class, so a partial override
retains every other currently configured method. The current adapter and supplied overlay
contribute own enumerable string and symbol properties. Inherited and
non-enumerable properties are ignored.
```javascript
import { View } from 'marionette';
export const PlainTextView = View.extend({
template() {
return 'Literal markup';
}
});
PlainTextView.setDomApi({
setContents(el, html) {
el.textContent = html;
}
});
export function renderPlainText() {
const view = new PlainTextView();
view.render();
return view;
}
```
`PlainTextView` uses the custom `setContents`, while `View` and unrelated View
subclasses retain their existing adapters. `CollectionView`, `Region`, and
`View` each support this class-level pattern.
## Optional jQuery adapter
Applications that rely on jQuery DOM bookkeeping can install jQuery and opt in
at application boot:
```javascript
import { setDomApi } from 'marionette';
import JQueryDomApi from '@mnjs/adapters/dom/jquery';
setDomApi(JQueryDomApi);
```
The optional adapter overrides `findEl`, `detachEl`, `setContents`,
`appendContents`, and `detachContents`. `View#$()` consequently returns a jQuery
collection. If application code also needs `$el`, initialize it once:
```javascript
import $ from 'jquery';
import { View } from 'marionette';
const JQueryView = View.extend({
initialize() {
this.$el = $(this.el);
}
});
```
The root is fixed at construction, so the wrapper remains valid through rendering
and detach/reattach. CollectionViews and Behaviors can initialize `$el` the same
way. `$el` is application-owned; the adapter has no wrapper or View setup API.
The native adapter does not create `$el`. The jQuery adapter does not replace
Marionette's event delegator, restore Backbone.View inheritance, or allow
selector strings as a View `el`. Configure those concerns separately when an
application actually requires them.
Prefer the native adapter for new applications. Use
`@mnjs/adapters/dom/jquery` only for an existing integration that depends on
jQuery selection, content, or detach semantics.
[Canonical source](/docs/markdown/docs/dom.api.md) · [Source identity](/docs/manifest.json)
---
Document: docs/dom.prerendered.md
Canonical URL: https://marionettejs.com/docs/prerendered-dom/
Markdown URL: https://marionettejs.com/docs/prerendered-dom.md
Reading SHA-256: ebfe3cf3e97c1abb6295c03ad8734f9eba845c026e6fed9a548a64a42c34c2fd
# Prerendered Content
View classes can be initialized with pre-rendered DOM.
This can be HTML that's currently in the DOM:
```javascript
import { View } from 'marionette';
const myView = new View({ el: document.querySelector('#foo-selector') });
myView.isRendered(); // true if '#foo-selector' exists and has content
myView.isAttached(); // true if '#foo-selector' is in the DOM
```
Or it can be DOM created in memory:
```javascript
import { View } from 'marionette';
const inMemoryHtml = document.createElement('div');
inMemoryHtml.textContent = 'Hello World!';
const myView = new View({ el: inMemoryHtml });
```
In both of the cases at instantiation the view will determine
its state as to whether the el is rendered
or attached.
**Note** `render` and `attach` events will not fire for the initial
state as the state is set already at instantiation and is not changing.
## Managing `View` children
With `View`, the `render` event is usually the best place to show child views for
efficient nested rendering.
However with pre-rendered DOM you may need to show child views in `initialize`
as the view will already be rendered.
```javascript
import { View } from 'marionette';
import HeaderView from './header-view';
const MyBaseLayout = View.extend({
regions: {
header: '#header-region',
content: '#content-region'
},
el() {
return document.querySelector('#base-layout');
},
initialize() {
this.showChildView('header', new HeaderView());
}
});
```
### Managing a Preexisting View Tree
It may be the case that you need child views of already existing DOM as well.
Query the existing DOM for each child's element. A Region declared with a
selector may still hold that selector in `region.el` before its first show;
`getRegion()` does not resolve it. Query from the owning View's concrete `el`:
The page contains this existing markup before the module runs:
```html
Existing account
Hello again.
' }); MessageView.setDomApi(MorphdomDomApi); ``` The template returns an HTML string containing the View's contents. Morphdom matches children using its normal rules, including element IDs. The adapter installs HTML directly into an empty root and morphs existing contents using `childrenOnly`, leaving the root's attributes under Marionette's control. Use `renderAttributes()` to refresh those attributes. ### Lit HTML ```sh npm install marionette@5.0.0-beta.1 @mnjs/adapters@5.0.0-beta.1 lit-html ``` ```js import { View } from 'marionette'; import { html } from 'lit-html'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; const MessageView = View.extend({ template: ({ message }) => html`${message}
`, templateContext: { message: 'Hello again.' } }); MessageView.setDomApi(LitDomApi); ``` Configure a View subclass before creating its instances. Further subclasses inherit the adapter. Neither DOM adapter modifies View methods or needs a View reference: template evaluation stays in the renderer and the returned value goes to `Dom.setContents(el, value)`. Lit async directives can own subscriptions and other resources. Marionette calls `Dom.notifyAttach(el)` and `Dom.notifyDetach(el)` through its existing attachment monitoring. Lit translates these notifications to its directive connection API. Detaching and destroying a View disconnects its directives while preserving the View root. A View keeps its initial element for its lifetime. Destroying an already constructed View also disconnects resources created before an explicit render failed. Failed construction does not roll back initialization. Keep `monitorViewEvents` enabled on the View and its ancestors and manage attachment through Regions. If you disable monitoring or remove its handlers with `off()`, the application must call the adapter's attachment methods itself. There is no separate hidden cleanup listener. Lifecycle overrides must call parent methods, as with other Marionette lifecycle overrides. The first explicit render replaces preexisting contents; this is not hydration. Subsequent renders update Lit's marked range. A disconnected element can be adopted by another View using the same adapter without erasing its contents. Release the previous owner first; one element cannot have two active View owners. Lit event handlers use Lit's normal element receiver; use closures when a handler needs application or View state. Do not independently replace Lit's contents or switch content adapters after rendering. [Canonical source](/docs/markdown/packages/adapters/readme.md) · [Source identity](/docs/manifest.json)