1
\$\begingroup\$

This is a subclass of Backbone.Collection with a method fetchNextPage that returns a Q promise.

To find out the next page's URL, it looks in meta.next from server response.
The server will return null for the last page.

It also has a getState method that returns a state with empty and fetching boolean variables.
When this state changes, it fires change:state event.

Finally, promiseAt is a convenience method that keeps fetching pages until it can return model at given index, or null if it can't.

(function (Backbone, q) {

  'use strict';

  window.st.Collection.PaginatedCollection = Backbone.Collection.extend({
    initialize: function () {
      _.bindAll(this, 'setFetchPromise');
      Backbone.Collection.initialize.apply(this, arguments);

      this.refreshState();
    },

    fetchNextPage: function () {
      if (this.isLastPage()) {
        return q();
      }

      if (this.fetchPromise) {
        return this.fetchPromise;
      }

      var fetchOptions,
          fetchPromise;

      fetchOptions = {
        remove: false,
        url: this.getNextPageUrl()
      };

      fetchPromise = this.fetch(fetchOptions)
        .finally(this.setFetchPromise/*(undefined)*/);

      return this.setFetchPromise(fetchPromise);
    },

    getNextPageUrl: function () {
      return this.lastResponse ?
        this.lastResponse.meta.next :
        this.url;
    },

    getState: function () {
      return {
        empty: this.isEmpty(),
        fetching: this.isFetching()
      };
    },

    isFetching: function () {
      return !!this.fetchPromise;
    },

    isLastPage: function () {
      if (!this.url) {
        return true;
      }

      if (!this.lastResponse) {
        return false;
      }

      return !this.lastResponse.meta.next;
    },

    parse: function (response) {
      this.lastResponse = response;
      return response.objects;
    },

    refreshState: function () {
      var newState = this.getState();
      if (_.isEqual(newState, this.state)) {
        return;
      }

      this.state = newState;
      this.trigger('change:state', newState);
    },

    setFetchPromise: function (promise) {
      this.fetchPromise = promise;
      this.refreshState();

      return promise;
    },

    promiseAt: function (index) {
      var _this = this;

      if (index < this.length - 1 || this.isLastPage()) {
        return q(this.at(index));
      }

      return this.fetchNextPage().then(function () {
        return _this.promiseAt(index);
      });
    }
  });
})(Backbone, Q);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.