BackboneJS: View renders fine, but refreshes with undefined collection

Asked By: Anonymous

I am messing around with Backbone some weeks now and made some simple applications based on tutorials. Now I started from scratch again and tried to use the nice features Backbone offers as I am supposed to.

My view gets in the way though. When the page loads, it renders fine and creates its nested views by iterating the collection.
When I call render() again to refresh the whole list of just a single entry, all of the views attributes seem to be undefined.

The model of a single entry:

Entry = Backbone.Model.extend({
});

A list of entries: (json.html is placeholder for dataside)

EntryCollection = Backbone.Collection.extend({
    model: Entry, 
    url: 'json.html'
});

var entries = new EntryCollection();

View for a single entry, which fills the Underscore template and should re-render itself, when the model changes.

EntryView = Backbone.View.extend({
    template: _.template($('#entry-template').html()), 
    initialize: function(){
        this.model.on('change', this.render);
    },
    render: function(){
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

View for the whole list of entries which renders a EntryView for each item in the collection and should re-render itself, if a new item is added. The button is there for testing purposes.

EntryListView = Backbone.View.extend({
    tagName: 'div',
    collection: entries, 
    events: {
        'click button': 'addEntry'
    },
    initialize: function(){
        this.collection.on('add',this.render);
    },
    render: function(){
        this.$el.append('<button>New</button>');  //to test what happens when a new item is added
        var els = [];
        this.collection.each(function(item){
            els.push(new EntryView({model:item}).render().el);
        });
        this.$el.append(els);
        $('#entries').html(this.el);
        return this;
    },
    addEntry: function(){
        entries.add(new Entry({
            title: "New entry", 
            text: "This entry was inserted after the view was rendered"
        }));
    }
});

Now, if I fetch the collection from the server, the views render fine:

entries.fetch({
    success: function(model,response){
        new EntryListView().render();
    }
});

As soon as I click the button to add an item to the collection, the event handler on EntryListView catches the ‘add’ event and calls render(). But if I set a breakpoint in the render function, I can see that all attributes seem to be “undefined”. There’s no el, there’s no collection

Where am I going wrong?
Thanks for your assistance,

Robert


Solution

Answered By: Anonymous

As is, EntryListView.render is not bound to a specific context, which means that the scope (this) is set by the caller : when you click on your button, this is set to your collection.

You have multiple options to solve your problem:

  1. specify the correct context as third argument when applying on
    initialize: function(){
        this.collection.on('add', this.render, this);
    },
    
  2. bind your render function to your view with _.bindAll:
    initialize: function(){
        _.bindAll(this, 'render');
        this.collection.on('add', this.render);
    },
    
  3. use listenTo to give your function the correct context when called
    initialize: function(){
        this.listenTo(this.collection, 'add', this.render);
    },
    

You usually would do 2 or/and 3, _.bindAll giving you a guaranteed context, listenTo having added benefits when you destroy your views

initialize: function(){
    _.bindAll(this, 'render');
    this.listenTo(this.collection, 'add', this.render);
},

And if I may:

  • don’t create your main view in a fetch callback, keep it referenced somewhere so you can manipulate it at a later time
  • don’t declare collections/models on the prototype of your views, pass them as arguments
  • don’t hardwire your DOM elements in your views, pass them as arguments

Something like

var EntryListView = Backbone.View.extend({
    events: {
        'click button': 'addEntry'
    },
    initialize: function(){
        _.bindAll(this, 'render');
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
    },
    render: function(){
        var els = [];
        this.collection.each(function(item){
            els.push(new EntryView({model:item}).render().el);
        });

        this.$el.empty();
        this.$el.append(els);
        this.$el.append('<button>New</button>');
        return this;
    },
    addEntry: function(){
        entries.add(new Entry({
            title: "New entry", 
            text: "This entry was inserted after the view was rendered"
        }));
    }
});

var view = new EntryListView({
    collection: entries,
    el: '#entries'
});
view.render();

entries.fetch({reset: true});

And a demo http://jsbin.com/opodib/1/edit

techinplanet staff

techinplanet staff


Windows 10 Kaufen Windows 10 Pro Office 2019 Kaufen Office 365 Lizenz Windows 10 Home Lizenz Office 2019 Home Business Kaufen windows office 365 satın al follower kaufen instagram follower kaufen porno