Build interfaces

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

Marionette.CollectionView#

A CollectionView manages repeated parts of a screen: rows, cards, or any ordered set of child views within a root element, el. It creates children from a collection, or lets you add and remove child views yourself.

Plain arrays work with the default Data API. Use an adapter when your collection needs to notify the view about changes; mutating a plain array does not send those notifications.

CollectionView includes:

A CollectionView can have Behaviors.

Documentation Index#

Instantiating a CollectionView#

When instantiating a CollectionView there are several properties, if passed, that will be attached directly to the instance: attributes, behaviors, childView, childViewContainer, childViewEventPrefix, childViewEvents, childViewOptions, childViewTriggers, className, collection, collectionEvents, el, emptyView, emptyViewOptions, events, id, model, modelEvents, sortWithCollection, stateEvents, tagName, template, templateContext, triggers, ui, viewComparator, viewFilter

import { CollectionView } from 'marionette';

const myCollectionView = new CollectionView();

CollectionView composes the same visual, event, and State contracts as View, but does not inherit View's named-Region methods. Use getEmptyRegion() for its empty View; put a CollectionView inside a parent View when a layout needs additional named Regions. A supplied state follows the State ownership contract.

Rendering a CollectionView#

The render method of the CollectionView is primarily responsible for rendering the entire collection. It loops through each of the children in the collection and renders them individually as a childView.

import { CollectionView } from 'marionette';

const MyCollectionView = CollectionView.extend({});

// all of the children views will now be rendered.
new MyCollectionView().render();

Rendering a Template#

In addition to rendering children, the CollectionView may have a template. The child views can be rendered within a DOM element of this template. The CollectionView will serialize either the model or collection along with context for the template to render.

For more detail on how to render templates, see View Template Rendering.

Defining the childViewContainer#

By default the CollectionView will render the children into the el of the CollectionView. If you are rendering a template you will want to set the childViewContainer to be a selector for an element within the template for child view attachment.

import { CollectionView } from 'marionette';

const MyCollectionView = CollectionView.extend({
  childViewContainer: '.js-widgets',
  template: () => '<h1>Widgets</h1><ul class="js-widgets"></ul>'
});

Errors An error will throw if the childViewContainer can not be found.

Re-rendering the CollectionView#

If you need to re-render the entire collection or the template, you can call the collectionView.render method. This method will destroy all of the child views that may have previously been added.

View Lifecycle and Events#

Like View, a CollectionView exposes its lifecycle as the independent isRendered(), isAttached(), and isDestroyed() state values. Its managed children have their own View lifecycle state. Existing contents in the CollectionView element do not make the CollectionView rendered; rendering means its child set has been built and inserted into its element.

The table describes the default rendered and monitored path. Passing { preventRender: true } to addChildView still renders the parent when needed, but manages the supplied child without rendering it; detaching that child returns it in its current lifecycle state. Setting monitorViewEvents: false on the CollectionView intentionally disables child attachment events and automatic child isAttached() updates.

Disabling monitoring does not make child destruction clear surrounding template content. Bulk removal is used only when the child container contains those Views' root elements and optional formatting whitespace.

Operation CollectionView state Managed child state
Construct Starts not rendered and not destroyed. It is attached only when its element is already in the document. No children have been built.
render() Enters rendered and preserves its attached state. Repeated render stays rendered. Builds and renders the current children. Repeated render destroys the previous children before building replacements.
A rendered collection resets Remains rendered and preserves its attached state. Destroys the previous children and builds replacements for the reset collection.
addChildView(view) Renders first when needed, then remains rendered. Renders and manages the added View.
detachChildView(view) State is unchanged. Removes and returns the live View in a detached state. The caller becomes responsible for it.
removeChildView(view) or external child destruction State is unchanged. Removes the child from the managed set. removeChildView destroys it; an externally destroyed child is removed once.
The owning Region detaches and re-shows the CollectionView Remains rendered while attached changes to false, then back to true. Live children follow the parent's detached and attached state.
destroy() Detaches, becomes not rendered, and enters destroyed. Repeated destroy returns the CollectionView without repeating lifecycle events. Detaches and destroys every still-managed child after the parent element is removed.
render() after destruction Returns the same CollectionView and remains not rendered and destroyed. Repeated calls are no-ops. Does not recreate or render children.
addChildView(view) once destruction begins Returns the supplied View without inspecting it, the index, or options or changing events, ownership, DOM, or lifecycle state. Calls during before:destroy and repeated calls after destruction are the same no-op. The supplied View remains unchanged and can be added to a live owner.

Collection sort, reset, and update events raised reentrantly during destruction do not rebuild, add, remove, sort, render, or destroy additional child Views.

