Build interfaces

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

Marionette.Region#

A Region gives a changing part of the screen a place to live. Show a view, replace it with another, or empty the Region when that part of the interface is no longer needed. By default, replacing or emptying a view destroys it; the Region remains available for the next view.

Region includes:

See the documentation for laying out views for an introduction in managing regions throughout your application.

Regions maintain the View's lifecycle while showing or emptying a view.

Documentation Index#

Instantiating a Region#

A Region accepts el, parentEl, allowMissingEl, and replaceElement. el is a native element or a selector; selector resolution is deferred until an operation needs the element. parentEl limits selector lookup and may be an element, document, or function returning one. allowMissingEl and replaceElement may also be functions; a boolean supplied to show(view, options) overrides the corresponding Region setting for that call.

import { Region } from 'marionette';

const myRegion = new Region({ el: '#content' });

While regions may be instantiated and useful on their own, their primary use case is through the Application and View classes.

Reading Region ownership#

A Region registered on a View exposes that existing relationship through pure, read-only queries. getOwner() returns the owning View and getName() returns the Region's name within that View. Neither query renders the View, resolves the Region element, or changes ownership. A standalone Region returns undefined from both methods. Removing a registered Region or completing its destruction clears both values. A throwing lifecycle hook interrupts teardown without rolling back ownership or retrying destruction.

A Region has one authoritative registration. Re-adding that same Region instance under its current owner and name returns it without lifecycle events or ownership changes. Registering it under a different owner or name, registering a Region whose destruction has begun or completed, or replacing an occupied Region name through addRegion throws MN0030 before committing the conflicting registration. A conflict found before addRegions starts rejects the whole batch. Lifecycle hooks must not re-register the Region or occupy its registration name while registration is in progress. Failed batch registration is not rolled back. Remove an existing named Region before replacing it, and use a fresh Region instance when another View needs a Region.

const contentRegion = myView.getRegion('content');

contentRegion.getOwner() === myView; // true
contentRegion.getName(); // 'content'

Lifecycle transition contract#

A Region owns at most one current View. Its public lifecycle state can be read without changing it:

State hasView() isDestroyed() currentView
Empty false false undefined
Occupied true false The View shown by the Region
Destroyed false true undefined

isSwappingView() is a temporary operation flag rather than a fourth stable state. It is true while one occupied Region replaces its current View with another, including the Region's before:show, before:empty, empty, and show callbacks. It returns to false when show completes. isReplaced() independently reports whether replaceElement has temporarily replaced the Region element; it does not change which lifecycle operations are valid.

Operation Empty Region Occupied Region Destroyed Region
show(view) when the Region element resolves Renders the View if needed, shows it, and enters occupied. Showing the same View is a no-op. Showing a different View destroys the old View and swaps to the new one. Returns the Region without inspecting or changing the caller-owned View or resolving the element.
detachView() Returns undefined; state is unchanged. Detaches and returns the live View, then enters empty. Returns undefined without changing state or DOM or emitting lifecycle events.
empty() Returns the Region and, when its element resolves, removes unmanaged contents from that element. Destroys the current View, clears currentView, and enters empty. Returns the Region without resolving the element or changing lifecycle state or DOM.
reset() Empties the Region and resets its element reference. Destroys the current View, enters empty, and resets the element reference. Returns the Region without resolving the element or changing lifecycle state, DOM, or element caches.
Current View is destroyed externally No effect. Runs the Region's empty lifecycle once, clears currentView, and enters empty. No effect.
destroy() Runs the destroy lifecycle and enters destroyed. Emits before:destroy, destroys and empties the current View, enters destroyed, then emits destroy. Returns the Region without repeating cleanup or lifecycle events.

