This article is part of a series about MarionetteJS and Brunch.io. While I try to keep the articles as abstract as possible so they wont depend too much on previous ones, I would encourage you guys to read all the previous articles as I go about explaining tons of concepts that may help you understand the articles better.

< Previous

Hey everyone, hope you are all okay. Sorry for the late article, I moved to London early this month and between the new job and finding a new house, Christmas and New Year, I didn’t had much time to write it, but here we are now :D

Last article we introduced Views and its properties like templates and regions, but we only talked about one kind of view — Mn.View . You can use it for all your views across the project without problem but sometimes you will struggle with some specific use-cases:

What if you have a Collection and you want to render a View for each Model ?

You would have to map your collection and render a View for each model right? While this doens’t seem so problematic, it can be if you try to add some fancy logic to it, like what if the collection is empty and you want to display a different View for it? You are starting to make specific conditions that may not look so pretty on your code.

Well but don’t worry Marionette got you covered :D

Aside from Mn.View , Marionette ships with 3 more types of Views, Mn.CollectionView , Mn.CompositeView and Mn.NextCollectionView each one created to address specific use-cases.

Mn.CollectionView

On the previous article we had a View to render user information with templates, regions and UI right? But the data source of that View was a Mn.Model meaning that View has only good to render information of a single user. But what if we have a collection of user models and we want to render this view for each one of them?

Like we approached above, we would have to map the collection and render the view for each model. This may not be the best approach though as we may need to add more fancy logic to it.

Mn.CollectionView was created to address this exact problem (Hype!). Differently from a Mn.View , this one accepts a Bb.Collection as data source instead of a Bb.Model and has no template or UI which makes sense because is essentialy a fancy way to render multiple views for each model in a collection, and not so much an actual view itself.

So lets take the following example:

import UserView from './UserView'const UserCollection = Bb.Collection({...})const UserCollectionView = Mn.CollectionView.extend({
collection: UserCollection,
tagName: 'ul',
className: 'user-list',

childView: UserView
});

Just like this, we got a list of UserViews (one for each model) displaying on the screen. Yap, just like that!

Everytime we make a view, that view will be wrapped inside a div on the DOM, to change this behavior we can use the property tagName. In this case, the view will be wrapped inside a ul instead of a div, which may make more sense if your are trying to render a list of items.

Also, you may want to give a specific class name for this view, you know for css purposes and stuff like that. To do that we use the property className.

Notice: You can use this two properties in any type of View, frankly I just forgot to cover them on the last section :P

Now we have the childView property, in it you want to put the View responsible to render each model, in our case, UserView.

Internally Mn.CollectionView will map all the models inside the collection and render the View we specified for each one of them. This is also done inside the onRender life cycle, like we approached before this allows the views to be rendered in a single draw, preventing degradation of performance by interacting multiple times with the DOM.

WAIT WAIT WAIT! So this so called “CollectionView” is only good to map models in a collection and render Views for each one of them? Thats sounds exactly the same as doing an actual map on the collection like you said before, why bother then?

True, the main point of using a Mn.CollectionView is indeed map models in a collection and render Views for each one of them, but this is only the tip of the iceberg, now comes the real good part :D

EmptyView

Say you want to render Views for each models in a collection, the above example will work but you have to remember something, collection fetching is an asynchronous process, so by the time you render the View the collection may still be empty. Yes you can make so the View is only rendered after the collection is successfully fetched but at this time and age nobody does that, cause you will leave the user with a blank and probably unresponsive page.

To solve this, Mn.CollectionView comes with a way to display a different view whenever your collection is empty. This view can be a normal View that you can set up maybe to display some placeholders, or show a message like ‘Loading…’, this part is really up to you.

import UserView from './UserView.js'const UserCollection = Bb.Collection({...})const EmptyView = Mn.View({
initialize(){
console.log('Loading...');
}
});
const UserCollectionView = Mn.CollectionView.extend({
...
childView: UserView,
emptyView: EmptyView
});

Just by doing this Mn.CollectionView automaticly switches views depending on the length of your collection. You can even set a custom behavior to show the empty view, say you want to show it up until your collection has at least 5 models in it:

...const UserCollectionView = Mn.CollectionView.extend({
...
emptyView: EmptyView,
isEmpty(options)
{
return this.collection.length < 5;
}
});

You can do it by using the property isEmpty, this way the empty view will be displayed until the collection has at least 5 models, after that it automaticly switches to the child view.

Listeners

All this magic is possible because Mn.CollectionView binds itself to a couple of Bb.Collection events: add , remove and reset .

Each and every time you trigger one of those events on the collection, Mn.CollectionView will do some magic!

If you trigger reset , Mn.CollectionView will re-render itself automatically, if you trigger add or remove , Mn.CollectionView will render/remove from DOM only the specific view for that model. This allows you to dynamically manipulate the collection and always have the latest info on the DOM with minimal performance impact and 0 additional lines of code, sweet right? :D

Also if you want to re-render the entire Mn.CollectionView without manipulating the collection, you can use the method render : Mn.CollectionView.render()

Mn.CompositeView

As we learned, a Mn.CollectionView is not so much a view itself, it doesn’t have his own template so it’s basically a container to hold other views, but sometime we may want a template and assign a region of that template for child view rendering.

Well, thats essentially what a Mn.CompositeView is, an extended type of a Mn.CollectionView that allows you to define a template for it.

Differently from a View that uses a model as data source, or a CollectionView that uses a collection, Mn.CompositeView being a mix of the two can hold both a model and a collection. It uses the model to populate its own template, and the collection to render child views on the designated region of the template.

...
const UserCompositeView = Mn.UserCompositeView.extend({
template: ...,
model: ...,
collection: ...,
childView: ...,
childViewContainer: ".childview-container"
});

All the properties used are already known, the only one we need to cover is childViewContainer , as the name suggests is simply the property to hold the jQuery selector for the element on the DOM that will be used to render the child views.

Properties

Being an extension of Mn.CollectionView , all its properties are available at the Mn.CompositeView .

Listeners

The same listeners from the Mn.CollectionView are present with the same behavior, for the exeption of the render method, in this case if you call render the entire Mn.CompositeView will be re-render, including its template. If you only wish to re-render the child views you have to call the method renderChildren

Mn.NextCollectionView

The good developers of Marionette have been working on a new version of the CollectionView to replace the old one. This version has a more straightforward flow and more consistent behavior resulting on better performance. This version is still on tests and hopefully will be out on Marionette v4, until then you can read more about it at https://medium.com/blog-marionettejs/next-collection-view-d335f5a9736a

And that’s it guys, hopefully you now have a better understanding on how Views work in Marionette. There’s still a lot to cover for views but lets leave that for the advance section later on. Hope this article was helpfull, sorry again for the delay and as always see you guys next time :D

I’ll be adding a simple project on github covering what we have be learning ‘till now, and I’ll be adding to it on each new article. Hopefully I’ll get it ready next week :D

--

--