Question

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.

});
Was it helpful?

Solution 4

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.

OTHER TIPS

Your view should be like this :

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

    initialize: function() {

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

        vent.on('contactR:closeAll', this.closeView, this);

        this.collection.fetch();
    },
});

So Irealised my errors where because of that pesky little this keyword melarky, so now its a stylistic or conceptual question

Probably in my setup file before calling the router.

App.contacts = new App.Collections.Contacts;

in my router:

list: function() {
    var AllContactsView = new App.Views.Contacts({
        collection : App.contacts
    });

    ViewManager.showView(AllContactsView);

},

In the view :

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

    initialize: function() {
        var that = this;
        this.collection.fetch({ update: true, remove: false}).then(function() {
            that.collection.on('add', that.addOne, that);
            vent.on('contactR:closeAll', that.closeView, that);
            that.render();
        });


    },
});

This to me seems a lot more flexible and conceptually right to me, but I would love feedback about this answer.

So it seems having the .each in the render method was kind of pointless, as the add event fires after the fetch connects successfully and before the sync event is fired. So the listenTo for the add event fires the addOne when it needs to and the render event now just appends the collection to the dom on sync.

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();

},

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

    /* 
     render each of the single views - removing this seems to set everything 
     right in the world
   */

   // 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);
}

});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top