dave mankoff
dave mankoff

Reputation: 17779

Backbone.js: Differentiating Between a Collection That Is Still Loading and an Empty Collection?

Using Backbone.js, I have a collection that I instanctiate, X. After instantiating it, I immediately called X.fetch() such that the data can be loaded from the server as soon as possible. I then pass X into various views that will operate on the collection.

What is the best way for these views do differentiate between when the collection is simply loading (and thus empty) and when it is loaded but actually empty? I would like my views to show appropriate "loading..." text up until the collection has pinged the server. At which point, I would like them to say "It looks like there's nothing here. Perhaps you should add something."

I was thinking of maybe listening to the reset event on the collection in each respective view, but this seems rather fragile to me. What happens if the reset event has already fired before the view attaches its listener? Is there a good pattern to inspect the state of a collection to find out if it's been fetched?

Upvotes: 1

Views: 204

Answers (2)

user920041
user920041

Reputation:

Make sure that you initialize the views before you call fetch(). This way you can listen for the reset event, and be sure that all views are updated. If you want the xlist to be loaded as soon as possible it could be a good idea to bootstrap it to your html server side.

A way of doing that could be:

<script src="/public/js/collecions/x-list.js">
<script src="/public/js/views/x-view.js">

    <script>
         var xlist = new App.Collections.XList();

        _.each($(".xdiv"), function(el){
             new App.Views.XView({
                 el: el,
                 collection: xlist
             });
             // make sure that XView have this in it's initialize method:
             // this.collection.bind("reset", this.render, this)
        }

        xlist.fetch();

        // or even better, use bootstraped data:

        x.list.reset((<%= @data.to_json %>)
    </script>

Upvotes: 1

Eugene Naydenov
Eugene Naydenov

Reputation: 7295

fetch method is asynchronous based on asynchronous nature of AJAX. Therefore you need to implement an event-driven behavior.

X.fetch({
    success: function(collection, response) {
        hidePreLoader();
        renderViews(collection); // collection argument is your X collection.
    }
});

Update

Based on your comments to my answer I can propose another idea. Namely you can set

X.isLoaded = true;

or any other property like

X.loadedAt = Date.now();

in success callback, so other code can check for the state of this property.

But though I see a kind of bad design here. You can render your views with preloader showed and in success callback trigger some event on which your views will start to work with the collection since it's loaded and became ready to use. So in total I'm again propose you to use event-driven behavior.

I didn't test, but here is a representation of my idea:

var XCollection = Backbone.Collection.extend({

    // ...

    model: X,
    loadState: {},
    loadedAt: -1,
    initialize: function(options) {
        _.extend(this.loadState, Backbone.Events);
    },

    isLoaded: function() {
        return this.loadedAt > -1;
    }
});

var Subview = Backbone.View.extend({

    // ...

    initialize: function(options) {
        this.collection.loadState.on("loadComplete",
            this.onLoadComplete, this);
    },

    onLoadComplete: function(response) {
        this.hidePreloader();
        this.renderData();
    },

    /**
     * Checks is the collection loaded and view can render fetched models.
     * It's just an example.
     * You'll not need to use it if you're handling a loadComplete event.
     */
    isRenderingAllowed: function() {
        return this.collection.isLoaded();
    }
});

var XView = Subview.extend({

    // ...

    initialize: function(options) {
        Subview.prototype.initialize.apply(this, arguments);
    }
});

var YView = Subview.extend({

    // ...

    initialize: function(options) {
        Subview.prototype.initialize.apply(this, arguments);
    }
});


// ...

var x = new XCollection();

var xView = new XView({collection: x}),
    yView = new YView({collection: x});

x.fetch({
    success: function(collection, response) {
        collection.loadedAt = Date.now();
        collection.loadState.trigger("loadComplete", response);
    }
});

Documentation

Upvotes: 3

Related Questions