API reference

5.0.0-beta.1 · Published beta · b06750c5. Published on npm. Match your installed version.

Marionette Events#

Marionette provides its own Events primitive for communication between objects. It is exported from marionette, mixed into every Marionette class, and does not require Backbone. These object events are separate from DOM events.

Documentation Index#

Triggering and Listening to Events#

Use the Events export directly when a plain object needs Marionette's event API, or use the same methods already present on a Marionette class.

import { Events, MnObject } from 'marionette';

const emitter = Object.assign({}, Events);
const listener = new MnObject();

listener.listenTo(emitter, 'status:changed', status => {
  console.log(status);
});

emitter.trigger('status:changed', 'ready');
listener.stopListening(emitter);

Events API#

Method Purpose
on(name, callback, context?) Register a callback on this object.
off(name?, callback?, context?) Remove matching callbacks registered with on.
trigger(name, ...args) Trigger one or more named events.
once(name, callback, context?) Register a callback that is removed after its first call.
listenTo(object, name, callback) Listen to another emitter while tracking the relationship on this object.
stopListening(object?, name?, callback?) Remove relationships created with listenTo or listenToOnce.
listenToOnce(object, name, callback) Listen to another emitter once.
triggerMethod(name, ...args) Trigger an event and call its matching onEventName method.

trigger, on, off, once, listenTo, listenToOnce, and stopListening accept space-separated event names. triggerMethod delegates to trigger for listener notification, but call it once per event when you need matching onEventName methods. Object-form trigger maps each key to the single value passed to that event's handlers:

emitter.on('start stop', value => console.log(value));
emitter.trigger('start stop', 'manual');

emitter.trigger({
  start: 'automatic',
  stop: 'complete'
});

During a multi-name or mapped trigger call, calling off() from a handler removes subscriptions for subsequent calls but does not cancel the remaining event names in the current call. For example, off() inside a start handler still allows the existing stop handlers in trigger('start stop') to run. Calling off('stop', handler) inside start instead removes that handler before stop is dispatched. A nested trigger call uses the current subscriptions.

once registers its generated callback through the object's overridable on method, and listenToOnce registers through overridable listenTo. This preserves the extension points used by event-lifecycle mixins. Likewise, listenTo and stopListening call an emitter's documented three-argument on and off methods exactly once per binding.

triggerMethod#

triggerMethod invokes the matching onEventName method when it exists, then fires the named event on the instance. If there are no listeners or matching method, the call still succeeds. All arguments after the event name are passed to both the method and event handlers.

import { View } from 'marionette';

const MyView = View.extend({
  callMethod(myString) {
    console.log(myString + ' was passed');
  }
});

const myView = new MyView();
myView.on('something:happened', myView.callMethod);

/* Calls callMethod('foo'); */
myView.triggerMethod('something:happened', 'foo');

The triggerMethod method is available to all Marionette classes.

Listening to Events#

Use on to register a callback directly on an emitter:

import { View } from 'marionette';

const MyView = View.extend({
  initialize() {
    this.on('event:happened', this.logCall);
  },

  logCall(myVal) {
    console.log(myVal);
  }
});

Use listenTo when the listener should own and later clean up the subscription:

import { View } from 'marionette';

const OtherView = View.extend({
  initialize({ source }) {
    this.listenTo(source, 'event:happened', this.logCall);
  },

  logCall(myVal) {
    console.log(myVal);
  }
});

const MyView = View.extend();

const myView = new MyView();

const otherView = new OtherView({ source: myView });

myView.triggerMethod('event:happened', 'someValue'); // Logs 'someValue'

listenTo calls the callback with the listener as its context and records the relationship for stopListening. A direct on subscription must be removed with off when it is no longer needed. Marionette view lifecycles also clean up their tracked listenTo relationships during destruction.

Backbone interop#

Backbone models and collections are observable event sources. Marionette listenTo and stopListening work directly with their native event interface, without changing Backbone. Select the integration separately when a View needs Backbone model reads, serialization, or structural collection observation:

import BackboneApi from '@mnjs/adapters/backbone';
import Backbone from 'backbone';
import { setDataApi, View } from 'marionette';

setDataApi(BackboneApi);

const model = new Backbone.Model();
const view = new View({ model });

view.listenTo(model, 'change', () => {
  // ...
});

The integration subscribes through Backbone's native event methods. It does not modify Backbone objects or prototypes, so existing listeners and Backbone's own listener bookkeeping remain intact. Marionette listenTo and stopListening interoperate with native Backbone objects, and Backbone can likewise listen to Marionette evented objects.

Event names#

Event callbacks are dispatched only when they were explicitly registered with on, once, listenTo, or listenToOnce. Names that also exist on Object.prototype, including constructor, toString, and __proto__, are ordinary event names and do not affect the event store's prototype. Remove them through the corresponding off or stopListening API as with any other name.

Private bookkeeping#

Marionette stores event internals under _rdEvents, _rdListeningTo, _rdListeners, and _rdListenId. These fields are private and replace the Backbone-shaped _events, _listeningTo, and _listenId names. Plugins should use on, off, listenTo, and stopListening instead of reading or writing either set of private fields.

