A Thorough Introduction To Backbone.Marionette (Part 3)

Advertisement

To help you tap the full potential of Marionette, we’ve prepared an entire eBook1 full of useful hands-on examples which is also available in the Smashing Library2. — Ed.

In this series on Backbone.Marionette, we’ve already discussed Application and Module. This time, we’ll be taking a gander at how Marionette helps make views better in Backbone. Marionette extends the base View class from Backbone to give us more built-in functionality, to eliminate most of the boilerplate code and to convert all of the common code down to configuration.

I highly recommend that you go back and read the articles about Application3 and Module4 first, if you haven’t already. Some things may be mentioned in this article that refer to the previous articles, and this is part of a series about Marionette, so if you wish to learn about Marionette, you should read the whole series.

Event Binding

Up until recently, Backbone views were often mishandled, causing a horrible problem known as “zombie views.” The problem was caused by the views listening to events on the model, which in itself is completely harmless. The problem was that when the views were no longer needed and were “discarded,” they never stopped listening to the events on the model, which means that the model still had a reference to the view, keeping it from being garbage-collected. This caused the amount of memory used by the application to constantly grow, and the view would still be responding to events from the model, although it wouldn’t be rendering anything because it was removed from the DOM.

Many Backbone extensions and plugins — including Marionette — remedied this early on. I won’t go into any detail on that, though, because Backbone’s developers remedied this problem themselves (finally!) in the recently released Backbone 1.0 by adding the listenTo and stopListening methods to Events, which Backbone’s View “class” inherits from. Marionette’s developers have since removed their own implementation of this feature, but that doesn’t mean Marionette doesn’t help us out with some other things related to event binding.

To make binding to events on the view’s models and collections simpler, Marionette gives us a few properties to use when extending Marionette’s views: modelEvents and collectionEvents. Simply pass in an object where the keys are the name of the event we’re listening to on the model or collection, and the property is the name(s) of the function to call when that event is triggered. Look at this simple example:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    modelEvents: {
        'change:attribute': 'attributeChanged render',
        'destroy': 'modelDestroyed'
    },

    render: function(){ … },
    attributeChanged: function(){ … },
    modelDestroyed: function(){ … }
});

This accomplishes the same thing as using listenTo, except it requires less code. Here’s the equivalent code using listenTo.

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    initialize: function() {
        this.listenTo(this.model, 'change:attribute', this.attributeChanged); 
        this.listenTo(this.model, 'change:attribute', this.render); 
        this.listenTo(this.model, 'destroy', this.modelDestroyed);
    },

    render: function(){ … },
    attributeChanged: function(){ … },
    modelDestroyed: function(){ … }
});

There are a couple key things to note. First, modelEvents is used to listen to the view’s model, and collectionEvents is used to listen to the view’s collection (this.model and this.collection, respectively). Secondly, you may have noticed that there are two callbacks for the change:attribute event. When you specify a string for the callbacks, you can have as many callback function names as you want, separated by spaces. All of these functions will be invoked when the event is triggered. Any function name that you specify in the string must be a method of the view.

There are alternative ways to specify modelEvents and collectionEvents, too. First, instead of using a string to specify the names of methods on the view, you can assign anonymous functions:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    modelEvents: {
        'change': function() {
            …
        }
    }
});

This probably isn’t the best practice, but the option is there if you need it. Also, instead of simply assigning an object literal to modelEvents or collectionEvents, you can assign a function. The function will need to return an object that has the events and callbacks. This allows you to create the list of events and callbacks dynamically. I haven’t been able to think of any situations in which you would need to determine event bindings dynamically, but if you need it, this could be very handy.

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    modelEvents: function() {
        return {'destroy': 'modelDestroyed'};
    },

    modelDestroyed: function(){ … }
});

