Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle initializing and rendering subviews in Backbone.js?

People also ask

How does Backbone js work?

Backbone. js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

Why use Backbone js?

BackboneJS allows developing of applications and the frontend in a much easier way by using JavaScript functions. BackboneJS provides various building blocks such as models, views, events, routers and collections for assembling the client side web applications.

What is Backbone in programming?

Backbone. js is a model view controller (MVC) Web application framework that provides structure to JavaScript-heavy applications. This is done by supplying models with custom events and key-value binding, views using declarative event handling and collections with a rich application programming interface (API).


This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.

I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:

Page Breakdown

Say the HTML is, after being rendered, something like this:

<div id="parent">
    <div id="name">Person: Kevin Peel</div>
    <div id="info">
        First name: <span class="first_name">Kevin</span><br />
        Last name: <span class="last_name">Peel</span><br />
    </div>
    <div>Phone Numbers:</div>
    <div id="phone_numbers">
        <div>#1: 123-456-7890</div>
        <div>#2: 456-789-0123</div>
    </div>
</div>

Hopefully it's pretty obvious how the HTML matches up with the diagram.

The ParentView holds 2 child views, InfoView and PhoneListView as well as a few extra divs, one of which, #name, needs to be set at some point. PhoneListView holds child views of its own, an array of PhoneView entries.

So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent views and Child views.

The difference between them is simple, Parent views hold child views while Child views do not. So in my example, ParentView and PhoneListView are Parent views, while InfoView and the PhoneView entries are Child views.

Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want Parent views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change. Child views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.

In a little more detail, for Parent views I like my initialize functions to do a few things:

  1. Initialize my own view
  2. Render my own view
  3. Create and initialize any child views.
  4. Assign each child view an element within my view (e.g. the InfoView would be assigned #info).

Step 1 is pretty self explanatory.

Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child events will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actually render any child views here, I allow them to do that within their own initialization.

Steps 3 and 4 are actually handled at the same time as I pass el in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.

For rendering, I try to keep it pretty simple for Parent views. I want the render function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.

Sometimes this doesn't always work though. For instance in my example above, the #name element will need to be updated any time the name within the model changes. However, this block is part of the ParentView template and not handled by a dedicated Child view, so I work around that. I will create some sort of subRender function that only replaces the content of the #name element, and not have to trash the whole #parent element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a new Child view (similar to the InfoView) that would handle the #name block.

Now for Child views, the initialization is pretty similar to Parent views, just without the creation of any further Child views. So:

  1. Initialize my view
  2. Setup binds listening for any changes to the model I care about
  3. Render my view

Child view rendering is also very simple, just render and set the content of my el. Again, no messing with delegation or anything like that.

Here is some example code of what my ParentView may look like:

var ParentView = Backbone.View.extend({
    el: "#parent",
    initialize: function() {
        // Step 1, (init) I want to know anytime the name changes
        this.model.bind("change:first_name", this.subRender, this);
        this.model.bind("change:last_name", this.subRender, this);

        // Step 2, render my own view
        this.render();

        // Step 3/4, create the children and assign elements
        this.infoView = new InfoView({el: "#info", model: this.model});
        this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
    },
    render: function() {
        // Render my template
        this.$el.html(this.template());

        // Render the name
        this.subRender();
    },
    subRender: function() {
        // Set our name block and only our name block
        $("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
    }
});

You can see my implementation of subRender here. By having changes bound to subRender instead of render, I don't have to worry about blasting away and rebuilding the whole block.

Here's example code for the InfoView block:

var InfoView = Backbone.View.extend({
    initialize: function() {
        // I want to re-render on changes
        this.model.bind("change", this.render, this);

        // Render
        this.render();
    },
    render: function() {
        // Just render my template
        this.$el.html(this.template());
    }
});

The binds are the important part here. By binding to my model, I never have to worry about manually calling render myself. If the model changes, this block will re-render itself without affecting any other views.

The PhoneListView will be similar to the ParentView, you'll just need a little more logic in both your initialization and render functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.

The PhoneView will be almost identical to the InfoView, only listening to the model changes it cares about.

Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.


I'm not sure if this directly answers your question, but I think it's relevant:

http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

The context in which I set up this article is different, of course, but I think the two solutions I offer, along with the pros and cons of each, should get you moving in the right direction.


To me it does not seem like the worst idea in the world to differentiate between the intital setup and subsequent setups of your views via some sort of flag. To make this clean and easy the flag should be added to your very own View which should extend the Backbone (Base) View.

Same as Derick I am not completely sure if this directly answers your question but I think it might be at least worth mentioning in this context.

Also see: Use of an Eventbus in Backbone


Kevin Peel gives a great answer - here's my tl;dr version:

initialize : function () {

    //parent init stuff

    this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!

    this.child = new Child();
},

I'm trying to avoid coupling between views like these. There are two ways I usually do:

Use a router

Basically, you let your router function initialize parent and child view. So the view has no knowledge of each other, but the router handles it all.

Passing the same el to both views

this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});

Both have knowledge of the same DOM, and you can order them anyway you want.


What I do is giving each children an identity (which Backbone has already done that for you: cid)

When Container does the rendering, using the 'cid' and 'tagName' generate a placeholder for every child, so in template the children has no idea about where it will be put by the Container.

<tagName id='cid'></tagName>

than you can using

Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element 
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();

no specified placeholder is needed, and Container only generate the placeholder rather than the children's DOM structure. Cotainer and Children are still generating own DOM elements and only once.


Here is a light weight mixin for creating and rendering subviews, which I think addresses all the issues in this thread:

https://github.com/rotundasoftware/backbone.subviews

The approach taken by this plug is create and render subviews after the first time the parent view is rendered. Then, on subsequent renders of the parent view, $.detach the subview elements, re-render the parent, then insert the subview elements in the appropriate places and re-render them. This way subviews objects are reused on subsequent renders, and there is no need to re-delegate events.

Note that the case of a collection view (where each model in the collection is represented with one subview) is quite different and merits its own discussion / solution I think. Best general solution I am aware of to that case is the CollectionView in Marionette.

EDIT: For the collection view case, you may also want to check out this more UI focused implementation, if you need selection of models based on clicks and / or dragging and dropping for reordering.