The v2 CHANGELOG is meant to be a succinct description of each change made for v2. But such terse statements are only so helpful. You might often be left wondering why a particular change was made. That's question the Changelog Detail is intended to answer.
To begin, select a v2 change from the menu below.
-
Removed the
Marionette.$
proxy. We are now usingBackbone.$
instead. -
Stricter JSHint rules were implemented for more consistent style across the codebase.
-
All instances of the word
type
in the API have been replaced with the wordclass
-
Most classes now have
bindEntityEvents
attached to their instance -
Most classes now have
unbindEntityEvents
attached to their instance
-
Regions now throw an error when they are instantiated with a nonexistent el.
-
Regions now use
.innerHtml
when clearing contents as compared thisthis.$el.empty()
. -
region.show
now returnthis
, just like how a view’s render returnsthis
. -
Regions trigger two new events,
before:swap
andswap
, when it swaps views when callingshow
. -
Regions now expose region.el and region.$el, just like View’s.
-
Calling show on a region with the same view that is currently shown is now a noop.
-
Returning false from
onBeforeClose
no longer prevents the view from closing. -
A new triggerMethod was added to collectionView:
before:child:remove
. -
childEvents callbacks no longer receives the event name as the first argument.
-
compositeView
now calls_onCollectionAdd
when adding a child view as compared toaddChildView
-
collectionView’s and compositeView’s method
onChildRemove
is now known as_onCollectionRemove
-
LayoutViews now lets you specify the
regions
hash as an option upon instantiation.
================
The Marionette.$
proxy of Backbone.$
was in its own category of Marionette objects called
"Configuration." It was
billed as a way to globally change the behavior of the framework, and was used everywhere internally instead of Backbone.$
.
We couldn't find a use case for why you would want to overwrite Marionette's $ and not Backbone's, so we scrapped it.
You might be using Marionette.$ in your apps. If so, it should be an easy swap to start using Backbone.$ instead.
triggerMethod now calls on
MethodName before triggering the event. Previously it was the other way around.
This is a much bigger change than you might initially think. TriggerMethod is the glue that powers Marionette. I might even go so far as to say that if there's anything that might cause major architectural rewrites than it would be this.
TriggerMethod does two things, but in v2 the order of those things is swapped. In v1 it first triggered an event of the same name
that you passed in. For instance, this.triggerMethod('some:event');
would call Backbone.Events' this.trigger('some:event')
internally. After that, it converts the name of the event to a camelcase function name, and executes it if it exists. In the above
example, it would search for onSomeEvent
and fire it. Every Marionette event and callback are actually powered by triggerMethod
internally.
So why the change?
We believe that events on a class are more likely to be handled internally by the class first, before being responded to by
the outside world. And when you call trigger
before firing the callback, other objects listening in will learn about
the event first. By switching the order we allow for the object to hear the event first.
This change probably won't affect your code, but we included it in the Changelog anyway. It's just a new set of incredibly strict linting rules to ensure that our code remains as consistent as possible.
This is another non-breaking change. The current version of the library is now available as Marionette.VERSION
.
We think that developers are most likely to think of Marionette and Backbone objects as 'Classes' rather than 'Types'. So wherever we had the word type in a method or option, we switched it up. We felt that this would make the API marginally easier to comprehend for newcomers to the framework, while only being a painless regex update for folks updating the library.
Marionette.getOption is a helper method that's been around for quite some time, but in the past our Classes have called it by passing
this
in as the first argument. For instance, Marionette.getOption(this, 'myOption');
. We didn't like this, and wanted to make it a
class-level method. This would let us call it via this.getOption('myOption');
. So we did.
We decided to implement this in a backwards-compatible way by introducing a new helper, Marionette.proxyGetOption
, which wraps the
old function and binds the context. Because of this, you can continue to use the old method. Do note that the old syntax is likely to become
deprecated, so we recommend switching over as soon as possible!
See the above.
See the above.
There used to be three builds of Marionette: just the core files (no Wreqr or Babysitter), a bundled version of the core, and a a UMD build of the core. There are just two builds now: bundled and not-bundled. Both are UMD.
The biggest change here is that the directory structure of the built files has changed. Be sure to update your build process to reference the new files, especially if you use UMD Marionette!
The arguments of a Module’s initialize function are now consistent with Module constructor argument order.
When we added the initialize function to Modules we inexplicably left out the second argument from the function. Our mistake. We quickly added it back, but had to add it as the third argument to retain backwards compatible. This created a mis-match between initialize and constructor that we resolved to fix as soon as possible. So we did, in v2.
This one might be a bit confusing to describe, but I'll do my best. Let's start at the beginning. In the Backbone world, as you might
already know, objects have an initialize function that is called when you instantiate a new instance of that object. Because of this,
we like to think of the verb initialize
being reserved from when things are started up. But Applications didn't use it like that.
Instead, Applications were using initialize
to refer to when they were being started! This was bad for a few reasons. Firstly, it
made no sense given the above. It also made no sense because modules had before:start
and start
triggerMethods. To make matters stranger,
the Application still had a start
callback that was identical to the initialize
callback other than the name.
We know, we know. Super confusing. But there is a bit of logic to this madness. You see, Applications have a method addInitializers
, which
adds a series of methods to be called when the Application begins. We think this is the origin of using the verb in this context came from. We
also dislike the name of that method, but that might be changed in a future version of Marionette.
Applications have an instance of an EventAggregator, Commands, and RequestResponse on them. A common use case for these is as a global messaging channel for the entire application. A recent update to Wreqr added an actual Channel object, which is just a collection of the three messaging systems with an associated name. It only made sense for us to replace the unnamed Application instances with a channel named 'global.'
You might be familiar with the fact that you can attach Regions directly to your application. What you might not know is that this is powered by an often-forgotten Marionette class called RegionManager. The details don't really matter, but what does matter is that the RegionManager has triggerMethods that are called before regions are added and removed. For whatever reason these weren't being forwarded onto the Application. Now they are!
This is a subtle change, like many of the evented changes seem to be. stopListening
removes all of the event listeners from the Controller,
but right now is called before the destroy
triggerMethod. In Views, the other classes that call stopListening in their destroy function,
the stopListening happens after close. So we changed it for consistency in the library.
Sometimes you might want to group your behaviors together. For instance, maybe you have form validation, modal, and keybinding behaviors that are meant to work together in some view that was designed to represent a Form. Instead of referencing all three behaviors in each FormView, you can now pull in a single Behavior that is a collection of the three.
Regions only work when you've got an el to work with. Previously you could instantiate a view without an el and nothing would happen, which was very difficult to debug in big applications. This change causes an error to be thrown when the view's el can't be found, which should help with the debugging process.
If you're one of the rare cases where you might want a region to exist before its el does, you can simply create a new class and overwrite the constructor.
This change provides us with a huge performance boost, with no loss of functionality. But it does place more responsibility on you as a developer. Whereas regions would previously clean up any event listeners, preventing memory leaks, they no longer do. This is because it isn't the responsibility of the region to clean up event listeners. Regions themselves don't have any event listeners out of the box in Marionette, so they weren't ever cleaning up their own events. Instead, they were handling a responsibility that belongs to the View.
Just keep in mind that if you're using jQuery plugins or setting custom DOM listeners for your views, be sure to shut them down in the
onBeforeDestroy
callback!
One of the things we did in v2 was make the API of regions more like views. We like this because both Classes are related to displaying content.
The only specification Backbone gives to View's render is that it should return this
, so we added that same feature to a Region's show
method, which is analogous to View's render method.
You're probably used to the show
triggerMethods of Regions, and we added new ones for you to work with. The swap
events are only triggered
when a view that already has a view is showing a new one. In other words, show
is called even when the view goes from being empty to filled,
whereas swap
is only changed when it goes from being occupied by one view to being occupied by another.
Region’s el can now be a DOM node / DOM string selector / Jquery selector instance, just like a View’s el.
One of the great features of Views is the flexibility in specifying their el
. But Regions never had the same flexibility, always
requiring you to pass a selector string. In v2 that has changed!
Just like Views, you can now access the Region's raw el
. In v1 this property always remained the selector string. To access the selector
you can always use this.$el.selector
.
The show
method of a region used to be really destructive. If you went to show a view that was already in the region, that view would
be completely re-rendered. Rendering is an expensive process, so we decided to make this method idempotent in v2. All that means is you can
call it any number of times with the same view, but it will only render that view on the first try.
To get the old functionality back, pass forceShow:true
in the options.
this.region.show(myView, {forceShow: true});
Every Marionette Class let you tune in before the destroy happened with onBeforeDestroy
. But Regions were an unexplained exception to that rule.
We decided to make it consistent by adding the before destroy hook to the Region class.
Of all of the triggerMethod callbacks, onBeforeClose
stood out for this unique feature. If you returned false from it, it would
actually prevent the view from closing. This might seem useful, but it wasn't consistent with the rest of the library, and, maybe
more frighteningly, it wasn't respected by Regions.
Instead of making sure we handle this situation in every case, we decided it would be better to remove it. Instead of using a before hook to cancel an action we believe you should determine that logic before you ever fire the action.
To give an example, instead of returning false in your onBeforeClose, you should have code like this:
if (myCondition) {
this.close();
} else {
// I am not closing!
}
Frequently we are asked why a region has completely removed a view from existence when it calls close on it. These users ask, "we just closed it, can't we open it again?" For those more familiar with the library, you might know that the answer is no, you can't just reopen it because close is meant to be a permanent action; it's intended to set the view up for garbage collection.
Because of this we decided to pick a verb that carries this same weight, and destroy fit the bill.
ItemViews had
Believe it not, collections never used to alert you when before child view was removed. Well, they do now.
CollectionViews have a childEvents
hash, which forwards child events to callbacks on the parent view itself. However,
the arguments passed to these callbacks were inconsistent with the normal callbacks for triggers. The inconsistency was due to
the first argument of the callback, which for childEvents
was the name of the event. In other situations the callbacks for Backbone Events
don't receive the name of the event at all. This change makes childEvents
arguments consistent with the arguments for any other callback.
One of the more confusing bits of the Marionette API was the use of the phrase ItemView
in the CollectionView API. This
suggests that you can only use ItemViews with CollectionViews. If you're familiar with Marionette you know that this isn't the case:
you can use any View class. We hope the more general term 'childView' will make this more explicit.
We decided to change this method to be private, as the internals of a collection view interacting with its collection shouldn't be overridden. We also decided to reference the changing collection rather than adding more API methods that reference children views, as the collection view API is becoming quite full with similar-sounding method names.
The reasoning here is really identical to the above.
Collection and Composite Views now respect the collection comparator when rendering and when new views are added.
One of the most-requested features for Marionette views were automatically sorted Collections. You've now got it! If you'd
rather opt out of this functionality, just pass the new sort
option to be false.
this.collectionView = new MyCollectionView({
sort: false
});
collectionView
and compositeView
now have an optional emptyViewOptions property which allows you to customize your emptyView
.
To complement the childViewOptions
you can use to style your child views we now have emptyViewOptions
. It works exactly as you'd expect.
If you're not familiar with this method, it's the one that renders the template of the Composite View. We began by deciding that it
should be private, then went on to decide that calling it renderModel
was really confusing, as many people working with Composite Views
think of the models as being in the ones in the collection. So we decided to call it the root as a reference to the visualization of
a composite view as a tree view.
Layouts can be difficult for newcomers to approach, and we don't think their name helps at all. A Layout is really just a View beneath all of
its fancy features, so we decided to rename it to LayoutView
in v2.
Layouts now facilitate overriding the default RegionManager with a custom Class through the use of the getRegionManager
method.
Ah, yes, the RegionManager. Layouts get their Region superpowers from a Marionette class called the RegionManager. It used to be difficult
to override the RegionManager because there was no helper function to do so. Now you can use getRegionManager
to easily provide
your own, should you feel so inclined.
There was previously no way to instantiate a LayoutView by itself. You absolutely needed to create a new Class for it. The reason for this
is because the regions
property was ignored if you passed it in as an option to Regions. In v2 we changed this, so you can now define a
basic LayoutView without creating a brand new class.
var myLayout = new LayoutView({
model: myModel,
template: myTemplate,
regions: {
myDiv: 'div.mine'
}
});