Search code examples
javascriptjquerybackbone.jsunderscore.jsunderscore.js-templating

Backbone Sub-View for List


I am having a hard time understanding sub-views with Backbone and am trying to learn how to use them.

I want to create a simple unordered list using a main view for the container, and a sub-view for each list item.

Here is a Fiddle to what I have currently: http://jsfiddle.net/LrZrJ/

Here is the HTML I want to see in the end:

<div class="js-container">
    <h3>Stat = ok</h3>
    <ul>
        <li>Hello world</li>
    </ul>
</div>

Here is my JavaScript:

var TheModel = Backbone.Model.extend({
    default: {
        photos: '',
        stat: ''   
    }
});

var TheCollection = Backbone.Collection.extend({
    model: TheModel,

    url: 'http://api.flickr.com/services/rest/?page=1&api_key=a2978e5ce30c337e3b639172d3e1a0d1&tags=candy&method=flickr.photos.search&per_page=3&format=json&jsoncallback=?'

});

// The main view
var List = Backbone.View.extend({
    el: '.js-container',

    initialize: function () {
        this.collection = new TheCollection();

        this.item = new ListItem();

        return this;
    },

    render: function () {
        var self = this;

        this.collection.fetch({
            dataType: 'jsonp',
            success: function (data) {
                var listTemplate = _.template( $('#listContainer').html(), {
                    collection: self.collection.toJSON()
                });

                self.$el.html(listTemplate);
                self.$el.html( self.item.render() );
            }
        });

        return this;
    }

});

var ListItem = Backbone.View.extend({
    render: function () {
        var itemTemplate = _.template( $('#item').html(), {} );

        return itemTemplate;
    }

});

var myList= new List();
myList.render();

Here's my HTML:

<script type="text/template" id="listContainer">
    <h3>Stat = <%- collection[0].stat %></h3>
    <ul>

    </ul>
</script>

<script type="text/template" id="item">
    <li>Hello world</li>
</script>

<div class="js-container">
</div>

Solution

  • You're almost there. You have two problems right here:

    self.$el.html(listTemplate);
    self.$el.html( self.item.render() );
    

    First of all, jQuery's html function replaces everything:

    When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content.

    That means that your second .html call:

    self.$el.html( self.item.render() );
    

    overwrites everything from the first one. You probably want to use append instead, that will just add the <li> to whatever you append to. You'll also want to append to the <ul>, not the self.$el; Backbone includes a handy this.$ function in your view's for searching within the view's el:

    $ (jQuery) view.$(selector)

    [...] It's equivalent to running: view.$el.find(selector)

    So to find the <ul> and put the <li> inside it, you can say this:

    self.$('ul').append(self.item.render())
    

    While I'm here, it is common for a view's render to return this and then the caller would something.append(v.render().el) to get the view onto the page. You could use setElement to replace the sub-view's el with your <li> from the template:

    var ListItem = Backbone.View.extend({
        render: function () {
            var itemTemplate = _.template( $('#item').html(), {} );
            this.setElement(itemTemplate);
            return this;
        }
    });
    

    and then self.$('ul').append(self.item.render().el) in the parent.

    Demo: http://jsfiddle.net/ambiguous/JHV9w/1/

    An alternative would be to add tagName: 'li' to ListItem so that Backbone would create an <li> as that view's el and then remove the <li>...</li> from the template. That would give you a ListItem like this:

    var ListItem = Backbone.View.extend({
        tagName: 'li',
        render: function () {
            var itemTemplate = _.template( $('#item').html(), {} );
            this.$el.html(itemTemplate);
            return this;
        }
    });
    

    and an #item template like this:

    <script type="text/template" id="item">
        Hello world
    </script>
    

    This structure would (IMO) be more standard than what you're currently working with (but Backbone isn't very opinionated so do what works for you).

    Demo: http://jsfiddle.net/ambiguous/DVFVT/