1

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.

});
bigmadwolf
  • 3,419
  • 3
  • 30
  • 44

4 Answers4

1

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();
    },
});
Rida BENHAMMANE
  • 4,111
  • 1
  • 14
  • 25
  • cool yeah, thats fine and dandy, and I've kind of expressed that above totally cool with injecting the collection, but that doesn't clear up - the where and when of fetching, - my assumption is in the view, when its instantiated by the router, - thoughts ? – bigmadwolf Mar 05 '14 at 21:40
  • Nice okay, - that makes kind of, woud love your feedback on my idea below please. – bigmadwolf Mar 05 '14 at 21:51
  • i do like that more - nice one, if you remove the first half, I'll mark it correct as its only the second half that answers the question. – bigmadwolf Mar 05 '14 at 22:22
  • The Problem now is that firstly, I'm not getting a reset event, by changing it sync I get results, but its then loading the collection twice. – bigmadwolf Mar 06 '14 at 09:53
  • `sync` is triggered when the collection receive the response from the server and `reset` when it reset it's state, if you pass the `update: true` the `reset` event won't be triggered. So you're right. Are you calling `collection.fetch()` twice ? – Rida BENHAMMANE Mar 06 '14 at 10:05
  • no not calling fetch twice, will post the other parts of my code in the question above so you can see what I'm doing. – bigmadwolf Mar 06 '14 at 10:10
0

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.

bigmadwolf
  • 3,419
  • 3
  • 30
  • 44
0

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

});
bigmadwolf
  • 3,419
  • 3
  • 30
  • 44
  • Removing this is also a fail, as then on trying to re instantiate the view later on - after adding a new one or editing one, the view is not visibly rendered. – bigmadwolf Mar 06 '14 at 15:48
0

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.

bigmadwolf
  • 3,419
  • 3
  • 30
  • 44