9 Comments

Rendering Nested Views in Backbone.js

Rendering a Backbone View is simple.  The view’s container element (affectionately known as its el) swaps out all of its html for new html, probably fetched from a template. This works great for any top-level view.

The problem is in the child view. The parent view has swapped out all of its html, including the child view’s container element. The child’s $el is now pointing at a stale element that no longer exists on the page. This means the child is no longer displaying data to the page or getting data from it. The web application is broken, and the programmer sits there frustrated.

Don’t Panic. There are several ways to handle this situation.

1. Don’t Re-Render the Parent View

The simplest is to design a parent view that doesn’t require re-rendering. It has static content and serves as a container of child views. The child view container elements are never re-rendered, so they never go stale. Easy enough to say, but I find this doesn’t cover a lot of situations I encounter.

2. Reassign the Child View’s Element

Another more practical approach is to reassign the child view’s container once the parent view renders. Your view can now re-render appropriately, but its events will still be broken, as they are still stale. This second piece can be fixed with a call to delegateEvents. This method of handling stale sub views is effective and gets right at the heart of the problem. However, I find it messy, hacky, and non intuitive — which leaves me desiring a better solution.

Example.ParentView = Backbone.View.extend({
 
	childView: new Example.ChildView(),
 
	render: function() {
		this.$el.html(this.template(this.model.toJSON()));
		this.childView.$el = this.$('#child-view-container');
		this.childView.render();
		this.childView.delegateEvents();
	}
});

3. Use a Marionette.js Region

My favorite way of handling this is with Marionette.js. Marionette addresses several of the pitfalls of Backbone — one of which is the issue of sub views.

Using the Marionette Region object, you can declare a region with an element inside the parent view. To render the child view into the region, invoke region.show and pass in the child view. A view’s regions intuitively understand and react any time the parent view is rendered. This means that when the parent view is rendered, the views that are displayed in its regions update themselves automatically. No reassigning elements or delegate events calls are required. Marionette Layout objects also have a way of defining regions in a hash, much like the backbone events hash. (Edit – in Marionette version 2, Layout was renamed to LayoutView)

Example.ParentView = Marionette.Layout.extend({
 
	childView: new Example.ChildView(),
 
	regions: {
		childViewRegion: "#child-view-container"
	},
 
	initialize: function() {
		this.render();
		this.childViewRegion.show(this.childView);
	}
 
});

Do you find yourself nesting views frequently? What strategies do you use?