darthal
darthal

Reputation: 543

The proper way of binding Backbone.js async fetch operation results to a view

I am wondering if there are any pointers on the best way of "fetching" and then binding a collection of data to a view within Backbone.js.

I'm populating my collection with the async fetch operation and on success binding the results to a template to display on the page. As the async fetch operation executes off the main thread, I one loses reference to the backbone view object (SectionsView in this case). As this is the case I cannot reference the $el to append results. I am forced to create another DOM reference on the page to inject results. This works but I'm not happy with the fact that

I've lost reference to my view when async fetch is executed, is there a cleaner way of implementing this ? I feel that I'm missing something...Any pointers would be appreciated.

SectionItem = Backbone.Model.extend({ Title: '' });
SectionList = Backbone.Collection.extend({
    model: SectionItem,
    url: 'http://xxx/api/Sections',
    parse: function (response) {
        _(response).each(function (dataItem) {
            var section = new SectionItem();
            section.set('Title', dataItem);
            this.push(section);
        }, this);

        return this.models;
    }
});

//Views---
var SectionsView = Backbone.View.extend(
        {
            tagName : 'ul',
            initialize: function () {
                _.bindAll(this, 'fetchSuccess');
            },
            template: _.template($('#sections-template').html()),
            render: function () {
                var sections = new SectionList();
                sections.fetch({ success: function () { this.SectionsView.prototype.fetchSuccess(sections); } }); //<----NOT SURE IF THIS IS THE BEST WAY OF CALLING fetchSuccess?
                return this;
            },
            fetchSuccess: function (sections) {
                console.log('sections ' + JSON.stringify(sections));
                var data = this.template({
                    sections: sections.toJSON()
                });
                console.log(this.$el); //<-- this returns undefined ???
                $('#section-links').append(data); //<--reference independent DOM div element to append results
            }
        }
    );

Upvotes: 1

Views: 760

Answers (1)

Enders
Enders

Reputation: 708

darthal, why did you re-implement parse? It seems to do exactly what Backbone does by default (receive an array of models from the AJAX call and create the models + add them to the collection).

Now on to your question... you are supposed to use the reset event of the Collection to do the rendering. You also have an add and remove when single instances are added or deleted, but a fetch will reset the collection (remove all then add all) and will only trigger one event, reset, not many delete/add.

So in your initialize:

this.collection.on("reset", this.fetchSuccess, this);

If you are wondering where the this.collection is coming from, it's a param you need to give to your view when you create it, you can pass either a model or a collection or both and they will automatically be added to the object (the view)'s context. The value of this param should be an instance of SectionList.

You'll also have to update fetchSuccess to rely on this.collection instead of some parameters. Backbone collections provide the .each method if you need to iterate over all the models to do stuff like appending HTML to the DOM.

In most cases you don't need a fetchSuccess, you should just use your render: when the collection is ready (on 'reset'), render the DOM based on the collection.

So to summarize the most general pattern:

  1. The collection should be independent from the view: you give the collection as a param to the view creation, the collection shouldn't be created from a specific view.

  2. You bind the View to the collection's reset event (+add, remove if you need) to run a render()

    this.collection.on("reset", this.render, this);

  3. You do a fetch on the collection, anytime (probably when you init your app).

A typical code to start the app would look something like this:

var sections = new SectionList();
var sectionsView = new SectionsView({collection: sections});
sections.fetch();

Because you bound the reset event in the view's initialize, you don't need to worry about anything, the view's render() will run after the fetch.

Upvotes: 1

Related Questions