onEvent Binding#

In addition to triggering listeners, triggerMethod can call specially named methods on the instance. For example, a view that has been rendered will internally fire view.triggerMethod('render') and call onRender - providing a handy way to add behavior to your views.

Determining what method an event will call is easy, we will outline this with an example using before:dom:refresh though this also works with any custom events you want to fire:

  1. Split the words around the : characters - so before, dom, refresh
  2. Capitalize the first letter of each word - Before, Dom, Refresh
  3. Add a leading on - on, Before, Dom, Refresh
  4. Mash it into a single call - onBeforeDomRefresh

Using this process, before:dom:refresh will call the onBeforeDomRefresh method. Let's see it in action with a custom event:

import { View } from 'marionette';

const MyView = View.extend({
  onMyEvent(myVal) {
    console.log(myVal);
  }
});

const myView = new MyView();

myView.triggerMethod('my:event', 'someValue'); // Logs 'someValue'

As before, all arguments passed into triggerMethod after the event name will make their way into the event handler. triggerMethod does not establish or clean up subscriptions; use listenTo and owner teardown, or explicit off, for listener cleanup.

View events and triggers#

Views can automatically bind DOM events to methods and View events with events and triggers respectively:

import { View } from 'marionette';

const MyView = View.extend({
  events: {
    'click a': 'showModal'
  },

  triggers: {
    'keyup input': 'data:entered'
  },

  showModal(event) {
    console.log('Show the modal');
  },

  onDataEntered(view, event) {
    console.log('Data was entered');
  }
});

For more information, see the DOM interactions documentation.

View entity events#

Views can automatically bind to its model or collection with modelEvents and collectionEvents respectively.

import { View } from 'marionette';

const MyView = View.extend({
  modelEvents: {
    'change:someattribute': 'onChangeSomeattribute'
  },

  collectionEvents: {
    'update': 'onCollectionUpdate'
  },

  onChangeSomeattribute() {
    console.log('someattribute was changed');
  },

  onCollectionUpdate() {
    console.log('models were added or removed in the collection');
  }
});

For more information, see the Entity events documentation.

Child View Events#

The View and CollectionView can handle events from their direct managed children through childViewEvents, forward selected names through childViewTriggers, or opt into a prefix through childViewEventPrefix. Without one of those configurations, a parent does not automatically forward every child event. For example:

import { View, CollectionView } from 'marionette';

const ChildView = View.extend({
  tagName: 'li',
  template: () => '<a href="#details">Select</a>',

  triggers: {
    'click a': 'select:model'
  }
});

const ListView = CollectionView.extend({
  tagName: 'ul',
  childView: ChildView,

  childViewEvents: {
    'select:model': 'modelSelected'
  },

  modelSelected(childView) {
    console.log('model selected: ' + childView.model.id);
  }
});

const list = new ListView({ collection: [{ id: 'example' }] }).render();
list.el.querySelector('a').click(); // Logs 'model selected: example'

Event Bubbling#

Set childViewEventPrefix: 'childview' on a parent to forward every child event as childview:<eventName>. The default is false, so prefixed forwarding is opt-in. Explicit childViewEvents and childViewTriggers still work when the prefix is disabled. Both trigger and triggerMethod events can be forwarded. The parent's matching method runs before its event listeners.

Each level must configure the forwarding it needs. Arguments pass through unchanged: Marionette does not prepend the child instance to arbitrary events. DOM triggers already supply (view, event), while a custom event must explicitly supply its View when handlers need it.

import { View } from 'marionette';

const MyView = View.extend({
  template: false,
  triggers: {
    click: 'click:view'
  },

  doSomething() {
    this.triggerMethod('did:something', this);
  }
});

const ParentView = View.extend({
  template: () => '<div class="foo-hook"></div>',
  childViewEventPrefix: 'childview',
  regions: {
    foo: '.foo-hook'
  },

  onRender() {
    this.showChildView('foo', new MyView());
  },

  onChildviewClickView(childView) {
    console.log('View clicked ' + childView);
  },

  onChildviewDidSomething(childView) {
    console.log('Something was done to ' + childView);
  }
});

NOTE triggers will automatically pass the child view as an argument to the parent view, however triggerMethod will not, and so notice that in the above example, the triggerMethod explicitly passes the child view.

Using CollectionView#

The same opt-in applies to a CollectionView and its childView:

import { View, CollectionView } from 'marionette';

const MyChild = View.extend({
  template: false,
  triggers: {
    click: 'click:child'
  }
});

const MyList = CollectionView.extend({
  childView: MyChild,
  childViewEventPrefix: 'childview',
  onChildviewClickChild(childView) {
    console.log('Childview ' + childView + ' was clicked');
  }
});

A Child View's Event Prefix#

You can customize the event prefix for events that are forwarded through the view. To do this, set the childViewEventPrefix on the view or collectionview. For more information on the childViewEventPrefix see Event bubbling.

The default value for childViewEventPrefix is false. It disables prefixed forwarding, while explicit child event maps remain active.

import { CollectionView, View } from 'marionette';

