<!-- Documentation snapshot: package 5.0.0-beta.1; channel next; base revision b06750c507494441f0b2298766b70087e45346a2; local changes false; original source SHA-256 81bd678c7d498e08c08c84cfc3bbe1d27451ef46ed521e67fff4f72450bdbadb. -->

# DOM Interactions

Marionette `View` and `CollectionView` instances manage DOM interactions through
a root DOM element, `el`. Core uses the browser DOM API by default: `view.$()`
and bound `getUI()` values are native `NodeList` instances, and delegated
handlers receive native DOM events.

`View`, `CollectionView`, and `Behavior` use the public EventDelegator runtime
adapter described below. Core provides a native DOM adapter by default.

## DOM Ownership Boundaries

Use these boundaries when deciding where DOM work belongs:

* The external shell chooses where a root View is mounted. Pass a concrete DOM
  element as `el`, or append the View's generated `el` to the shell's mount.
* A View owns its `el` and the nodes produced by its template.
* A Behavior borrows its host View's DOM boundary. It does not own a separate
  root; see [Behavior host communication](/docs/behavior.md#host-communication-and-event-proxies).
* The external shell or owning View owns the DOM element used as a Region mount.
  The Region manages the placement and lifecycle of its current child View at
  that mount. Use the [View Region APIs](/docs/view.md#laying-out-views---regions)
  to show, access, detach, or empty that child.
* A child View owns its own `el` and handles interactions inside it.

DOM scoping is structural, not ownership-aware. `view.$()`, `ui`, and delegated
selectors are rooted at `view.el`, so they exclude matching elements outside
that root. They can still match a descendant owned by a child View. Do not use a
parent query such as `parentView.$('.child-control')` to manipulate child-owned
DOM. Give each owner distinct selectors and communicate across View boundaries
through public View or Region APIs and
[explicit child events](/docs/events.md#child-view-events).

## Canonical View Interaction

The example below defines selectors once in `ui`, handles a save click through
`events`, and translates a close click into the `form:close` View event through
`triggers`.

<!-- executable-example: view-dom-interactions -->
```javascript
import { View } from 'marionette';

export const FormView = View.extend({
  template() {
    return `
      <form>
        <button class="save" type="button">Save</button>
        <button class="close" type="button">Close</button>
      </form>
    `;
  },

  ui: {
    save: '.save',
    close: '.close'
  },

  events: {
    'click @ui.save': 'onSave'
  },

  triggers: {
    'click @ui.close': 'form:close'
  },

  onSave(event) {
    const [saveButton] = this.getUI('save');

    saveButton.disabled = true;
    this.triggerMethod('form:save', this, event);
  },

  onFormClose(view) {
    view.el.dataset.closed = 'true';
  }
});
```

Create and render the View before accessing its bound UI elements:

```javascript
const formView = new FormView();

formView.render();
document.querySelector('#form-host').append(formView.el);
```

The shell owns `#form-host`; `formView` owns the generated `formView.el` inside
it. Destroy the View when the shell is finished with it so delegated handlers
and other owned resources are cleaned up.

## View `events`

The `events` attribute delegates DOM events from the View's `el` to functions or
methods on the View. A key has this shape:

```javascript
'<dom event> [CSS selector]': 'methodName'
```

The CSS selector is optional. Without one, the handler is bound to the View's
root `el`. Use `@ui.<name>` in place of a literal selector to reference a
declared `ui` key, as the canonical example does with `@ui.save`.

The handler receives the native DOM event as its first argument and runs with
the View as its context. An `events` value must be a function or a string that
resolves to a callable method. Invalid handlers throw `MarionetteError` with
code [`MN0019`](/docs/diagnostics.md#look-up-a-code) before Marionette delegates any handler from
that event map.

Delegation sees matching descendants throughout `el`. If a child View contains
the same selector, its bubbling DOM event can reach the parent handler. Prefer
owner-specific selectors; use Marionette events for parent-child communication
instead of relying on DOM bubbling across ownership boundaries.

Call `view.delegateEvents(events)` to refresh delegated DOM handlers after
changing a callable `events` or `triggers` definition. UI references use the
View's current selector bindings; a Behavior retains the selector map captured
at construction, as described in [Behavior UI resolution](/docs/behavior.md#ui-resolution-and-binding). A supplied event
map replaces only the View's configured `events` for that delegation pass;
View triggers and Behavior events and triggers remain active. The method first
removes existing handlers, so repeated calls do not duplicate them.
`view.undelegateEvents()` removes the View and Behavior DOM handlers. Both
methods return the View, and both are no-ops after destruction has started.
Construction calls `delegateEvents()`. A subclass override remains responsible
for delegating to the base method when it wants Marionette's cleanup and redelegation.

## EventDelegator Adapter

An EventDelegator owns how one normalized `events` or `triggers` declaration is
registered and removed. Marionette still owns declaration resolution, handler
context, UI normalization, and the timing of registration and cleanup.

Configure every View, CollectionView, and Behavior class with the root setter:

```javascript
import { setEventDelegator } from 'marionette';

setEventDelegator(MyEventDelegator);
```

Or configure one class hierarchy through its static setter:

```javascript
const InstrumentedView = View.extend({});
InstrumentedView.setEventDelegator(MyEventDelegator);
```

The supplied object is a complete adapter, not a partial overlay. It must
provide this method. This example retains native selector and focus behavior;
an instrumentation adapter could record around the same registration:

<!-- executable-example: event-delegator-adapter -->
```javascript
export const CustomEventDelegator = {
  delegate({ eventName, selector, handler, rootEl }) {
    const capture = eventName === 'focus' || eventName === 'blur';
    const listener = selector ? event => {
      const target = event.target.nodeType === 1 ?
        event.target : event.target.parentElement;
      const match = target && target.closest(selector);

      if (match && match !== rootEl && rootEl.contains(match)) {
        event.delegateTarget = match;
        return handler(event);
      }
    } : handler;

    rootEl.addEventListener(eventName, listener, capture);
    return () => rootEl.removeEventListener(eventName, listener, capture);
  }
};
```

The arguments are:

* `eventName`: the first token in the declaration key. Begin the key with the
  event name, without leading whitespace.
* `selector`: the remaining selector, or an empty string for a direct handler.
* `handler`: Marionette's normalized callback. The adapter must preserve its
  arguments and return behavior.
* `rootEl`: the View or CollectionView's current `el`. A Behavior receives its
  host View's current `el`.

`delegate` must return an idempotent cleanup function that removes exactly the
registration it created, including its original root, listener, namespace, and
capture/options policy. Marionette owns and stores that opaque cleanup. The
adapter must not mutate View internals.

Marionette invokes the returned cleanups during redelegation or destruction,
in reverse registration order. Registration and cleanup errors propagate to the
caller and stop the operation. Core does not roll back failed registration or
attempt remaining cleanup after a callback throws. See the shared
[synchronous failure boundary](/docs/lifecycle.md#synchronous-failures).

`setEventDelegator` requires an adapter with a callable `delegate` method.
Each registration must return a working cleanup. The TypeScript contract
checks these shapes; core trusts the configured adapter.

Adapter selection occurs at registration time. Changing a global or per-class
adapter does not reinterpret existing registrations; their original opaque
cleanups remain authoritative. The newly configured adapter is used the next
time declarations are delegated, including a new instance, an explicit
`delegateEvents()` call. A per-class setter creates an own
adapter override for that class hierarchy, so a later root setter does not
replace it.

The native adapter uses `addEventListener`. Selector declarations walk from a
text or element target to the closest matching descendant of `rootEl` and set
`event.delegateTarget` to that match. Native event names are literal:
namespaces such as `click.menu` are not interpreted, and non-bubbling events
such as `mouseenter` are not emulated.

Delegated native `focus` and `blur` use capture because those events do not
bubble. The delegated handler therefore runs before a target-element listener.
A Marionette trigger stops propagation by default, which prevents the event
from reaching that target listener. Set `stopPropagation: false` on that
trigger when the target must also observe the focus or blur event; the
Marionette trigger still runs first. Marionette does not silently translate
these declarations to `focusin` or `focusout`.

A jQuery adapter can implement the same protocol with paired `.on()` and
`.off()` calls. Compatibility tests exercise that protocol, but v5 does not yet
ship a jQuery EventDelegator. A custom adapter is needed only when the
application requires jQuery-specific namespaces, programmatic dispatch, and
delegated focus behavior without adding jQuery to the core production graph.
React and Vue normally own events within the subtree they
render; integrate those subtrees through explicit DOM and lifecycle ownership
boundaries instead of replacing Marionette's EventDelegator with a React or Vue
adapter.

## View `triggers`

The `triggers` attribute translates a DOM event into a Marionette View event.
In the canonical example, clicking the close button emits exactly
`form:close`. Listeners and the matching `onFormClose` method receive the
triggering View first, followed by the native DOM event.

By default, a trigger calls `preventDefault()` and `stopPropagation()` on the
DOM event. Configure either behavior for one trigger with an object:

```javascript
triggers: {
  'click @ui.close': {
    event: 'form:close',
    preventDefault: true,
    stopPropagation: false
  }
}
```

These settings are local to the configured trigger. Selectors remain scoped only
by the View's root `el`.

For a child owned through a Region, automatic parent handling and forwarding is
opt-in. `childViewEvents` calls a configured parent handler,
`childViewTriggers` re-emits a configured parent event, and a non-false
`childViewEventPrefix` forwards prefixed events. A parent may instead subscribe
directly with public [`listenTo(childView, ...)`](/docs/events.md#listening-to-events),
but that is an explicit subscription rather than automatic bubbling. See
[Child View Events](/docs/events.md#child-view-events) for the configured contracts.

## Organizing a View with `ui`

The `ui` attribute gives frequently used CSS selectors stable names:

```javascript
ui: {
  save: '.save',
  close: '.close'
}
```

When Marionette iterates a UI definition for binding, or a map passed to a UI
normalization helper, it uses own enumerable string keys in standard JavaScript
own-key order. Inherited, symbol, and non-enumerable properties are ignored by
those iterations, and a numeric `length` is an ordinary key rather than an
array-like signal. Arrays, sparse arrays, and other array-like values are not
supported as UI maps. A literal own `__proto__` key remains an own entry in
normalized and bound UI maps without changing either map's prototype. Direct
`@ui.<name>` lookup follows the own-declaration contract described below and
does not require the declared selector property to be enumerable.

When the View renders, Marionette queries each selector within `view.el` and
replaces the configured string with the resulting collection. With the default
DOM API, `view.getUI('save')` and `view.ui.save` are native `NodeList`
instances. Marionette rebinds those collections to replacement nodes after
each render.

Use `getUI(name)` after declaring a `ui` map and binding its elements when
application code needs a named element. Calling it without a declared map,
before binding, or after unbinding throws
`MarionetteError` with code [`MN0023`](/docs/diagnostics.md#look-up-a-code). Once bound, a missing
key preserves the existing `undefined` result. Use the `@ui.<name>`
form in `events`, `triggers`, Behaviors, and Regions so a selector change has one
source of truth.

Every `@ui.<name>` reference must contain a non-empty name for an own, declared
key in the applicable `ui` map. Missing or inherited keys throw
`MarionetteError` with code [`MN0018`](/docs/diagnostics.md#look-up-a-code) during normalization.
Selector values must be strings. An own key with `undefined` is not diagnosed
as missing by core; do not rely on a particular result for that unsupported value.
An explicitly declared empty selector is a known key, though the DOM API may
reject it when the selector is used.

## Optional jQuery DOM Adapter

Applications that explicitly configure
[`@mnjs/adapters/dom/jquery`](/docs/installation.md#jquery-dom-adapter-is-optional)
before constructing Views receive jQuery collections from query methods. The
[application-owned `$el` setup](/docs/dom-api.md#optional-jquery-adapter) can add a
wrapper on View, CollectionView, and Behavior subclasses; no base-class helper
is exported. Core examples use native
collections so the default package remains jQuery-free.


[Canonical source](/docs/markdown/docs/dom.interactions.md) · [Source identity](/docs/manifest.json)
