Search code examples
javascriptbackbone.jshandlebars.js

Nesting Backbone views with templates


I'm trying to nest backbone views that use handlebar templates.

http://jsfiddle.net/6j4yodz6/6/

My problem is that I don't know how to utilize the templates so both the outer and inner view are using templates. Also, right now it displays the html of the template:

< li > Title1 - Content #1< /li>
< li > Title2 - Content #2< /li>
< li > Title3 - Content #3< /li> 

HTML:

<script type="text/html" id="ul-template">
    < ul class = "outer" > < /ul>
</script>

<script type="text/html" id="li-template">
    < li > {{attributes.title}} - {{attributes.content}}< /li>
</script>

JAVASCRIPT:

var documents = [
new Backbone.Model({
    title: 'Title1',
    content: 'Content #1'
}),
new Backbone.Model({
    title: 'Title2',
    content: 'Content #2'
}),
new Backbone.Model({
    title: 'Title3',
    content: 'Content #3'
})];

var ContentsView = Backbone.View.extend({
    tagName: 'ul',
    render: function () {
        _(this.collection).each(function (document) {
            //How do you use ul-template?
            this.$el.append(new DocumentListView({
                model: document
            }).render().el);
        }, this);
        return this;
    }
});

var DocumentListView = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click': function () {
            console.log("clicked");
        }
    },
    render: function () {
        var source = $("#li-template").html(); 
        var template = Handlebars.compile(source);
        //This is using the template but is displaying the html.
        this.$el.html(template(this.model));
        return this;
    }
});


$('body').html(new ContentsView({
    collection: documents
}).render().el);

Solution

  • Here's a hopefully useful rewrite for your example application. Note that you don't need that template if that's just the container tag, using tagName will set the tag to be used by that view. If that parent view actually need a more complicated view, I would just render a child view and append it to an element in the parent view template.

    var documents_data = [{
        title: 'Title1',
        content: 'Content #1'
    },{
        title: 'Title2',
        content: 'Content #2'
    },{
        title: 'Title3',
        content: 'Content #3'
    }];
    
    var documents = new Backbone.Collection(documents_data);
    
    var ContentsView = Backbone.View.extend({
      tagName: 'ul',
      className: 'outer',
      render: function () {
        this.collection.each(this.addOne, this);
        return this;
      },
      addOne: function (document) {
        var documentListView = new DocumentListView({
          model: document
        });
        documentListView.render().$el.appendTo( this.el );
      }
    });
    
    var DocumentListView = Backbone.View.extend({
      tagName: 'li',
      template: Handlebars.compile($("#li-template").html()),
      events: {
        'click': 'onClick'
      },
      render: function () {
        this.$el.html(this.template(this.model));
        return this;
      },
      onClick: function(){
        $(document.body).append("<p>clicked: "+ this.model.get('title') +'</p>' );
      }
    });
    
    var contentsView = new ContentsView({
        collection: documents
    });
    contentsView.render().$el.appendTo( document.body );
    <script src='http://code.jquery.com/jquery.js'></script>
    <script src='http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js'></script>
    <script src='http://backbonejs.org/backbone.js'></script>
    <script src='http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.js'></script>
    
    <script type="text/template" id="li-template">
      {{attributes.title}} - {{attributes.content}}
    </script>