A View returned by detachChildView() is no longer managed by the CollectionView; another owner may show it, or the caller must destroy it. Other operations on an already destroyed CollectionView remain outside this lifecycle contract until their invalid-transition behavior is made consistent.

Read More:

Entity Events#

A CollectionView subscribes to its model and collection through the configured DataApi. Event names and callback arguments belong to that data provider. Plain objects and arrays do not emit changes; declaring entity event maps for unobservable values throws MN0037.

Read More:

DOM Interactions#

CollectionView uses the same native events, triggers, and ui contracts as View. Keep parent selectors and handlers specific to DOM that the CollectionView itself owns. Delegation is rooted at the parent el, so a broad selector can also match child-owned descendants; do not rebind the parent's ui to reach into child View DOM.

After application code places parent-owned DOM inside a template-less CollectionView, call bindUIElements() before reading it with getUI(). Use that method only to bind the CollectionView's own DOM, not child View DOM. Calling getUI() without a declared ui map or while UI elements are unbound throws MN0023.

When parent code needs a child, retrieve the child View through the public children lookup APIs and call an intentional public method on that View. For communication initiated by a child, use childViewEvents or childViewTriggers, or an explicit public listenTo subscription, instead of querying or mutating the child's DOM from the parent.

Read More:

Behaviors#

A Behavior provides a clean separation of concerns to your view logic, allowing you to share common user-facing operations between your views.

Read More:

Managing Children#

Children are automatically managed once the CollectionView is rendered. For each model within the collection the CollectionView will build and store a childView within its children object. This allows you to easily access the views within the collection view, iterate them, find them by a given indexer such as the view's model or id and more.

During its first render, the CollectionView subscribes through DataApi.observeCollection() to normalized update, reset, and reorder notifications. The configured provider owns the source event vocabulary; Backbone is one supported observable integration.

When the collection for the view is reset, the view will destroy all children and re-render the entire collection.

When the adapter reports a model addition, the CollectionView constructs its child and renders it if it passes the presentation filter.

When a model is removed from the collection (or destroyed / deleted), the CollectionView will destroy and remove that model's child view.

Collection updates, sort(), and filter() use the same child-rendering path. Surviving visible children keep their elements mounted, including when a viewFilter or custom viewComparator is active. New or newly visible children are attached through attachHtml; existing elements move only when their order needs to change. Removal alone does not move or rerender surviving children. See DOM movement for focus and text-selection preservation and the browser fallback behavior.

The before:render:children and render:children events receive all visible children. This describes the render pass, not a list of children whose templates were rerendered. Already-rendered children reuse their contents unless the data adapter reports them as updated.

Overriding sort() or filter() replaces that part of the flow. Call the parent method to retain its behavior; CollectionView does not force a render after an override that deliberately skips it.

When the collection for the view is sorted, the view by default reconciles its child views to the collection's source order unless the sortWithCollection attribute on the CollectionView is set to false. Setting viewComparator: false disables a separate presentation sort; it does not disable keyed source-order reconciliation.

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

setDataApi(BackboneApi);

const collection = new Backbone.Collection();

const MyChildView = View.extend({
  template: false
});

const MyCollectionView = CollectionView.extend({
  childView: MyChildView,
  collection,
});

const myCollectionView = new MyCollectionView();

// Collection view will not re-render as it has not been rendered
collection.reset([{foo: 'foo'}]);

myCollectionView.render();

// Collection view will effectively re-render displaying the new model
collection.reset([{foo: 'bar'}]);

When the children are rendered the render:children and before:render:children events will trigger.

When a childview is added to the children add:child and before:add:child events will trigger

When a childview is removed from the children remove:child and before:remove:child events will trigger.

Attaching children within the el#

The CollectionView places new or newly visible child root elements into a DocumentFragment, then calls attachHtml(fragment, container) to insert that batch. Already mounted children remain in place or move only as needed to match the presentation order; they are not all removed and appended on each pass.

You can override this by specifying an attachHtml method in your view definition. This method takes two parameters and has no return value.

import { CollectionView } from 'marionette';

CollectionView.extend({

  // The default implementation:
  attachHtml(els, container) {
    // Unless childViewContainer is set, container === this.el
    this.Dom.appendContents(container, els);
  }
});

The first parameter is the DOM fragment containing child root elements, and the second parameter is the native DOM container for the children which by default equates to the view's el unless a childViewContainer is set.

Destroying All children#

CollectionView implements a destroy method which automatically destroys its children and cleans up listeners.

When a nonempty owned child set is destroyed, the destroy:children and before:destroy:children events will trigger.

Read More:

CollectionView's childView#