Successful show, empty, and destroy calls return the Region when their operation completes. With allowMissingEl: true, show instead returns undefined and leaves the current View unchanged when its element does not resolve. A View returned by detachView() remains the caller's responsibility until the same or another Region shows it or it is destroyed. After destruction, show(), empty(), and reset() return the Region without changing it, and detachView() returns undefined. As soon as destruction begins, show(), detachView(), and recursive destroy() calls are no-ops. empty() and reset() remain available during cleanup. A View passed to show() during or after destruction remains caller-owned and unchanged. A destroyed Region cannot be reused.

When its current View destroys itself, the Region clears that View's ownership and releases the owning parent View's subscriptions to it. Later events on the destroyed child are no longer forwarded to the parent.

The following example preserves a View by detaching it before showing it again. Calling empty() afterward destroys the View and returns the Region to its empty state.

import { Region, View } from 'marionette';

export function runRegionLifecycle() {
  const region = new Region({ el: '#content' });
  const contentView = new View({
    template() {
      return '<p>Content</p>';
    }
  });

  region.show(contentView);
  const detachedView = region.detachView();
  region.show(detachedView);
  region.empty();

  return region;
}

Defining the Application Region#

The Application defines a single region el using the region attribute. This can be accessed through getRegion() or have a view displayed directly with showView(). Below is a short example:

import { Application } from 'marionette';
import SomeView from './view';

const MyApp = Application.extend({
  region: '#main-content',

  onStart() {
    const mainRegion = this.getRegion();  // Has all the properties of a `Region`
    mainRegion.show(new SomeView());
  }
});

For more information, see the Application docs.

Defining Regions#

In Marionette you can define a region with a string selector or an object literal on your Application or View. This section will document the two types as applied to View, although they will work for Application as well - just replace regions with region in your definition.

Region declaration maps, including maps passed to addRegions, use own enumerable string keys in standard JavaScript own-key order. Inherited, symbol, and non-enumerable properties are ignored, and a numeric length property is an ordinary Region name rather than an array-like signal. Arrays, sparse arrays, and other array-like values are not supported as Region declaration maps.

Named View Region operations require a non-empty string name. addRegion, removeRegion, hasRegion, getRegion, showChildView, detachChildView, and getChildView throw MN0032 for an empty name. The public types require strings; unsupported shapes have no guaranteed diagnostic. Ordinary collision names such as constructor, toString, and __proto__ remain valid when explicitly registered.

String Selector#

You can use a CSS selector string to define regions.

import { View } from 'marionette';

const MyView = View.extend({
  regions: {
    mainRegion: '#main'
  }
});

Region#getEl(selector) resolves the selector within parentEl, or within the document when no parent is defined, and returns the first matching native DOM element. A custom getEl override must preserve that native-element return contract; do not return a NodeList or jQuery collection. To customize selector lookup through the DOM adapter, implement findEl(context, selector) instead. The v4 DomApi#getEl method is not part of the v5 DOM API.

Selector lookup is deferred until a DOM operation such as show() needs it. During construction, initialize observes the configured selector string in this.el; constructing a Region does not query the document or dispatch through a getEl override.

Additional Options#

You can define regions with an object literal. Object literal definitions expect an el property - the selector string to hook the region into. With this format is possible to define whether showing the region overwrites the el or just overwrites the content (the default behavior).

Region defaults and object-literal definitions contribute their own enumerable properties, including symbols, through object spread. Inherited and non-enumerable properties are ignored when Marionette builds the Region options.

To replace the Region's placeholder with the child View's root element, use replaceElement: true:

import { View } from 'marionette';

const ReplacementView = View.extend({
  className: 'new-class',
  template: () => '<p>Replacement content</p>'
});

const Layout = View.extend({
  template: () => '<div class="overwrite-me"></div>',
  regions: {
    main: {
      el: '.overwrite-me',
      replaceElement: true
    }
  }
});

export const view = new Layout().render();
export const placeholder = view.el.querySelector('.overwrite-me');
export const replacement = new ReplacementView();