The modelEvents and collectionEvents feature follows the pattern that Backbone and Marionette use as often as possible: Relegate code to simple configuration. Backbone itself did this with the events hash, which enables you to easily set up DOM event listeners. Marionette’s modelEvents and collectionEvents are directly inspired by the original events configuration in Backbone. You’ll see this configuration concept show up a lot, especially in subsequent articles, when we get into ItemView, CollectionView and CompositeView.

Destroying A View

As I mentioned at the beginning of the previous section, sometimes a view needs to be discarded or removed because a model was destroyed5 or because we need to show a different view in its place. With stopListening, we have the power to clean up all of those event bindings. But what about destroying the rest of the view? Backbone has a remove function that calls stopListening for us and also removes the view from the DOM.

Generally, this would be all you need, but Marionette takes it a step further by adding the close function. When using Marionette’s views, you’ll want to call close instead of remove because it will clean up all of the things that Marionette’s views set up in the background.

Another benefit offered by Marionette’s close method is that it fires off some events. At the start of closing the view, it’ll fire off the before:close event, and then the close event when it’s finished. In addition to the events, you can specify methods on the view that will run just before these events are fired.

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    onBeforeClose: function() {
        // This will run just before the before:close event is fired
    },

    onClose: function(){
        // This will run just before the close event is fired
    }
});

If you want to run some code before the view disappears completely, you can use the onBeforeClose and onClose view methods to automatically have it run without your needing to listen to the events. Simply declare the methods, and Marionette will make sure they are invoked. Of course, other objects will still need to listen to the events on the view.

DOM Refresh

Back when we discussed Application, I mentioned Region a bit. I won’t get into this much here (once all of the articles about views are done, I’ll go into more detail), but know that a Region is an object that handles the showing and hiding or discarding of views in a particular part of the DOM. Look at the code below to see how to render a view in a Region.

var view = new FooView(); // Assume FooView has already been defined
region.show(view); // Assume the region was already instantiated. Just use "show" to render the view.

When you use show, it will render the view (all view classes that Marionette implements that are based on this base View class will also call the onRender function if you’ve defined it and will fire a render event when render is invoked), attach it to the DOM and then show the view, which simply means that a show event is fired so that components will know that the view was rendered via a Region. After a view has been rendered and then shown, if the view is rendered again, it will trigger a DOM refresh.

This actually isn’t true at the moment because of a bug, but it’s on the developers’ to-do list. Currently, when a view is rendered, it will set a flag saying that it was rendered. Then, when the view is shown, it will set a flag saying that it was shown. The moment when both of these flags have been activated, it will trigger a DOM refresh. Then, any time after that, the DOM refresh will be triggered any time the view is rendered or shown. Keep this in mind if you need to use this functionality.

When a DOM refresh is triggered, first, it will run the onDomRefresh method of the view (if you defined one) and then trigger the dom:refresh event on the view. This is mostly useful for UI plugins (such as jQuery UI, Kendo UI, etc.) with some widgets that depend on the DOM element they are working with being in the actual DOM. Often, when a view is rendered, it won’t be appended into the DOM until after the rendering has finished. This means that you can’t use the plugin during render or in your onRender function.

However, you can use it in onShow (which is invoked just before the show event is triggered) because a Region is supposed to be attached to an existing DOM node (as we’ll see in a future article). Now, since the view has been shown, you will know that the view is in the DOM; so, every time render is called, a DOM refresh will take place immediately after the rendering, and you can call the UI plugin’s functionality safely again.

DOM Triggers

Sometimes, when a user clicks a button, you want to respond to the event, but you don’t want the view to handle the work. Instead, you want the view to trigger an event so that other modules that are listening for this event can respond to it. Suppose you have code that looks like this:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    events: {
        'click .awesomeButton': 'buttonClicked'
    },
    buttonClicked: function() {
        this.trigger('awesomeButton:clicked', this);
    }
});