When using a collection to manage the children of CollectionView, specify a Marionette View or CollectionView class as childView, rather than an instance. A plain Backbone View is not a supported child; wrap it in a Marionette View when integrating a legacy component.

import { View, CollectionView } from 'marionette';

const MyChildView = View.extend({});

const MyCollectionView = CollectionView.extend({
  childView: MyChildView
});

Errors When Marionette needs to construct a collection-backed child and childView is missing, it throws MN0011. An empty CollectionView or a CollectionView with only manually added children does not require childView.

You can also define childView as a function. In this form, the value returned by this method is the ChildView class that will be instantiated when a Model needs to be initially rendered. This method also gives you the ability to customize per Model ChildViews.

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

setDataApi(BackboneApi);

const FooView = View.extend({
  template: _.template('foo')
});

const BarView = View.extend({
  template: _.template('bar')
});

const MyCollectionView = CollectionView.extend({
  collection: new Backbone.Collection(),
  childView(model) {
    // Choose which view class to render,
    // depending on the properties of the model
    if  (model.get('isFoo')) {
      return FooView;
    }
    else {
      return BarView;
    }
  }
});

const collectionView = new MyCollectionView().render();

const foo = new Backbone.Model({
  isFoo: true
});

const bar = new Backbone.Model({
  isFoo: false
});

// Renders a FooView
collectionView.collection.add(foo);

// Renders a BarView
collectionView.collection.add(bar);

A resolver must return a Marionette View class. Core trusts that result; unsupported returns can fail later during construction or child setup.

Building the children#

The buildChildView method is responsible for taking the ChildView class and instantiating it with the appropriate data. This method takes three parameters and returns a view instance to be used as the child view.

buildChildView(child, ChildViewClass, childViewOptions){
  // build the final list of options for the childView class
  const options = { model: child, ...childViewOptions };
  // create the child view instance
  const view = new ChildViewClass(options);
  // return it
  return view;
},

Override this method when you need a more complicated build, but use childView if you need to determine which View class to instantiate.

import _ from 'underscore';
import BackboneApi from '@mnjs/adapters/backbone';
import Backbone from 'backbone';
import { CollectionView, setDataApi } from 'marionette';
import MyListView from './my-list-view';
import MyView from './my-view';

setDataApi(BackboneApi);

const MyCollectionView = CollectionView.extend({
  childView(child) {
    if (child.get('type') === 'list') {
      return MyListView;
    }

    return MyView;
  },
  buildChildView(child, ChildViewClass, childViewOptions) {
    let options;

    if (child.get('type') === 'list') {
      const childList = new Backbone.Collection(child.get('list'));
      options = _.extend({collection: childList}, childViewOptions);
    } else {
      options = _.extend({model: child}, childViewOptions);
    }

    // create the child view instance
    const view = new ChildViewClass(options);
    // return it
    return view;
  }
});

Passing Data to the childView#

There may be scenarios where you need to pass data from your parent collection view in to each of the childView instances. To do this, provide a childViewOptions definition on your collection view as an object literal. This will be passed to the constructor of your childView as part of the options.

import { View, CollectionView } from 'marionette';

const ChildView = View.extend({
  initialize(options) {
    console.log(options.foo); // => "bar"
  }
});

const MyCollectionView = CollectionView.extend({
  childView: ChildView,

  childViewOptions: {
    foo: 'bar'
  }
});

You can also specify the childViewOptions as a function, if you need to calculate the values to return at runtime. The model will be passed into the function should you need access to it when calculating childViewOptions. The function may return an object, null, or undefined. The attributes of a returned object will be copied to the childView instance's options. Whether provided directly or returned by a function, the object's own enumerable properties, including symbols, are copied by object spread. null or undefined adds no extra options. A supplied model option overrides the source model; use that only when the child deliberately represents different data.

import { CollectionView } from 'marionette';

const MyCollectionView = CollectionView.extend({
  childViewOptions(model) {
    // do some calculations based on the model
    return {
      foo: 'bar'
    };
  }
});

CollectionView's emptyView#

When a collection has no children, and you need to render a view other than the list of childViews, you can specify an emptyView attribute on your collection view. The emptyView, like the childView, can be passed as an option on instantiation. It must be a View class or a resolver that returns a View class. Marionette calls resolvers with the CollectionView as this; arrow and bound functions retain their normal JavaScript this semantics.

If the resolved emptyView property is undefined, null, or false, no empty view is rendered. Because an undefined constructor option does not replace an inherited value, use null or false to disable an inherited definition. A resolver may return a View class or undefined, null, or false to disable the empty view. The public types describe these alternatives; Marionette trusts the result when the collection is empty. Errors thrown by a resolver propagate unchanged.