// Rendering the parent creates the placeholder. Showing the child replaces it.
view.showChildView('main', replacement);

view.$('.overwrite-me').length; // 0
view.$('.new-class').length; // 1

showChildView() replaces .overwrite-me with the child's el; rendering the parent alone does not. The className option takes a class name, without the . used in CSS selectors. Emptying the Region destroys its current child and restores the original placeholder. The parent View's own root remains unchanged.

This is useful when a container requires particular direct children, such as a table body containing rows. Choose a child tagName valid for that container.

import { View } from 'marionette';

const MyView = View.extend({
  regions: {
    regionDefinition: {
      el: '.bar',
      replaceElement: true
    }
  }
});

Errors An operation that needs the element throws MN0004 when no el is configured, or MN0005 when a selector finds no element and allowMissingEl is false. Construction alone does not resolve the selector.

Specifying regions as a Function#

On a View the regions attribute can also be a function returning an object:

import { View } from 'marionette';

const MyView = View.extend({
  regions(){
    return {
      firstRegion: '#first-region'
    };
  }
});

Using a RegionClass#

If you've created a custom region class, you can use it to define your region.

import { Application, Region, View } from 'marionette';

const MyRegion = Region.extend({
  onShow(){
    // Scroll to the middle
    const viewHeight = this.currentView.el.getBoundingClientRect().height;
    const regionHeight = this.el.getBoundingClientRect().height;
    this.el.scrollTop = viewHeight / 2 - regionHeight / 2;
  }
});

const MyApp = Application.extend({
  regionClass: MyRegion,
  region: '#first-region'
})

const MyView = View.extend({
  regionClass: MyRegion,
  regions: {
    firstRegion: {
      el: '#first-region',
      regionClass: Region // Don't scroll this to the top
    },
    secondRegion: '#second-region'
  }
});

Referencing UI in regions#

The UI attribute can be useful when setting region selectors - simply use the @ui. prefix:

import { View } from 'marionette';

const MyView = View.extend({
  ui: {
    region: '#first-region'
  },
  regions: {
    firstRegion: '@ui.region'
  }
});

Adding Regions#

To add regions to a view after it has been instantiated, simply use the addRegion method:

import MyView from './myview';

const myView = new MyView();
myView.addRegion('thirdRegion', '#third-region');

Now we can access thirdRegion as we would the others.

You can also add multiple regions using addRegions.

import MyView from './myview';

const myView = new MyView();
myView.addRegions({
  main: {
    el: '.overwrite-me',
    replaceElement: true
  },
  sidebar: '.sidebar'
});

Removing Regions#

You can remove all of the regions from a view by calling removeRegions or you can remove a region by name using removeRegion. When a region is removed the region will be destroyed.

import { View } from 'marionette';

const MyView = View.extend({
  regions: {
    main: '.main',
    sidebar: '.sidebar',
    header: '.header'
  }
});

const myView = new MyView();

// remove only the main region
const mainRegion = myView.removeRegion('main');

mainRegion.isDestroyed(); // -> true

// remove all regions
myView.removeRegions();

Using Regions on a view#

In addition to adding and removing regions there are a few methods to help utilize regions. hasRegion and getRegion are pure own-registry queries, and getRegions returns a pure snapshot; none renders. Child View operations and emptyRegions first render a live, unrendered View before resolving or mutating Regions.

  • getRegion(name) - Request an own registered Region without rendering.
  • getRegions() - Return a fresh own-key snapshot of registered Regions without rendering.
  • hasRegion(name) - Check if a View has an own registered Region without rendering.
  • emptyRegions() - Render when needed, then empty all Regions returned by getRegions().

Showing a View#

Once a region is defined, you can call its show method to display the view:

const myView = new MyView();
const childView = new MyChildView();
myView.render();
const mainRegion = myView.getRegion('main');

// render and display the child View
mainRegion.show(childView, { fooOption: 'bar' });

