Nyxynyx
Nyxynyx

Reputation: 63599

Backbone.js: After doing Fetch(), render only the new models

I have a Collection App.listingList where subsequent fetch() are called with add:true.

App.listingList.fetch({
        data: {some:data},
        processData: true,
        add: true
});

Problem: How can the newly added models have their views rendered, without re-rendering the views of the existing models. This means I cannot do:

this.collection.each( function(listing, index) {
    new ListingMarkerView({ model:listing }).render();
}, this);

Attempt #1

Rendering the View on the collection's add event, I cannot figure out a way to access the new models to render

ListingListView = Backbone.View.extend({

    initialize: function() {
        this.collection.bind('add', this.renderNew, this);
    },

    render: function() {
        console.log('render ListingListView');
        this.collection.each( function(listing, index) {
            new ListingMarkerView({ model:listing }).render();
        }, this);
        return this;
    },

    renderNew: function() {
        // How do I grab the new models?
        new ListingMarkerView({ model:listing }).render(); // wont work
        return this;
    }
});

Attempt #2

I tried having a second Collection to do the subsequent fetch on, and compare the models of both collections using underscore.js's _.without(), but the array returned still contains the elements found in the 2nd array passed as the parameter. Using _difference() also returned the same array passed as the first array.

App.listingListNew.fetch({
        data: {some:data},
        processData: true,
        success: function() {
            console.log(App.listingListNew.models);
            console.log(App.listingList.models);
            console.log(_.without(App.listingListNew.models, App.listingList.models));
            console.log(_.difference(App.listingListNew.models, App.listingList.models));
        }
});

console.log Output

Since I passed in 2 identical arrays into _.difference() and _.without(), the output should be []. But it isnt :/ Maybe because cid is different, so every one of them are treated as unique?

enter image description here

Upvotes: 5

Views: 2879

Answers (2)

byron
byron

Reputation: 994

I know this is an old question, but I was having the same issue and came across this response so I thought I'd add an alternate approach. I'm not sure how efficient it is, but it works. I'm using this to support an infinite scroll feature.

I'm using Backbone 1.2.1, so in the collection fetch, I'm using remove:false instead of the deprecated add:true per the docs here: http://backbonejs.org/#Collection-fetch

The basic approach is to set a rendered attribute to true on each item in the collection when first rendered, then use that to ignore previously rendered items on subsequent fetches.

Model and Collection:

MyApp.Item = Backbone.Model.extend({});

MyApp.ItemList = Backbone.Collection.extend({
model: MyApp.Item,
    url: '/api/item/',
    parse : function(response){
        if (response.stat) {
            return _.map(response.content, function(model, id) {
                model.id = id;
                return model;
            });
        }
    }
});

Views:

MyApp.ItemListView = Backbone.View.extend({
    tagName: 'ul',
    className: 'item-list',
    render: function() {
        this.collection.each(function(item){
            //don't render items that have already been rendered!
            if (!item.rendered) {
                var itemListDetailView = new MyApp.ItemListDetailView({model: item});
                this.$el.append(itemListDetailView.render().el);
                item.rendered = true;
            }
        }, this)
        return this;
    }
});

MyApp.ItemListDetailView = Backbone.View.extend({
    tagName: 'li',
    className: 'item-list-detail',
    render: function() {
        $(this.el).html( '<div class="item-title">' + this.model.get('title') + '</div>');
        return this;
    }
});

Fetch function:

MyApp.loadMyItems = function () {
    MyApp.gettingData = true;  //flag for infinite scroll
    MyApp.myItems.fetch({
        traditional: true,
        remove:false,
        data: {
            u_id: MyApp.User.id,
            order: 'create_date:desc',
            start: MyApp.myItems.length,
            num_items: 10
        },
        success: function(){
           MyApp.gettingData = false; //flag for infinite scroll
           MyApp.myItemsView.render();
        }
    });
};

Calling:

//on initial page load
MyApp.myItems = new MyApp.ItemsCollection();
MyApp.myItemsView = new MyApp.ItemListView({
                            collection: MyApp.myItems, 
                            el: $('#my-items') 
                         });
MyApp.loadMyItems();

//infinite scroll
$('#items .infinite-scroll').on('loadmore', function () {
    if (!MyApp.gettingData) {
        MyApp.loadMyItems();
    } 
});

Upvotes: 0

jmk2142
jmk2142

Reputation: 8581

When you do a collection.bind('add', this.renderNew, this); it automatically passes the added model to your method as an argument.

Include the argument in your method and you should have access to the new model.

renderNew: function(newModel) {
    new ListingMarkerView({ model:newModel }).render();
    return this;
}

Upvotes: 5

Related Questions