When the empty collection is rendered or filtered again, a disabled result also removes any empty View already shown.

import _ from 'underscore';
import { View, CollectionView } from 'marionette';

const MyEmptyView = View.extend({
  template: _.template('Nothing to display.')
});

const MyCollectionView = CollectionView.extend({
  // ...

  emptyView: MyEmptyView
});

CollectionView's getEmptyRegion#

When a CollectionView is instantiated it creates a region for showing the emptyView. This region can be requested using the getEmptyRegion method. It uses the resolved childViewContainer when present, otherwise the CollectionView's el, and is shown with replaceElement: false.

Note The CollectionView expects to be the only entity managing the region. Showing things in this region directly is not advised.

const isEmptyShowing = myCollectionView.getEmptyRegion().hasView();

This region can be useful for handling the EmptyView Region Events.

Passing Data to the emptyView#

Similar to childView and childViewOptions, there is an emptyViewOptions property that will be passed to the emptyView constructor. It can be provided as an object literal or as a function.

If emptyViewOptions aren't provided, the CollectionView falls back to childViewOptions. A callable definition receives no model argument and runs with the CollectionView as this; it must support that empty-view call.

import { View, CollectionView } from 'marionette';

const EmptyView = View.extend({
  initialize(options){
    console.log(options.foo); // => "bar"
  }
});

const MyCollectionView = CollectionView.extend({
  emptyView: EmptyView,

  emptyViewOptions: {
    foo: 'bar'
  }
});

Defining When an emptyView shows#

If you want to control when the empty view is rendered, you can override isEmpty:

import { CollectionView } from 'marionette';

const MyCollectionView = CollectionView.extend({
  isEmpty() {
    // some logic to calculate if the view should be rendered as empty
    return this.collection.length < 2;
  }
});

The default implementation of isEmpty returns !this.children.length.

Use getEmptyRegion().hasView() to determine whether an empty View is actually shown. isEmpty() alone does not establish that an emptyView was configured:

import { CollectionView } from 'marionette';

const MyCollectionView = CollectionView.extend({
  // ...
  onRenderChildren() {
    if (this.getEmptyRegion().hasView()) { console.log('Empty View Shown'); }
  }
});

Accessing a Child View#

You can retrieve a view by a number of methods. If the findBy* method cannot find the view, it will return undefined.

Note children is the current presentation container. It can include unrendered children added with preventRender until the next render/filter pass; filtered-out children remain owned but are absent from this container.

CollectionView children's: findByCid#

Find a view by its cid.

const bView = myCollectionView.children.findByCid(buttonView.cid);

CollectionView children's: findByModel#

Find a view by DataApi.key(model). With the default DataApi this is the model object identity. An adapter may use a stable key so that a new model object representing the same item resolves the currently indexed child. This lookup does not promise child retention when a collection observation replaces the model object; see collection observations.

const bView = myCollectionView.children.findByModel(buttonView.model);

CollectionView children's: findByKey#

children.findByKey(key) returns the View indexed by the exact key produced by its DataApi, or undefined when absent. Do not assume this key is the model's id: native Marionette and Backbone models use their provider's identity contract, while snapshot adapters can use an application-selected key.

children.hasView(view) checks that the exact View instance is present under its cid; children.contains(view) checks instance membership as well. These lookups refer to the public presentation container. A filtered-out child can remain owned by the CollectionView without appearing in children. Keep an explicit reference when an application needs to detach such a child; do not reach into private containers.

CollectionView children's: findByIndex#

Find by numeric index (unstable)

const bView = myCollectionView.children.findByIndex(0);

CollectionView children's: findIndexByView#

Find the index of the exact View inside children, or -1 when absent.

const index = myCollectionView.children.findIndexByView(bView);

CollectionView children Iterators And Collection Functions#

The container is iterable: for (const child of list.children) visits the current presentation order. Use children.toArray() when you need a separate array before changing membership.

The container owns the following iteration and collection functions:

  • each
  • map
  • reduce
  • find
  • filter
  • reject
  • every
  • some
  • contains
  • invoke
  • toArray
  • first
  • initial
  • rest
  • last
  • without
  • isEmpty
  • pluck
  • partition

These methods can be called directly on the container, to iterate and process the views held by the container.

each, map, reduce, find, filter, reject, every, some, and partition require callback functions. The public types enforce that contract; unsupported JavaScript callback shapes have no guaranteed Marionette diagnostic. String, object, and null iteratee shorthand is not supported. Structurally adding, removing, or reordering children while a callback runs is unsupported, and these methods do not promise call-start snapshot semantics. Mutating ordinary properties on a child View remains valid.