The parent View must already be rendered before calling a selector Region's show directly. Use showChildView('main', childView) to render the parent when needed before showing the child.

This is equivalent to a view's showChildView which can be used as:

const myView = new MyView();
const childView = new MyChildView();

// render and display the view
myView.showChildView('main', childView, { fooOption: 'bar' });

Both forms require a Marionette View instance. Construct a View explicitly when displaying a template or static content; Regions do not allocate hidden Views from View classes, functions, strings, or option objects. The wrapper pattern provides explicit ownership for legacy integrations.

import { View } from 'marionette';

myView.showChildView('header', new View({
  template: () => 'Welcome to the site'
}));

The argument after the View instance in Region#show(view, options) and View#showChildView(name, view, options) is a separate show-options object passed to the events fired during show.

For more information on showChildView and getChildView, see the Documentation for Views

Errors

  • A destroyed View throws MN0007. Other input shapes are unsupported; core does not guarantee a Marionette diagnostic for an invalid value.
  • An error will be thrown if the view is already managed by a Region or CollectionView, including a filtered or deferred CollectionView child. Detach it from that owner first.

Checking whether a region is showing a view#

If you wish to check whether a region has a view, you can use the hasView function. This will return a boolean value depending whether or not the region is showing a view.

const myView = new MyView();
myView.render();
const mainRegion = myView.getRegion('main');

mainRegion.hasView() // false
mainRegion.show(new OtherView());
mainRegion.hasView() // true

If you show a view in a region with an existing view, Marionette will remove the existing View before showing the new one.

Wrapping a non-Marionette view#

Regions and CollectionViews manage Marionette Views. They do not synthesize render or destroy events for Backbone Views or fall back to a remove() method. Keep a legacy integration inside a Marionette owner:

import { View } from 'marionette';
import LegacyView from './legacy-view.js';

const LegacyWrapper = View.extend({
  template: () => '<div class="legacy"></div>',
  onRender() {
    this.legacy?.remove();
    this.legacy = new LegacyView({ el: this.$('.legacy')[0] });
    this.legacy.render();
  },
  onDestroy() {
    this.legacy?.remove();
  }
});

Show new LegacyWrapper() in the Region. The wrapper owns the legacy instance and translates its actual rendering and cleanup API. No global prototype mixin or compatibility flags are needed.

Emptying a Region#

You can remove a view from a region (effectively "unshowing" it) with region.empty() on a region:

const myView = new MyView();

myView.showChildView('main', new OtherView());
const mainRegion = myView.getRegion('main');
mainRegion.empty();

This will destroy the view, clean up any event handlers and remove it from the DOM. When a region is emptied empty events are triggered. Calling empty() after Region destruction completes returns the Region without resolving its element, changing the DOM, or emitting empty lifecycle events.

NOTE If the region does not currently contain a View it will detach any HTML inside the region when emptying. If the region does contain a View, any HTML that doesn't belong to the View will remain.

Preserving Existing Views#

If you replace the current view with a new view by calling show, it will automatically destroy the previous view. You can prevent this behavior by detaching the view before showing another one.

Detaching Existing Views#

If you want to detach an existing view from a region, use detachView.

const myView = new MyView();

const myOtherView = new MyView();

const childView = new MyChildView();

// render and display the view
myView.showChildView('main', childView);

// ... somewhere down the line
myOtherView.showChildView('main', myView.getRegion('main').detachView());

Note Detaching transfers responsibility for the live View to the caller. Show it again in the same emptied Region or another Region when needed, or call destroy() when finished with it.

reset A Region#

Resetting a live Region destroys its current View and restores its original el reference. An original selector is queried again by the next operation that needs it; an original DOM element is reused without a selector query.

const myView = new MyView();
myView.showChildView('main', new OtherView());
const myRegion = myView.getRegion('main');
myRegion.reset();

This can be useful in unit testing your views. Calling reset() after Region destruction completes returns the Region without changing its element reference or cache.

