50

I'm trying to understand how a portion of backbone.js works. I have to fetch a collection of models once the app begins. I need to wait until fetch is complete to render each view. I'm not 100% sure the best approach to take in this instance.

var AppRouter = Backbone.Router.extend({
    routes: {
        "": "home",
        "customer/:id": "customer" 
    },

    home: function () {
        console.log("Home");
    },

    customer: function (id) {
        if (this.custromers == null)
            this.init();

        var customer = this.customers.at(2); //This is undefined until fetch is complete. Log always says undefined.
        console.log(customer);
    },

    init: function () {
        console.log("init");
        this.customers = new CustomerCollection();
        this.customers.fetch({
            success: function () {
                console.log("success"); 
                // I need to be able to render view on success.
            }
        });
        console.log(this.customers);
    }
});    
Derick Bailey
  • 72,004
  • 22
  • 206
  • 219
Frankie
  • 2,235
  • 4
  • 21
  • 22
  • Do you know how many Objects you're getting in advance? Why do you need to wait until they're all down before you render. Can you not design the page to allow each object to render itself and attach it to the Dom? – tkone Feb 12 '12 at 17:03
  • When I try to run this.customers.at(2) and grab the second object it always comes back undefined unless I wait until success is fired on fetch. I'm assuming, it's something I'm doing wrong and not fully understanding. I'm pretty new to JScript. – Frankie Feb 12 '12 at 17:05
  • FYI. It's JavaScript not jscript. Jscript is the Microsoft bastard child varient. But yes fetch is non-blocking which means that if you call fetch, the next command will run regardless of the results of fetch. Look at my answer below. – tkone Feb 12 '12 at 17:08

6 Answers6

65

The method I use is the jQuery complete callback like this:

var self = this;
this.model.fetch().done(function(){
  self.render();
});

This was recommended in a Backbone bug report. Although the bug report recommends using complete, that callback method has since been deprecated in favor of done.

Rovanion
  • 4,382
  • 3
  • 29
  • 49
  • 3
    This is super easy and works perfectly. I highly recommend this answer. – tylerthemiler Jan 10 '13 at 22:41
  • Michael, you must be calling the render function elsewhere too. Perhaps after you instantiate your view? – SeanK Oct 31 '13 at 19:57
  • I dont think it is wise to render the view within itself. it's error prone. – BonifatiusK Dec 04 '14 at 09:14
  • @BonifatiusK Why? And whree do you suggest placing the render function instead? – Rovanion Dec 04 '14 at 23:42
  • well as I understand you will initialize your view and do a fetch of the model within the view, after which you will callback to tell the same view to start the render function. I keep this separated. there is probably another view where you tell this view to run this functionality. Why just don't fetch and render there? – BonifatiusK Dec 05 '14 at 10:03
  • 1
    @BonifatiusK You didn't answer why the method presented in the above answer would be more error prone than firing the render function from somewhere else. Of course every developer should use their own judgement, but this snippet keeps all code related to the view inside the view. Having code related to the view in another file just makes the code hard to untangle. But there are certainly situations in which you wouldn't want to render the view directly when the fetch is complete, but doing just that is what the question asked for - a method for delaying rendering until the fetch is complete. – Rovanion Dec 05 '14 at 13:33
  • @Rovanion I agree you want all code for a specific view at the view. At the same time, this can cause problems as well. So maybe it is not more error prone than the other. For this solution above: I personally found out that the .done() method does not always wait until the fetch has finished as correctly as I wish. Another way is trigger a listner in the parse of the model or collection. you can fetch model.fetch(); and then do a model.on('listener', function(){}); in this case I always have the information I need. – BonifatiusK Dec 10 '14 at 08:00
  • is anything passed back in the callback function?? – Alexander Mills Jun 24 '15 at 02:57
  • @AlexMills I don't think so no. – Rovanion Jun 24 '15 at 13:36
  • hmmm i must say, there has to be some error handling in the case that the fetch failed, I am sorry to say it but this syntax must be incomplete! – Alexander Mills Jun 24 '15 at 16:54
  • @AlexMills There is nothing wrong with the syntax. If you want to add error management there are a number of ways, either add the optional arguments for jqXHR.done() and inspect these or register a jqXHR.error() alongside the above jqXHR.done(). You can read the documentation here: http://api.jquery.com/jquery.ajax/ – Rovanion Jun 25 '15 at 08:15