each(callback, context) visits every child View in order, calls callback as (view, index), binds this to context when provided, and returns the children container. An empty container returns itself without calling the callback.

map(callback, context) calls (view, index) for every child View and returns a new ordered array of callback results. An empty container returns a new []. Use map(view => view.id) or pluck('id') instead of property-name shorthand.

reduce(callback, initialValue, context) calls (accumulator, view, index) in container order and binds optional context. When initialValue is supplied, every child View is visited; an empty container returns that exact value without calling the callback. When it is omitted, the first child View becomes the accumulator and traversal starts at index 1. An empty container without an initial value throws MN0024.

pluck(key) reads key directly from each child View. For example, children.pluck('model') returns the child Views' model objects, and a child without a model contributes undefined. It does not read model attributes; use an explicit callback such as children.map(view => view.model?.get('status')) for those values. Array-form deep paths are not traversed; replace children.pluck(['model', 'cid']) with children.map(view => view.model?.cid). An empty container returns [].

contains(value) checks for the exact child View instance. A child View's model or another object with the same properties is not considered contained. An empty container returns false.

find, filter, reject, every, some, and partition call their predicate with (view, index) and set this to optional context.

find(predicate, context) returns the first child View for which the predicate is truthy, preserving View identity, and stops iterating at that match. It returns undefined when no View matches or the container is empty.

filter(predicate, context) and reject(predicate, context) visit every child View and return new ordered arrays containing the Views for which the predicate is truthy or falsey, respectively. Changing a returned array does not change the container. An empty container returns [] without calling the predicate.

every(predicate, context) returns false and stops at the first falsey result; otherwise it returns true. some(predicate, context) returns true and stops at the first truthy result; otherwise it returns false. For an empty container, every returns true and some returns false, without calling the predicate.

partition(predicate, context) visits every child View and returns [matchingViews, rejectedViews]. Both members are new arrays that preserve the container order and contain the exact child View instances. An empty container returns [[], []] without calling the predicate.

invoke(methodName, ...args) requires a direct string method name, invokes that method with each child View as this, forwards args, and returns a new ordered array of results. TypeScript restricts the name to callable child methods and checks their arguments and result types. Function-form and deep-path method names are not supported. An empty container returns [].

toArray() returns a new array containing the current child Views in container order. Changing the returned array's membership or order does not change the container. An empty container returns [].

Without a count, first() and last() return the first or last child View. With a nonnegative integer count, they return a new ordered array containing up to that many Views from the corresponding end of the container. A count of 0 returns []. For an empty container, the no-count forms return undefined and the count forms return [].

initial(count = 1) and rest(count = 1) return new ordered arrays after excluding count Views from the end or start of the container, respectively. The count is a nonnegative integer: 0 returns a new array of every child View, and a count greater than or equal to the container length returns []. An empty container also returns []. first, initial, rest, and last throw MN0024 when a supplied count is not a nonnegative integer.

without(...views) returns a new ordered array excluding the exact child View instances supplied. Models and lookalike objects do not exclude their associated Views. With no arguments it returns a new array of every child View. Changing the returned array's membership or order does not change the container. An empty container returns [].

children.isEmpty() reports whether the child container currently has zero Views. It is distinct from the overridable CollectionView#isEmpty() method, which controls whether a CollectionView renders its emptyView.

The child container is iterable. for...of, spread, destructuring, and Array.from(children) yield the exact child View instances in container order. The iterator is defined once on the prototype rather than allocated as an own property on every container.

The former undocumented Underscore aliases forEach, detect, select, all, any, and include are not part of the v5 container. Use each, find, filter, every, some, and contains, respectively.

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

setDataApi(BackboneApi);

const collectionView = new CollectionView({
  collection: new Backbone.Collection()
});

collectionView.render();

// iterate over all of the views and process them
collectionView.children.each(function(childView) {
  // process the `childView` here
});

Listening to Events on the children#

The CollectionView can take action depending on what events are triggered in its children.

Read More:

Self-Managed children#

In addition to children added by Marionette matching the model of a collection, the children of the CollectionView can be manually managed.

Adding a Child View#

The addChildView method can be used to add a view that is independent of your collection source. This method takes three parameters, the child view instance, optionally the index for where it should be placed within the CollectionView's children, and an options hash. It returns the added view.

import { CollectionView, View } from 'marionette';

const ChildView = View.extend({
  tagName: 'li',
  template() {
    return 'Model';
  }
});

