A CollectionView
like View
manages a portion of the DOM via a single parent DOM element
or el
. This view manages an ordered set of child views that are shown within the view's el
.
These children are most often created to match the models of a Backbone.Collection
though a
CollectionView
does not require a collection
and can manage any set of views.
CollectionView
includes:
A CollectionView
can have Behavior
s.
childView
emptyView
children
children
children
children
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
, tagName
, template
, templateContext
,
triggers
, ui
, viewComparator
, viewFilter
import { CollectionView } from 'backbone.marionette';
const myCollectionView = new CollectionView({ ... });
Some of these properties come from Marionette, but many are inherited from
Backbone.View
.
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 'backbone.marionette';
const MyCollectionView = CollectionView.extend({...});
// all of the children views will now be rendered.
new MyCollectionView().render();
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.
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 'backbone.marionette';
const MyCollectionView = CollectionView.extend({
childViewContainer: '.js-widgets',
template: _.template('<h1>Widgets</h1><ul class="js-widgets"></ul>')
});
Errors An error will throw if the childViewContainer can not be found.
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.
An instantiated CollectionView
is aware of its lifecycle state and will throw events related
to when that state changes. The view states indicate whether the view is rendered, attached to
the DOM, or destroyed.
Read More:
The CollectionView
can bind to events that occur on the attached model
and collection
- this
includes both standard backbone-events and custom events.
Read More:
In addition to what Backbone provides the views, Marionette has additional API
for DOM interactions: events
, triggers
, and ui
.
By default ui
is only bound to the elements within the template.
However as events
and triggers
are delegated to the view's el
they will apply to any children.
There may be instances where binding ui
is helpful when you want to access elements inside
CollectionView
s children with getUI()
. For these
cases you will need to bind ui
yourself. To do so run bindUIElements
on the CollectionView
:
import { CollectionView } from 'backbone.marionette';
const MyCollectionView = CollectionView.extend({
// ...
ui: {
checkbox: 'input[type="checkbox"]'
}
});
const collectionView = new MyCollectionView();
collectionView.bindUIElements();
console.log(collectionView.getUI('checkbox')); // Output all checkboxes.
Read More:
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:
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.
After the initial render
the CollectionView
binds to the update
and reset
events of the collection
.
When the collection
for the view is reset
, the view will destroy all
children and re-render the entire collection.
When a model is added to the collection
, the CollectionView
will render that
one model into the children
.
When a model is removed from the collection
(or destroyed / deleted), the CollectionView
will destroy and remove that model's child view.
When the collection
for the view is sorted, the view by default automatically re-sorts
its child views unless the sortWithCollection
attribute on the CollectionView
is set
to false
, or the viewComparator
is false
.
import Backbone from 'backbone';
import { View, CollectionView } from 'backbone.marionette';
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.
children
within the el
By default the CollectionView
will add the HTML of each ChildView
into an element buffer array, and then call the DOM API's
appendContents once at the end
to move all of the HTML within the collection view's el
.
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 'backbone.marionette';
CollectionView.extend({
// The default implementation:
attachHtml(els, $container){
// Unless childViewContainer, $container === this.$el
this.Dom.appendContents(this.el, els);
}
});
The first parameter is the HTML buffer, and the second parameter
is the expected container for the children which by default equates
to the view's el
unless a childViewContainer
is set.
children
CollectionView
implements a destroy
method which automatically
destroys its children and cleans up listeners.
When the children are destroyed the
destroy:children
and before:destroy:children
events
will trigger.
Read More:
childView
When using a collection
to manage the children of CollectionView
,
specify a childView
for your CollectionView
. This must be
a Backbone view class definition, not an instance. It can be any
Backbone.View
related class including both Marionette's View
and
CollectionView
.
import { View, CollectionView } from 'backbone.marionette';
const MyChildView = View.extend({});
const MyCollectionView = CollectionView.extend({
childView: MyChildView
});
Errors If you do not specify a childView
, an exception will be thrown
stating that you must specify a 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 Backbone from 'backbone';
import { View, CollectionView } from 'backbone.marionette';
const FooView = View.extend({
template: _.template('foo')
});
const BarView = View.extend({
bar
});
const MyCollectionView = CollectionView.extend({
collection: new Backbone.Collection(),
childView(item) {
// Choose which view class to render,
// depending on the properties of the item model
if (item.get('isFoo')) {
return FooView;
}
else {
return BarView;
}
}
});
const collectionView = new MyCollectionView();
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);
Errors If childView
is a function that does not return a view class
an error will be thrown.
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 = _.extend({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 Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
import MyListView from './my-list-view';
import MyView from './my-view';
const MyCollectionView = CollectionView.extend({
childView(child) {
if (child.get('type') === 'list') {
return MyListView;
}
return MyView;
},
buildChildView(child, ChildViewClass, childViewOptions) {
const 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;
}
});
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 'backbone.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 must return an object, and the attributes
of the object will be copied to the childView
instance's options.
import { CollectionView } from 'backbone.marionette';
const MyCollectionView = CollectionView.extend({
childViewOptions(model) {
// do some calculations based on the model
return {
foo: 'bar'
};
}
});
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
just like the childView
can also be passed as an option on instantiation or can be a
function that returns the emptyView
.
import _ from 'underscore';
import { View, CollectionView } from 'backbone.marionette';
const MyEmptyView = View.extend({
template: _.template('Nothing to display.')
});
const MyCollectionView = CollectionView.extend({
// ...
emptyView: MyEmptyView
});
getEmptyRegion
When a CollectionView
is instantiated it creates a region for showing the emptyView
.
This region can be requested using the getEmptyRegion
method. The region will share the el
with the CollectionView
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.
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
will default to passing the childViewOptions
to the emptyView
.
import { View, CollectionView } from 'backbone.marionette';
const EmptyView = View.extend({
initialize(options){
console.log(options.foo); // => "bar"
}
});
const MyCollectionView = CollectionView.extend({
emptyView: EmptyView,
emptyViewOptions: {
foo: 'bar'
}
});
emptyView
showsIf you want to control when the empty view is rendered, you can override
isEmpty
:
import { CollectionView } from 'backbone.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
.
You can also use this method to determine when the empty view was shown:
import { CollectionView } from 'backbone.marionette';
const MyCollectionView = CollectionView.extend({
// ...
onRenderChildren() {
if (this.isEmpty()) { console.log('Empty View Shown'); }
}
});
You can retrieve a view by a number of methods. If the findBy* method cannot find the view,
it will return undefined
.
Note That children
represents the views rendered that are or will be
attached within the view's el
.
children
's: findByCid
Find a view by its cid.
const bView = myCollectionView.children.findByCid(buttonView.cid);
children
's: findByModel
Find a view by model.
const bView = myCollectionView.children.findByModel(buttonView.model);
children
's: findByModelCid
Find a view by model cid.
const bView = myCollectionView.children.findByModelCid(buttonView.model.cid);
children
's: findByIndex
Find by numeric index (unstable)
const bView = myCollectionView.children.findByIndex(0);
children
's: findIndexByView
Find the index of the view inside the children
const index = myCollectionView.children.findIndexByView(bView);
children
Iterators And Collection FunctionsThe container object borrows several functions from Underscore.js, to provide iterators and other collection functions, including:
These methods can be called directly on the container, to iterate and process the views held by the container.
import Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
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
});
children
The CollectionView
can take action depending on what
events are triggered in its children
.
Read More:
children
In addition to children added by Marionette matching the model of a collection
,
the children
of the CollectionView
can be manually managed.
The addChildView
method can be used to add a view that is independent of your
Backbone.Collection
. 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 } from 'backbone.marionette';
import ButtonView from './button-view';
const MyCollectionView = CollectionView.extend({
onRender() {
View = new ButtonView();
this.addChildView(buttonView, this.children.length);
}
});
const myCollectionView = new MyCollectionView();
myCollectionView.render();
Note Unless an index is specified, this added view will be subject to filtering and sorting and may be difficult to manage in complex situations. Use with care.
Errors An error will be thrown if the view is already shown in a Region or CollectionView.
preventRender
optionIf you wish to add a child view to the children without the collectionview rendering
the children use the preventRender
option.
import { CollectionView } from 'backbone.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
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.
import { CollectionView } from 'backbone.marionette';
const MyCollectionView = CollectionView.extend({
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);
}
});
The detachChildView
method is the same as removeChildView
with the exception that the removed view is not destroyed.
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 one child is in the el
but the other is not, filter will be called.
import Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
import MyChildView from './my-child-view';
const collection = new Backbone.Collection([
{ name: 'first' },
{ name: 'middle' },
{ name: 'last' }
]);
const myColView = new CollectionView({
collection: collection,
childView: MyChildView
});
myColView.swapChildViews(myColView.children.first(), myColView.children.last());
myColView.children.first().model.get('name'); // "last"
myColView.children.last().model.get('name'); // "first"
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
view
sorting will be disabled.
This method is called internally when rendering and
sort
and before:sort
events
will trigger.
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.
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 Backbone collection uses.
import { CollectionView } from 'backbone.marionette';
const myCollectionView = new CollectionView({
collection: someCollection,
viewComparator: 'otherFieldToSortOn'
});
import Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
const myCollection = new Backbone.Collection([
{ id: 1 },
{ id: 4 },
{ id: 3 },
{ id: 2 }
]);
myCollection.comparator = 'id';
const mySortedColView = new CollectionView({
//...
collection: myCollection
});
const myUnsortedColView = new CollectionView({
//...
collection: myCollection,
viewComparator: 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
The viewComparator
can take any of the acceptable Backbone.Collection
comparator formats -- a sortBy
(pass a function that takes a single argument), as a sort (pass a comparator
function that expects two arguments), or as a string indicating the attribute to
sort by.
getComparator
Override this method to determine which viewComparator
to use.
import { CollectionView } from 'backbone.marionette';
const MyCollectionView = CollectionView.extend({
sortAsc(model) {
return -model.get('order');
},
sortDesc(model) {
return model.get('order');
},
getComparator() {
// The collectionView's model
if (this.model.get('sorted') === 'ASC') {
return this.sortAsc;
}
return this.sortDesc;
}
});
setComparator
The setComparator
method modifies the CollectionView
's viewComparator
attribute and re-sorts. Passing { preventRender: true }
in the options argument
will prevent the view being rendered.
import { CollectionView } from 'backbone.marionette';
const cv = new CollectionView({
collection: someCollection
});
cv.render();
// Note: the setComparator is preventing the automatic re-render
cv.setComparator('orderBy', { preventRender: true });
// Render the children ordered by the orderBy attribute
cv.render();
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 } from 'backbone.marionette';
const cv = new CollectionView({
collection: someCollection
});
cv.render();
cv.setComparator('orderBy');
//Remove the current comparator without rendering again.
cv.removeComparator({ preventRender: true });
collection
's sortBy 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 Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
const myCollection = new Backbone.Collection([
{ id: 1 },
{ id: 4 },
{ id: 3 },
{ id: 2 }
]);
myCollection.comparator = 'id';
const mySortedColView = new CollectionView({
//...
collection: myCollection
});
const myUnsortedColView = new CollectionView({
//...
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
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 a viewFilter
exists the
filter
and before:filter
events
will trigger.
By default the CollectionView will refilter when views change or when the CollectionView is sorted.
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.
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.
Errors An error will be thrown if the ViewFilter
is not one of these options.
viewFilter
as a functionThe 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.
import Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
const cv = new CollectionView({
childView: SomeChildView,
emptyView: SomeEmptyView,
collection: new Bb.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 objectThe viewFilter
predicate object will filter against the view's model attributes.
import Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
const cv = new CollectionView({
childView: SomeChildView,
emptyView: SomeEmptyView,
collection: new Bb.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 stringThe viewFilter
string represents the view's model attribute and will filter
truthy values.
import Backbone from 'backbone';
import { CollectionView } from 'backbone.marionette';
const cv = new CollectionView({
childView: SomeChildView,
emptyView: SomeEmptyView,
collection: new Bb.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 { CollectionView } from 'backbone.marionette';
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 modifies the CollectionView
's viewFilter
attribute and filters.
Passing { preventRender: true }
in the options argument will prevent the view
being rendered.
import { CollectionView } from 'backbone.marionette';
const cv = new CollectionView({
collection: someCollection
});
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 });
// Render the new state of the ChildViews instead of the whole DOM.
cv.render();
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 { CollectionView } from 'backbone.marionette';
const cv = new CollectionView({
collection: someCollection
});
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 });