destroy A Region#

A region can be destroyed which will reset the region, destroy its current View, remove it from any parent View's Region lookups, and stop any internal Region listeners. Reentrant Region destruction from before:destroy or destroy, repeated calls, and later destruction of the parent View do not repeat the child or Region teardown. A throwing lifecycle hook stops destruction. Later destroy() calls do not retry hooks or resume partial teardown. Discard the Region after a cleanup error; its remaining state is not a reusable lifecycle state. isDestroyed() becomes true after reset() finishes, before the destroy event. It remains false in before:destroy, before:empty, and empty handlers called during teardown.

destroy() calls the overridable reset() method, which calls empty(). Overrides can use this ordinary synchronous chain while cleanup is in progress. An override that does not delegate to the base method owns the corresponding cleanup; for example, a custom reset() can call this.empty() and reset its own element reference. Nested empty() or reset() calls from lifecycle handlers are ordinary calls, so handlers must avoid recursive loops.

After destruction completes, empty() and reset() return the Region without changing its element or DOM. show() and detachView() already stop accepting Views or transferring ownership as soon as destruction begins.

import { View } from 'marionette';

const MyView = View.extend({
  regions: {
    mainRegion: '#main'
  }
});

const myView = new MyView();
myView.render();

const myRegion = myView.getRegion('mainRegion');

myRegion.show(new ChildView());

myRegion.destroy();

myRegion.isDestroyed(); // true
myRegion.hasView(); // false
myView.hasRegion('mainRegion'); // false

Check If View Is Being Swapped By Another#

The isSwappingView method returns if a view is being swapped by another one. It's useful inside region lifecycle events / methods.

The example will show an message when the region is empty:

import { Region } from 'marionette';

const EmptyMsgRegion = Region.extend({
  onEmpty() {
    if (!this.isSwappingView()) {
      this.el.append('Empty Region');
    }
  }
});

Set How View's el Is Attached and Detached#

Override the region's attachHtml method to change how the view is attached to the DOM (when not using replaceElement: true). This method receives one parameter - the view to show.

The default implementation of attachHtml is essentially:

import { Region } from 'marionette';

Region.prototype.attachHtml = function(view){
  this.el.appendChild(view.el);
}

Similar to attachHtml, override detachHtml to determine how the region detaches the contents from its el. This method receives no parameters.

For most cases you will want to use the DOM API to determine how a region html is attached, but in some cases you may want to override a single Region class for situations like animation where you want to control both attaching and view removal.

This example will make a view slide down from the top of the screen instead of just appearing in place:

import $ from 'jquery';
import { Region, View } from 'marionette';

const ModalRegion = Region.extend({
  attachHtml(view){
    // Some effect to show the view:
    const $el = $(this.el);
    $el.empty().append(view.el);
    $el.hide().slideDown('fast');
  }
});

const MyView = View.extend({
  regions: {
    mainRegion: '#main-region',
    modalRegion: {
      regionClass: ModalRegion,
      el: '#modal-region'
    }
  }
});

Configure How To Remove View#

Override the region's removeView method to change how and when the view is destroyed / removed from the DOM. This method receives one parameter - the view to remove.

The default implementation of removeView is:

import { Region } from 'marionette';

Region.prototype.removeView = function(view){
  this.destroyView(view);
}

destroyView(view) destroys a Marionette View and returns it. It forwards the Region owner's lifecycle-monitoring policy; it does not adapt a Backbone View or fall back to remove(). Keep this helper when overriding removeView.

Region operations are synchronous. A removeView override must complete cleanup before returning if callers should observe the normal empty/destroy contract. Returning a Promise does not delay Region lifecycle completion. For an exit animation, finish the animation in the application before calling empty() or showing the replacement, and let the Region perform its normal synchronous teardown. The application owns cancellation when navigation or destruction interrupts that animation.