export function runChildOwnershipLifecycle() {
  const collectionView = new CollectionView({ tagName: 'ul' });
  const reusableChild = new ChildView();
  const remainingChild = new ChildView();

  collectionView.render();
  collectionView.addChildView(reusableChild);

  const detachedChild = collectionView.detachChildView(reusableChild);
  collectionView.addChildView(detachedChild);
  collectionView.removeChildView(detachedChild);

  collectionView.addChildView(remainingChild);
  collectionView.destroy();
}

detachChildView() returns the same live View and transfers responsibility to the caller. That View may be added again without rendering it a second time. removeChildView() destroys the removed View, while destroying the CollectionView destroys every child that it still manages.

An omitted or null index appends the child before sorting and filtering. The options-only form follows the same rule; use a numeric index to choose an insertion position.

A numeric index bypasses sorting and filtering for that addition only. A later sort() or filter() processes the child normally. The numeric index in an options object takes precedence over the separate positional argument.

Errors Adding a View that is still managed by a Region or CollectionView throws MN0003. Detach the View from its current owner before transferring it.

Filtering a child out or adding it with preventRender still leaves it managed by that CollectionView. Use detachChildView() to transfer it to another owner.

preventRender option#

If you wish to add a child view to the children without the collectionview rendering the children use the preventRender option.

import { CollectionView } from 'marionette';
import ButtonView from './button-view';

const myCollectionView = new CollectionView();

const insertIndex = 0; // Add to the top

myCollectionView.addChildView(new ButtonView(), { preventRender: true, index: insertIndex });
myCollectionView.addChildView(new ButtonView(), insertIndex, { preventRender: true });
myCollectionView.addChildView(new ButtonView());  // renders all three children

Removing a Child View#

The removeChildView method is useful if you need to remove and destroy a view from the CollectionView without affecting the view's collection. In most cases it is better to use the data to determine what the CollectionView should display.

This method accepts the child view instance to remove as its parameter. It returns the removed view.

Later updates to the retained model do not recreate its removed View. Rendering the CollectionView again or resetting its collection rebuilds its children from the current collection.

import { CollectionView } from 'marionette';

// Fragment for a collection using the Backbone DataApi.
const MyCollectionView = CollectionView.extend({
  childViewEvents: { 'foo:event': 'onChildViewFooEvent' },
  onChildViewFooEvent(childView, model) {
    // NOTE: we must wait for the server to confirm
    // the destroy PRIOR to removing it from the collection
    model.destroy({wait: true});

    // but go ahead and remove it visually
    this.removeChildView(childView);
  }
});

Detaching a Child View#

The detachChildView method is the same as removeChildView with the exception that the removed view is not destroyed.

Swapping Child Views#

Swap the location of two views in the CollectionView children and in the el. This can be useful when sorting is arbitrary or is not performant.

Errors If either of the two views aren't part of the CollectionView an error will be thrown.

If only one of the two children is in the presentation children container, filter is called after swapping their owned order.

import BackboneApi from '@mnjs/adapters/backbone';
import Backbone from 'backbone';
import { CollectionView, setDataApi } from 'marionette';
import MyChildView from './my-child-view';

setDataApi(BackboneApi);

const collection = new Backbone.Collection([
  { name: 'first' },
  { name: 'middle' },
  { name: 'last' }
]);

const myColView = new CollectionView({
  collection: collection,
  childView: MyChildView
});

myColView.render();
myColView.swapChildViews(myColView.children.first(), myColView.children.last());

myColView.children.first().model.get('name'); // "last"
myColView.children.last().model.get('name'); // "first"

Sorting the children#

The sort method will loop through the CollectionView children prior to filtering and sort them with the viewComparator. By default, if a viewComparator is not set, the CollectionView will sort the views by the order of the models in the collection. If set to false, presentation sorting is disabled. Normalized collection observations still reconcile the keyed children to source order when sortWithCollection is enabled.

This method is called internally when rendering. sort and before:sort events fire when owned children exist and a comparator is active.

By default the CollectionView will maintain a sorted collection's order in the DOM. This behavior can be disabled by specifying {sortWithCollection: false} on initialize.

Default source ordering uses each notification's captured snapshot. A nested notification waits for the current sort, filter, and render pass to finish. Calling sort() outside a collection notification reads the current source after before:sort. With the default comparator, manually added children whose models are absent from the source stay before the source children.

Custom comparators still determine their own order and data reads. With sortWithCollection enabled, source order breaks ties and manually added children follow source children on ties. With it disabled, ties retain the existing child order.

Defining the viewComparator#

CollectionView allows for a custom viewComparator option if you want your CollectionView's children to be rendered with a different sort order than the underlying collection uses.

import { CollectionView, View } from 'marionette';

