5.0.0-beta.1 · Published beta · b06750c5. Published on npm. Match your installed version.
ConsumerMarionette.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:
- The DOM API
- Class Events
- DOM Interactions
- Child Event Bubbling
- Entity Events
- View Rendering
- Prerendered Content
- View Lifecycle
A CollectionView can have Behaviors.
Documentation Index#
- Instantiating a CollectionView
- Rendering a CollectionView
- View Lifecycle and Events
- Entity Events
- DOM Interactions
- Behaviors
- Managing Children
- CollectionView's
childView - CollectionView's
emptyView - Accessing a Child View
- Listening to Events on the
children - Self Managed
children - Sorting the
children - Filtering the
children
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:
eachmapreducefindfilterrejecteverysomecontainsinvoketoArrayfirstinitialrestlastwithoutisEmptypluckpartition
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 });