The function for handling the click event just triggers an event on the view. Marionette has a feature that allows you to specify a hash of these events to simplify this code. By specifying the triggers property when extending a View, you can assign a hash very similar to the events property; but, instead of giving it the name of one of the view’s methods to invoke, you give it the name of an event to fire. So, we can convert the previous snippet to this:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    triggers: {
        'click .awesomeButton': ' awesomeButton:clicked '
    }
});

And it’ll do nearly the same thing. There is one major difference between these two snippets: the arguments that are passed to the listening functions. In the first snippet, all we passed to the functions listening for the event was this, which was the view. Using triggers, Marionette will pass a single object with three properties as the argument to each of the functions. These three properties are the following:

  • view
    A reference to the view object that triggered the event.
  • model
    A reference to the view’s model property, if it has one.
  • collection
    A reference to the view’s collection property, if it has one.

So, if you were subscribing to the event from the previous snippet, it would look like this:

// 'view' refers to an instance of the previously defined View type
view.on('awesomeButton:clicked', function(arg) {
    arg.view; // The view instance
    arg.model; // The view's model
    arg.collection; // The view's collection
}

I know there isn’t a surplus of use cases for this, but in the few situations where this applies, it can save plenty of hassle.

DOM Element Caching

Often, this.$el isn’t the only element that you’ll need to directly manipulate. In such cases, many people will do something like this:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    render: function() {
        this.list = this.$('ul');
        this.listItems = this.$('li');
        . . .
        // Now we use them and use them in other methods, too.
    }
});

Once again, Marionette makes this simpler by converting this all into a simple configuration. Just specify a ui property that contains a hash of names and their corresponding selectors:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    ui: {
        list: 'ul',
        listItems: 'li'
    }
});

You can access these elements with this.ui.x, where x is the name specified in the hash, such as this.ui.list. This ui property is converted into the cached jQuery objects by the bindUIElements method. If you’re extending Marionette.View, instead of one of the other view types that Marionette offers, then you’ll need to call this method yourself; otherwise, the other view types will call it for you automatically.

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    ui: {
        list: 'ul',
        listItems: 'li'
    },
    render: function() {
        // render template or generate your HTML, then…
        this.bindUIElements();
        // now you can manipulate the elements
        this.ui.list.hide();
        this.ui.listItems.addClass('someCoolClass');
    }
});

Conclusion

We’ve already seen a plethora of features that Marionette brings to views that cut down on the complexity and amount of code required for common tasks, but we haven’t even touched on the most important part. Marionette.View doesn’t handle any of the rendering responsibilities for us, but Marionette has three other view types that do: ItemView, CollectionView and CompositeView.

These view types, which are what you’ll actually be extending in your code (note the “We don’t normally directly extend this view” comment in all of the code snippets), will take a few minor configuration details and then handle the rest of the rendering for you. We’ll see how this is all done in the next article. For now, ponder all of these features that you’ve been introduced to.

(Front page image credits: nyuhuhuu6)

(al, il)

Footnotes

  1. 1 https://shop.smashingmagazine.com/better-backbone-applications-with-marionettejs.html
  2. 2 https://shop.smashingmagazine.com/smashing-library-complete.html
  3. 3 http://www.smashingmagazine.com/2013/02/11/introduction-backbone-marionette/
  4. 4 http://http://www.smashingmagazine.com/2013/04/02/thorough-introduction-backbone-marionette-part-2-modules/
  5. 5 http://backbonejs.org/#Model-destroy
  6. 6 http://www.flickr.com/photos/nyuhuhuu/4443886636/

↑ Back to topShare on Twitter

Joseph Zimmerman is a web developer for Kaplan Professional and runs his own JavaScript blog. When he isn't feeding his coding obsession, he's spending time with his wife and two little boys.