const RowView = View.extend({ template: ({ rank }) => String(rank) });
const myCollectionView = new CollectionView({
  collection: [{ rank: 2 }, { rank: 1 }],
  childView: RowView,
  viewComparator: 'rank'
});
import BackboneApi from '@mnjs/adapters/backbone';
import Backbone from 'backbone';
import { CollectionView, setDataApi, View } from 'marionette';

setDataApi(BackboneApi);
const RowView = View.extend({ template: ({ id }) => String(id) });

const myCollection = new Backbone.Collection([
  { id: 1 },
  { id: 4 },
  { id: 3 },
  { id: 2 }
]);

myCollection.comparator = 'id';

const myDescendingView = new CollectionView({
  childView: RowView,
  collection: myCollection,
  viewComparator: childView => -childView.model.id
});

const mySourceOrderView = new CollectionView({
  childView: RowView,
  collection: myCollection,
  viewComparator: false
});

myDescendingView.render(); // 4 3 2 1
mySourceOrderView.render(); // 1 4 3 2

myCollection.sort();
// myDescendingView remains 4 3 2 1
// mySourceOrderView reconciles to source order: 1 2 3 4

A viewComparator can be a one-argument criterion function, a two-argument comparison function, or a string naming a model attribute read through DataApi. Functions receive child Views, not models, and run with the CollectionView as this. These forms do not require Backbone.

A string or single-argument comparator evaluates one criterion per child View and sorts stably. Equal, NaN, or otherwise incomparable criteria retain their existing order, while undefined criteria sort last. A string comparator therefore places a child without a model last. Two-argument comparators retain native Array#sort semantics. Sorting keeps the same children container in use. If evaluating or comparing a single-argument criterion throws, the error propagates without changing the child order.

getComparator#

Override this method to determine which viewComparator to use.

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

setDataApi(BackboneApi);

const MyCollectionView = CollectionView.extend({
  sortAsc(view) {
    return view.model.get('order');
  },
  sortDesc(view) {
    return -view.model.get('order');
  },
  getComparator() {
    // The collectionView's model
    if (this.model.get('sorted') === 'ASC') {
      return this.sortAsc;
    }

    return this.sortDesc;
  }
});

setComparator#

The setComparator method updates viewComparator and calls sort() when the value changes. { preventRender: true } defers that sort/filter/child-render pass. It returns the CollectionView and does not run the parent before:render/render lifecycle. Call it after initial rendering, or defer the pass until the initial render().

import { CollectionView, View } from 'marionette';

const RowView = View.extend({ template: ({ orderBy }) => String(orderBy) });
const cv = new CollectionView({
  collection: [{ orderBy: 2 }, { orderBy: 1 }],
  childView: RowView
});

cv.render();

// Note: the setComparator is preventing the automatic re-render
cv.setComparator('orderBy', { preventRender: true });

// Apply the order without rebuilding the children or parent template
cv.sort();

removeComparator#

This function is actually an alias of setComparator(null, options). It is useful for removing the comparator. removeComparator also accepts preventRender as a option.

import { CollectionView, View } from 'marionette';

const RowView = View.extend({ template: ({ orderBy }) => String(orderBy) });
const cv = new CollectionView({
  collection: [{ orderBy: 2 }, { orderBy: 1 }],
  childView: RowView
});

cv.render();

cv.setComparator('orderBy');

//Remove the current comparator without rendering again.
cv.removeComparator({ preventRender: true });

Maintaining the collection's sort#

By default the CollectionView will maintain a sorted collection's order in the DOM. This behavior can be disabled by specifying {sortWithCollection: false} on initialize or on the view definiton.

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

setDataApi(BackboneApi);
const RowView = View.extend({ template: ({ id }) => String(id) });

const myCollection = new Backbone.Collection([
  { id: 1 },
  { id: 4 },
  { id: 3 },
  { id: 2 }
]);

myCollection.comparator = 'id';

const mySortedColView = new CollectionView({
  childView: RowView,
  collection: myCollection
});

const myUnsortedColView = new CollectionView({
  childView: RowView,
  collection: myCollection,
  sortWithCollection: false
});

mySortedColView.render(); // 1 4 3 2
myUnsortedColView.render(); // 1 4 3 2

myCollection.sort();
// mySortedColView auto-renders 1 2 3 4
// myUnsortedColView has no change

Filtering the children#

The filter method will loop through the CollectionView's sorted children and test them against the viewFilter. The views that pass the viewFilter are rendered if necessary and attached to the CollectionView and the views that are filtered out will be detached. After filtering the children will only contain the views to be attached.

If owned children exist and an active viewFilter is applied, the filter and before:filter events will trigger.

The CollectionView refilters during normalized collection updates and sorting. An arbitrary child property change does not itself trigger filtering; call filter() when application-owned presentation criteria change.