24

You can also do this with jquery 1.5+

$.when(something1.fetch(), something2.fetch()...all your fetches).then(function() {
   initialize your views here
});
iririr
  • 261
  • 3
  • 6
10

You can send your own options.success to the collections fetch method which runs only when the fetch is complete


EDIT (super late!)

From the backbone source (starting line 624 in 0.9.1)

fetch: function(options) {
  options = options ? _.clone(options) : {};
  if (options.parse === undefined) options.parse = true;
  var collection = this;
  var success = options.success;
  options.success = function(resp, status, xhr) {
    collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
    if (success) success(collection, resp);
  };

Note the second to last line. If you've passed in a function in the options object as the success key it will call it after the collection has been parsed into models and added to the collection.

So, if you do:

this.collection.fetch({success: this.do_something});

(assuming the initialize method is binding this.do_something to this...), it will call that method AFTER the whole shebang, allowing you trigger actions to occur immediately following fetch/parse/attach

tkone
  • 22,092
  • 5
  • 54
  • 78
  • I'm on my phone waiting to get my car fixed :). Will do when I return home! FYI the backbone source code is very well commented and annotated. You might want to just look at fetch in the meantime – tkone Feb 12 '12 at 17:09
  • @tkone Still on your cellphone? :) – Scott Greenfield Mar 16 '12 at 19:16
  • @ScottGreenfield wow that one slipped my mind. There's what I was talking about though. – tkone Mar 16 '12 at 19:28
  • I didn't want my Models or Collections to have any references to the view in my backbone definitions JS file. This method allows me to avoid that. Thanks – IcedDante Nov 30 '14 at 02:41
2

Another useful way might be to bootstrap in the data directly on page load. This if from the
FAQ:

Loading Bootstrapped Models

When your app first loads, it's common to have a set of initial models that you know you're going to need, in order to render the page. Instead of firing an extra AJAX request to fetch them, a nicer pattern is to have their data already bootstrapped into the page. You can then use reset to populate your collections with the initial data. At DocumentCloud, in the ERB template for the workspace, we do something along these lines:

<script>
  var Accounts = new Backbone.Collection;
  Accounts.reset(<%= @accounts.to_json %>);
  var Projects = new Backbone.Collection;
  Projects.reset(<%= @projects.to_json(:collaborators => true) %>);
</script>
Martin
  • 755
  • 1
  • 6
  • 11
1

Another option is to add the following inside of your collections initialize method:

this.listenTo(this.collection, 'change add remove update', this.render);

This will fire off the render method whenever the fetch is complete and/or the collection is updated programmatically.

IcedDante
  • 6,145
  • 12
  • 57
  • 100
0

You Can Use on and Off Methods

if you want to add trigger method like suppose if you want on success you want to call render method so please follow below example.

    _this.companyList.on("reset", _this.render, _this);
    _this.companyList.fetchCompanyList({firstIndex: 1, maxResult: 10},     _this.options);

in Model js please use like

    fetchCompanyList: function(data, options) {
    UIUtils.showWait();
    var collection = this;
    var condition = "firstIndex=" + data.firstIndex + "&maxResult=" + data.maxResult;
    if (notBlank(options)) {
        if (notBlank(options.status)) {
            condition += "&status=" + options.status;
        }
    }
    $.ajax({
        url: "webservices/company/list?" + condition,
        type: 'GET',
        dataType: 'json',
        success: function(objModel, response) {
            UIUtils.hideWait();
            collection.reset(objModel);
            if (notBlank(options) && notBlank(options.triggerEvent)) {
                _this.trigger(options.triggerEvent, _this);
            } 
        }
    });
}