Advertising
  1. 1

    Why should I use backbone, when there are such tools like angular.js, meteor.js and many more convenient tools.

    I’m not about cretinism, the question is about use-cases?

    -4
    • 2

      This probably isn’t the most appropriate post to be asking this question on, but no matter.

      The Backbone vs. Ember vs. Angular vs. Meteor vs. the world debate has been raging for a while now. With Backbone, you need to realize that you are getting far less built in than in practically any other framework. This makes it really easy to learn, but it also requires you to do more on your own. This is where Marionette comes in. It fills a lot of the holes and fleshes Backbone out into a more full-fledged application framework. At this point, Angular and Ember (probably others too, but I don’t really know much about them) will still do more for you, but in the end, it is more about your opinion on the programming paradigms used by each framework than anything.

      Backbone takes a more classical object-oriented approach while Ember and Angular tend to have you build things with callbacks. Neither is right or wrong, but Ember and Angular just don’t sit nicely with the way my brain works. I wish the Backbone/Marionette could do a bit more of the “automagical” stuff that Ember does, but the fact that it doesn’t also makes it more flexible to be used in different ways.

      tldr: It’s more about personal opinion than use cases. Also, I’m not really sure what Cretinism has to do with this.

      9
      • 3

        Sorry that I wrote short misspelled comment. “Cretinism” supposed to be a “criticism” )
        Sorry. If there is any cretinism, that is only from my side ). I really thank you for your response, and article.

        I’ve used backbone at first and loved it a lot. It was my favorite framework. Even after angular showed up, I steel did not wanted something else. Than I start feeling that I do a lot of routing work, and opened for myself Backbone.Marionette.js.

        About a 6 month ago, I tried angular just for fun, and I realized that it saves me 2-3 time more time, than developing with backbone.mariononate, and code is more understandable. Maybe I missed something with Marionette, but this is my observation and personal opinion.

        Thank you for your article, and time. It is nice!

        1
        • 4

          I’ve tried to use Angular and it really doesn’t make much sense to me, and it pigeon-holes you, which isn’t a bad thing unless you try to use it in a way that it wasn’t designed to be used. If it’s faster for you to code with and it’s readable to you, go ahead and use it.

          0
  2. 5

    Michiel van Roon

    June 7, 2014 6:45 pm

    Woops, looks like you’re missing a colon in your link to the second article:
    http://http//coding.smashingmagazine.com/2013/04/02/thorough-introduction-backbone-marionette-part-2-modules/

    1
  3. 6

    Its work on online css or not we build up it to a web site.

    0
  4. 8

    I don’t use Marionette, I like to keep the project dependencies as minimal as possible. The way I handled Zombie events is by doing this…

    Backbone.View.prototype.close = function() {
    // remove view DOM element and it’s bound events
    this.remove();
    // unbind/destroy any events triggered by the view
    this.off();
    };

    0
    • 9

      That works since Backbone 1.0, but before that you needed more. And zombie views was just the first thing that Marionette brought to the table. There’s a whole lot more.

      0
  5. 10

    Great blog post series!

    Does Smashing Magazine offer a Marionette ebook that’s updated for Marionette V2.1.0?

    It seems like the ebook’s preview mentions that the “current version” is 1.3

    Thanks!

    – Ryo

    0
    • 11

      Sorry. Just as I was finishing writing this book, the Marionette contributors started really pushing Marionette forward and they’ve created several versions in a very short time.

      For the most part, everything in the book is still applicable. There are a few things you’ll want to look into after reading it though:
      1) They’ve made the names of some properties more normalized. E.g. sometimes a property is xxxType on one component whereas it’s xxxClass somewhere else and they’ve changed all those to be xxxType.
      2) They’ve added Behaviors. This is an amazing feature that could almost have an entire book dedicated to it. Look it up and learn it!
      3) They’ve added more ways to define and create Modules. Not necessarily something you need to learn, but these new ways are handy.

      Otherwise, you shouldn’t be too far behind, so the book will still be quite useful. Also, you can see all of the updates in the changelog: https://github.com/marionettejs/backbone.marionette/blob/master/changelog.md

      0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top