user802232
user802232

Reputation: 2611

Backbone.js: nesting views through templating

Is it technically possible to nest views, using templating, something like that:

<%= new PhotoCollectionView({model:new PhotoCollection(model.similarPhotos)}).render().el) %>

I can put all the stuff in the render method as well, but templating gives much more room for flexibility and layout.

I tried the aforementioned variant, but all I get as a result on the screen is [HTMLDivElement].

If I try to extract just the HTML out ouf it, using jQuery's HTML, I get it rendered, but it turns out that the DOM nodes that get printed out are different from the ones that the views hold a reference to, because no interaction whatsoever with those DOM nodes is possible using the view instance. For instance if within the view I say $(this.el).hide(), nothing will happen.

What is the proper way, if any?

Upvotes: 18

Views: 15370

Answers (5)

Alessandro Giannone
Alessandro Giannone

Reputation: 885

The accepted answer has a major flaw, which is the fact that the ChildView is going to be re-initialised everytime it's rendered. This means you will lose state and potentially have to re-initialised complicated views on each render.

I wrote a blog about this here: http://codehustler.org/blog/rendering-nested-views-backbone-js/

To summarise though, I would suggest using something like this:

var BaseView = Backbone.View.extend({

    // Other code here...

    renderNested: function( view, selector ) {
        var $element = ( selector instanceof $ ) ? selector : this.$el.find( selector );
        view.setElement( $element ).render();
    }
});

var CustomView = BaseView.extend({

    // Other code here...

    render: function() {
        this.$el.html( this.template() );
        this.renderNested( this.nestedView, ".selector" );
        return this;
    }
});

You do not need to extend the Backbone view if you don't want to, the renderNested method could be put anywhere you like.

With the code above, you can now initialise the ChildView in the initialisation method and then simply render it when render() is called.

Upvotes: 14

Brave Dave
Brave Dave

Reputation: 1300

Check out the Backbone.Subviews mixin. It is a minimalist mixin built for managing nested views and does not re-initialize the child views every time the parent is rendered.

Upvotes: 2

Andrey Petrov
Andrey Petrov

Reputation: 2391

The decision to initialize a new object every time you render seems to me very inefficient. Particularly this:

render: function() {
    var child_view = new ChildView({ el: this.$('#child-el') }); //This refers to ParentView. 
    return this;
  }

Ideally the Rendering of the parent should be something like

render: function() {
   this.$el.html(this.template());
   this.childView1.render();
   this.childView2.render();
}

And the children creation should happen only when initializing the parent:

initialize: function() {
       this.childView1 = new ChildView1(selector1);
       this.childView2 = new ChildView2(selector2);
} 

the problem is that we do not have selector1 and selector2 before rendering the parent template. This is where I am stuck at right now :)

Upvotes: -1

Skylar Anderson
Skylar Anderson

Reputation: 5703

I typically render the parent view first. I then use the this.$('selector') method to find a child element that I can use as the el of the child view.

Here is a full example:

var ChildView = Backbone.View.extend({
  //..
})

var ParentView = Backbone.View.extend({
  template: _.template($('#parent-template').html()),
  initialize: function() {
    _.bindAll(this, 'render');
  }
  render: function() {
    var child_view = new ChildView({ el: this.$('#child-el') }); //This refers to ParentView. 
    return this;
  }
});

var v = new ParentView();
v.render(); 

Upvotes: 29

Bryce Fischer
Bryce Fischer

Reputation: 5442

I don't know about within a template itself, but I've done it with tables and lists before. In the outer template, just have the stub:

<script type="text/template" id="table-template">
    <table>
        <thead>
            <th>Column 1</th>
        </thead>
        <tbody>
        </tbody>
    </table>
</script>

and for the individual items: <%= field1 %>

then in your render method, just render the individual items and append them to the tbody element...

Upvotes: 1

Related Questions