Note This is a presentation functionality used to easily filter in and out constructed children. All children of a collection will be instantiated once regardless of their filtered status. If you would prefer to manage child view instantiation, you should filter the collection itself.

Defining the viewFilter#

CollectionView allows for a custom viewFilter option if you want to prevent some of the underlying children from being attached to the DOM. A viewFilter can be a function, predicate object, or string. Use null or false to disable it. Other shapes are unsupported; core does not guarantee a diagnostic for an invalid filter.

viewFilter as a function#

The viewFilter function takes a view from the children and returns a truthy value if the child should be attached, and a falsey value if it should not. It runs with the CollectionView as this and receives the child View, index, and the live backing child array. A filter pass captures the array's initial length, visits every index densely, and does not visit entries appended during that pass.

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

setDataApi(BackboneApi);
const SomeChildView = View.extend({ template: ({ value }) => String(value) });
const SomeEmptyView = View.extend({ template: () => 'No matches' });

const cv = new CollectionView({
  childView: SomeChildView,
  emptyView: SomeEmptyView,
  collection: new Backbone.Collection([
    { value: 1 },
    { value: 2 },
    { value: 3 },
    { value: 4 }
  ]),

  // Only show views with even values
  viewFilter(view, index, children) {
    return view.model.get('value') % 2 === 0;
  }
});

// renders the views with values '2' and '4'
cv.render();

viewFilter as a predicate object#

The viewFilter predicate object will filter against the view's model attributes. Each filter pass snapshots the predicate's own enumerable string keys and values in standard JavaScript own-key order. Inherited, symbol, and non-enumerable keys are ignored. Every predicate key must exist in the model attributes and its value must compare strictly equal; nested objects therefore match by identity. Arrays are not predicate objects.

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

setDataApi(BackboneApi);
const SomeChildView = View.extend({ template: ({ value }) => String(value) });
const SomeEmptyView = View.extend({ template: () => 'No matches' });

const cv = new CollectionView({
  childView: SomeChildView,
  emptyView: SomeEmptyView,
  collection: new Backbone.Collection([
    { value: 1 },
    { value: 2 },
    { value: 3 },
    { value: 4 }
  ]),

  // Only show views with value 2
  viewFilter: { value: 2 }
});

// renders the view with values '2'
cv.render();

viewFilter as a string#

The viewFilter string represents the view's model attribute and will filter truthy values.

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

setDataApi(BackboneApi);
const SomeChildView = View.extend({ template: ({ value }) => String(value) });
const SomeEmptyView = View.extend({ template: () => 'No matches' });

const cv = new CollectionView({
  childView: SomeChildView,
  emptyView: SomeEmptyView,
  collection: new Backbone.Collection([
    { value: 0 },
    { value: 1 },
    { value: 2 },
    { value: null },
    { value: 4 }
  ]),

  // Only show views 1,2, and 4
  viewFilter: 'value'
});

// renders the view with values '1', '2', and '4'
cv.render();

getFilter#

Override this function to programatically decide which viewFilter to use when filter is called.

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

setDataApi(BackboneApi);

const MyCollectionView = CollectionView.extend({
  summaryFilter(view) {
    return view.model.get('type') === 'summary';
  },
  getFilter() {
    if (this.collection.length > 100) {
      return this.summaryFilter;
    }
    return this.viewFilter;
  }
});

setFilter#

The setFilter method updates viewFilter and calls filter() when the value changes. { preventRender: true } defers that filter/child-render pass. It returns the CollectionView without running the parent render lifecycle. Call it after initial rendering, or defer the pass until the initial render().

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

setDataApi(BackboneApi);
const RowView = View.extend({ template: ({ value }) => String(value) });
const cv = new CollectionView({
  collection: new Backbone.Collection([{ value: 1 }, { value: 2 }]),
  childView: RowView
});

cv.render();

const newFilter = function(view, index, children) {
  return view.model.get('value') % 2 === 0;
};

// Note: the setFilter is preventing the automatic re-render
cv.setFilter(newFilter, { preventRender: true });

// Apply the new filter while retaining surviving child instances.
cv.filter();

removeFilter#

This function is actually an alias of setFilter(null, options). It is useful for removing filters. removeFilter also accepts preventRender as a option.

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

setDataApi(BackboneApi);
const RowView = View.extend({ template: ({ value }) => String(value) });
const cv = new CollectionView({
  collection: new Backbone.Collection([{ value: 1 }, { value: 2 }]),
  childView: RowView
});

cv.render();

cv.setFilter(function(view, index, children) {
  return view.model.get('value') % 2 === 0;
});

// Remove the current filter without rendering again.
cv.removeFilter({ preventRender: true });