Search code examples
javascriptbackbone.jsbackbone-viewsbackbone-routingbackbone.js-collections

Backbone.js Where and When should I fetch my collection data?


I'm a bit puzzled by when and where to instantiate collections and fetch them in a backbone App.

I've seen it done a few ways now, and had a working in my very simple little CRUD contact manager app Im messing around with, but I'm sure there are some conventions and 'gotchas' that I'm not aware of.

These are the options I've looked at and either gotten to work, or kind of 'work' :) Option 4 seems to be the best conceptually going forward, but I'm screwing it up some how.

BAD IDEA : 1) Before I was really doing anything with the router, I instantiated a new collection and called fetch then initiation the app.view in a doc ready statement.

    // Create an Instance of the collection
    App.contacts = new App.Collections.Contacts;

    App.contacts.fetch({update: true, remove: false}).then(function() {
        new App.Views.App({ collection : App.contacts });
    });
  • this worked - but doesn't seem like its really the right way if I were to have multiple collections in an App, I think this idea fails.

BAD IDEA : 2) As I started using the Router, I thought to do the same as the above in the router init method, which also worked, but I think leaves me with the same issues.

BAD IDEA : 3) I tried fetching in the collections init method, which seemed to work though I read in numerous places this was a bad idea.

GOOD IDEA(?) : BUT I CANT MAKE IT WORK : 4) I've thought, it would make sense to get the data for a collection when I instantiate its View, this way If I have a contacts collection and a tasks collection each would only get pulled from the server when I instantiate its view in the router. This seems like the winning formula to me, but when I tried to put this in the View init or render methods my this.collection.on('add', this.addOne, this); gives me can't call .on on undefined. (NB : I tried putting this in the success function)

This is doing my head in, please help.

Cheers.

EDIT : Additional Code so we can diagnose the double load discussed below.

In my router file I'm using this utility obj :

var ViewManager = {
    currentView : null,
    showView : function(view) {
        if (this.currentView !== null && this.currentView.cid != view.cid) {
            this.currentView.remove();
        }
        this.currentView = view;
        return view;
    }
}

in my router : I'm calling this method on my route

list: function() {
    console.log('backbone loading list route');

    var AllContactsView = new App.Views.Contacts({ //init method runs now
        collection : App.contacts
    });

    ViewManager.showView(AllContactsView);

},

my collection

App.Collections.Contacts = Backbone.Collection.extend({
    model: App.Models.Contact,
    url: 'contacts',
});

in my view

App.Views.Contacts = Backbone.View.extend({
    tagName: 'tbody',

    initialize: function() {

            this.listenTo(this.collection, 'sync', this.render);
            this.listenTo(this.collection, 'add', this.addOne);

        this.collection.fetch({
            // update: true,
            // remove: false
        });

    },

    render: function() {
        // append the list view to the DOM
        $('#allcontacts').append(this.el);

        // render each of the single views
        this.collection.each( this.addOne, this);
        return this;
    },

    addOne: function(contact) {
        var contactView = new App.Views.Contact({ model: contact});
        this.$el.append(contactView.render().el);
    }

});

in my app.js

// Run App
jQuery(document).ready(function($) {

    // Create an Instance of the collection
    App.contacts = new App.Collections.Contacts;

    // Init BB Router
    new App.Router.Route;
    Backbone.history.start(); // Kick it all off.

});

Solution

  • So after a lot of googling, reading and testing I've landed up doing the following. I dont think this is optimal as we're forcing backbone to run its reset event, which seems to be the way its being done in most example apps, though I feel like this is ignoring the fact that the default behavior was changed, surely this was for a reason?

    Ultimately this works currently, but I would be super excited to see an alternative.

    /*Collection view
    - - - - - - -*/
    App.Views.Contacts = Backbone.View.extend({
        tagName: 'tbody',
        initialize: function() {
              this.listenTo(this.collection, 'add', this.addOne);
              this.listenTo(this.collection, 'reset', this.addAll);
              this.listenTo(this.collection, 'all', this.render);
              this.collection.fetch({reset:true});
        },
        render: function() {
            // append the list view to the DOM
            $('#allcontacts').append(this.el);
            return this;
        },
        addAll : function() {
            this.collection.each( this.addOne, this);
        },
        addOne: function(model) {
            var contactView = new App.Views.Contact({ model: model});
            this.$el.append(contactView.render().el);
        }
    });
    

    I have this utility, which I've added the call to render back into :

    var ViewManager = {
        currentView : null,
        showView : function(view) {
            if (this.currentView !== null && this.currentView.cid != view.cid) {
                this.currentView.remove();
            }
            this.currentView = view;
            return view.render();
        }
    }
    

    in my router I call this on the list route :

    list: function() {
        console.log('backbone loading list route');
    
        var AllContactsView = new App.Views.Contacts({ //init method runs now
            collection : App.contacts
        });
    
        ViewManager.showView(AllContactsView);
    
    },
    

    As I said I feel like there may be a minor issue or two with this but it currently works in my app, and I'll come back to it at a later stage again undoubtably.