A New Refined CollectionView for Marionette.js

In version 3.3 of Marionette, we will be introducing the Marionette.NextCollectionView along side the current CollectionView. This new CollectionView will bring better performance along with a more consistent API, and in doing so it includes some breaking changes and introduces a lot of new code that needs to be battle tested in the real world. So introducing it alongside the current CollectionView allows developers to gradually try it and migrate prior to needing to move to it in an upcoming major release.

The goals behind this change fall into three major categories:

  • There should be one method and logic flow for doing something.
  • The CollectionView should be more concerned with the child views than the collection models.
  • The API should be intuitive and consistent

One logic flow

Backbone v1.3 released a substantial feature when it included an update event on the collection which is described by backbonejs.org as a, “single event triggered after any number of models have been added or removed from a collection.” Previous to this release a developer was given two options, reset the collection, which in Marionette would re-render the entire CollectionView, or let Marionette add and remove changed views one at a time, each time determining where a view should be inserted or removed in the DOM. If Marionette knew it was re-rendering the entire CollectionView it would create a document fragment to build the DOM within before inserting it all at once, but inserting one at a time made it such that we needed two different ways to render. Thus the current CollectionView contains both an attachBuffer and an attachHtml. Though Marionette was able to utilize the update event to improve performance in some cases, in others it was difficult if not impossible without introducing breaking changes. This complexity introduced added issues when combined with sorting and filtering. An added feature had so many edge-case issues because there were multiple ways to add or remove views, some related to the collection and some not.

So we introduced a simplified logic flow. Regardless of how a child is added or removed, the same logic flow is used.

This means that any view added to the DOM by a collection will be added via the same attachHtml method. It also means that each child view will only be instantiated and rendered if it has to be. Outside of removing and re-adding a model to a collection, a child view will only be rendered by the CollectionView one time (assuming it isn’t pre-rendered), no matter what action you take. Currently the CollectionView will heavy handedly destroy and reinitialize all of its children in many cases such as if any models are added when the CollectionView’s reorderOnSort optimization is set to true.

Concerning Views

Marionette’s current CollectionView has a precarious relationship with its collection. In theory the API would suggest, and in many cases is designed such, that you do not need a collection to use the view. A collection is merely one way (and perhaps the primary way) to manage the views in a CollectionView. However as the CollectionView progressed, some features focused more specifically on the CollectionView’s relation to the collection’s underlying models. The biggest example of this is probably filter.

When filtering a CollectionView currently the view iterates over this.collection.models to filter which views should be included in the children. With this approach, views added outside of the collection whether they have a model or not are never considered by the filter. To further complicate things, models are filtered prior to constructing the children. At first this may not seem like a big deal, but after this point if a model is inserted into the original collection, everything must be reconstructed as the inserted index has no real context with the already filtered children.

In the NextCollectionView the children are determined first. Then sorting and filtering iterate over the children by view and not model. Views that are filtered out are merely detached instead of destroyed and remain in the children. If they are ever filtered back in, they are simply re-attached and do not have to be reconstructed or rendered.

Consistent API

As with any refactor it is important to take a step back and consider the issue as a whole. While iterating on new features on the CollectionView, APIs were added that made sense in context, but it is time to consider how the API could be made more consistent and intuitive. Ideally learning one feature leads to an early understanding of another such that one could assume the API of a different feature without needing to read documentation. The API was modified a little bit in various areas, but the most drastic modification was to sorting and filtering. The chart below compares both sorting and filtering for the next and current CollectionView.

First the actions to perform a sort or filter were aptly named sort and filter. While these methods may not makes sense to call directly often, the option is there for those use-cases. This meant that the current sort flag would need to be renamed, and it was given a name that matches more directly its underlying purpose sortWithCollection. Both sets of features now have getters, setters, and a remove method (which is sugar for set(null)). Additionally the view iterators were similarly named viewFilter and viewComparator and as previously mentioned they both iterate over views now instead of models.

An additional refinement to both iterators is that they can be defined as a string, indicating an attribute on the views model (returning an undefined if the view has no model). Additionally viewFilter can take a predicate object that would also attempt to match the value within the view’s model, ie: view.setFilter({ visible: true });


Other notable breaking changes…

Two other breaking changes worth mentioning regard how the NextCollectionView deals with an emptyView and how the developer can manipulate the children.

In the current CollectionView an emptyView is added like any other child affecting the children.length. Additionally the CollectionView triggers specific events unique to the empty view. In the NextCollectionView, a region is attached to the NextCollectionView’s el and the emptyView is shown in this region. The emptyRegion is made publicly accessible and can be used to understand when an emptyView is shown. Otherwise the emptyView is kept fairly separate from the NextCollectionView children. It is notable that an emptyView may be shown when there are no children or when there are children but they have all been filtered out. Thus it makes sense that the emptyView should not affect the view’s children.

Another significant change is the children can no longer be modified directly. You can still iterate over them or request specific view’s, but you can no longer add or remove children directly. This keeps the CollectionView from getting out of sync with the children container.

A detailed list of breaking changes are on the feature’s PR, https://github.com/marionettejs/backbone.marionette/pull/3244

We need your help!

The NextCollectionView cannot be successful without the involvement of the Marionette community. We need developers to try the new view in real world scenarios. We need help identifying and squashing bugs as well as understanding any performance regressions or new limitations we’ve overlooked. Please, if you can, try out the new view and let us know how it goes in Gitter, or if you run into a problem, open an issue on Github.