const MyChildView = View.extend({ template: () => 'Child' });
const MyCollectionView = CollectionView.extend({
  childViewEventPrefix: 'some:prefix',
  childView: MyChildView
});
const collectionView = new MyCollectionView({ collection: [{}] });

collectionView.on('some:prefix:render', childView => {
  console.log('Child rendered', childView);
});
collectionView.render();

The childViewEventPrefix can be provided in the view definition or in the constructor function call, to get a view instance.

Explicit Event Listeners#

To call specific functions on event triggers, use the childViewEvents attribute to map child events to methods on the parent view. This takes events fired on child views - without the childview: prefix - and calls the method referenced or attached function.

import { View } from 'marionette';

const MyView = View.extend({
  template: false,
  triggers: {
    click: 'view:clicked'
  }
});

const ParentView = View.extend({
  template: () => '<div class="foo-hook"></div>',
  regions: {
    foo: '.foo-hook'
  },

  childViewEvents: {
    'view:clicked': 'displayMessage'
  },

  onRender() {
    this.showChildView('foo', new MyView());
  },

  displayMessage(childView) {
    console.log('Displaying message for ' + childView);
  }
});

Attaching Functions#

The childViewEvents attribute can also attach functions directly to be event handlers:

import { View } from 'marionette';

const MyView = View.extend({
  template: false,
  triggers: {
    click: 'view:clicked'
  }
});

const ParentView = View.extend({
  template: () => '<div class="foo-hook"></div>',
  regions: {
    foo: '.foo-hook'
  },

  childViewEvents: {
    'view:clicked'(childView) {
      console.log('Function called for ' + childView);
    }
  },

  onRender() {
    this.showChildView('foo', new MyView());
  }
});

Using CollectionView's childViewEvents#

import { CollectionView } from 'marionette';

// childViewEvents can be specified as a hash...
const MyCollectionView = CollectionView.extend({
  childViewEvents: {
    // This callback will be called whenever a child is rendered or emits a `render` event
    render() {
      console.log('A child view has been rendered.');
    }
  }
});

Triggering Events on Child Events#

A childViewTriggers hash or method permits proxying of child view events without manually setting bindings. Each own map key selects a child event, and its value names the event to trigger on the parent. Inherited entries are ignored. childViewEvents also normalizes only own enumerable string keys.

childViewTriggers is sugar on top of childViewEvents much in the same way that view triggers are sugar for view events.

import { View, CollectionView } from 'marionette';

// The child view fires a custom event, `show:message`
const ChildView = View.extend({
  template: () => '<button class="button">Message</button><form><button>Submit</button></form>',

  // Events hash defines local event handlers that in turn may call `triggerMethod`.
  events: {
    'click .button': 'onClickButton'
  },

  triggers: {
    'submit form': 'submit:form'
  },

  onClickButton () {
    // Both `trigger` and `triggerMethod` events will be caught by parent.
    this.trigger('show:message', 'foo');
    this.triggerMethod('show:message', 'bar');
  }
});

// The parent forwards the child's event through childViewTriggers.
const ParentView = CollectionView.extend({
  childView: ChildView,

  childViewTriggers: {
    'show:message': 'child:show:message',
    'submit:form': 'child:submit:form'
  },

  onChildShowMessage (message) {
    console.log('A child view fired show:message with ' + message);
  },

  onChildSubmitForm (childView) {
    console.log('A child view fired submit:form');
  }
});

const GrandParentView = View.extend({
  template: () => '<div class="list"></div>',
  regions: {
    list: '.list'
  },

  onRender() {
    this.showChildView('list', new ParentView({
      collection: this.collection
    }));
  },

  childViewEvents: {
    'child:show:message': 'showMessage'
  },

  showMessage(message) {
    console.log('A child sent: ' + message);
  }
});

Using CollectionView's childViewTriggers#

import { View, CollectionView } from 'marionette';

// The child view fires a custom event, `show:message`
const ChildView = View.extend({
  template: () => '<button class="button">Message</button><form><button>Submit</button></form>',

  // Events hash defines local event handlers that in turn may call `triggerMethod`.
  events: {
    'click .button': 'onClickButton'
  },

  // Triggers hash converts DOM events directly to view events catchable on the parent.
  // Note that `triggers` automatically pass the first argument as the child view.
  triggers: {
    'submit form': 'submit:form'
  },

  onClickButton () {
    // Both `trigger` and `triggerMethod` events will be caught by parent.
    this.trigger('show:message', 'foo');
    this.triggerMethod('show:message', 'bar');
  }
});

// The parent forwards the child's event through childViewTriggers.
const ParentView = CollectionView.extend({

  childView: ChildView,

  childViewTriggers: {
    'show:message': 'child:show:message',
    'submit:form': 'child:submit:form'
  },

  onChildShowMessage (message) {
    console.log('A child view fired show:message with ' + message);
  },

  onChildSubmitForm (childView) {
    console.log('A child view fired submit:form');
  }
});

Lifecycle Events#

Marionette Views fire events during their creation and destruction lifecycle. For more information see the documentation covering the View Lifecycle.