Source: Collection.js

'use strict';

var request = require('superagent');
var Promise = require('es6-promise').Promise;

function CollectionFactory(collectionOptions) {
  if (!collectionOptions.resource) {
    throw new Error('"resource" not specified. Construct the CollectionFactory as: new CollectionFactory({resource: MyResource})');
  }

  /**
   * Collection
   *
   * Collection of resources returned by {@link Resource#query}.
   *
   * @class
   */
  function Collection() {
    var collection = Object.create(Array.prototype);

    // Initialize the array.
    collection = Array.apply(collection, arguments) || collection;

    // Add all the class methods to the collection.
    for (var method in Collection.prototype) {
      if (Collection.prototype.hasOwnProperty(method)) {
        collection[method] = Collection.prototype[method];
      }
    }

    // Return the new collection object.
    return collection;
  }

  Collection.prototype = {
    /**
     * Fetch previous page of collection and prepend items to current instance of collection.
     *
     * @example
     * var collection = Resource.query();
     * collection.then(function() {
     *   if (collection.hasPrev()) {
     *     var promise = collection.prevPage();
     *     promise.then(function(data) {
     *       // previous page has been fetched
     *       // `data` contains the newly fetched resources
     *       // `collection` now contains all resources
     *     });
     *   }
     * });
     *
     * @example
     * // Don't attach `then` to the `collection` because that was already resolved earlier as a response to {@link #query}.
     * var promise = collection.prevPage();
     * collection.then(function() {
     *   // WRONG. Already resolved.
     * });
     * promise.then(function() {
     *   // CORRECT. Will be resolved when previous page is fetched.
     * });
     *
     * @return {Promise}    promise
     * @throws {RangeError} When previous page not available. You should check {@link #hasPrev} before calling `prevPage()`.
     */
    prevPage: function() {
      if (!this.prev_page) {
        throw new RangeError("No previous page.");
      }

      return new Promise(function(resolve, reject) {
        request
          .get(this.prev_page)
          .accept('json')
          .end(function(response) {
            if (response.error) {
              var error = (response.body || JSON.parse(response.text)).error || response.error;
              reject({error: error});
            } else {
              var body = response.body || JSON.parse(response.text);

              var resources = [];
              body.data.forEach(function(data) {
                var resource = new collectionOptions.resource(data);
                resources.push(resource);
              }.bind(this));
              this.unshift.apply(this, resources);

              var newCollection = new Collection();
              newCollection.push.apply(newCollection, this);

              if (body.pagination && body.pagination.previous_page) {
                this.prev_page = body.pagination.previous_page;
                newCollection.prev_page = body.pagination.previous_page;
              } else {
                delete this.prev_page;
              }
              newCollection.next_page = this.next_page;

              resolve(newCollection);
            }
        }.bind(this));
      }.bind(this));
    },
    /**
     * @alias prevPage
     */
    previousPage: function() {
      return this.prevPage.apply(this, arguments);
    },

    /**
     * Fetch next page of collection and append resources to current instance of collection.
     *
     * @example
     * var collection = Resource.query();
     * collection.then(function() {
     *   if (collection.hasNext()) {
     *     var promise = collection.nextPage();
     *     promise.then(function(data) {
     *       // next page has been fetched
     *       // `data` contains the newly fetched resources
     *       // `collection` now contains all resources
     *     });
     *   }
     * });
     *
     * @example
     * // Don't attach `then` to the `collection` because that was already resolved earlier as a response to {@link #query}.
     * var promise = collection.nextPage();
     * collection.then(function() {
     *   // WRONG. Already resolved.
     * });
     * promise.then(function() {
     *   // CORRECT. Will be resolved when next page is fetched.
     * });
     *
     * @return {Promise}    promise
     * @throws {RangeError} When next page not available. You should check {@link #hasNext} before calling `nextPage()`.
     */
    nextPage: function() {
      if (!this.next_page) {
        throw new RangeError("No next page.");
      }

      return new Promise(function(resolve, reject) {
        request
          .get(this.next_page)
          .accept('json')
          .end(function(response) {
            if (response.error) {
              var error = (response.body || JSON.parse(response.text)).error || response.error;
              reject({error: error});
            } else {
              var body = response.body || JSON.parse(response.text);

              var newCollection = new Collection();
              body.data.forEach(function(data) {
                var resource = new collectionOptions.resource(data);
                this.push(resource);
              }.bind(this));
              newCollection.push.apply(newCollection, this);

              if (body.pagination && body.pagination.next_page) {
                this.next_page = body.pagination.next_page;
                newCollection.next_page = body.pagination.next_page;
              } else {
                delete this.next_page;
              }
              newCollection.prev_page = this.prev_page;

              resolve(newCollection);
            }
        }.bind(this));
      }.bind(this));
    },

    /**
     * Check if there is previous page available.
     *
     * This method doesn't trigger any external request. The resolution is based on the result of the last query.
     *
     * @example
     * var collection = Resource.query();
     * if (collection.hasPrev()) {
     *   collection.prevPage();
     * }
     *
     * @return {Boolean} has previous page
     */
    hasPrev: function() {
      return !! this.prev_page;
    },
    /**
     * @alias hasPrev
     */
    hasPrevious: function() {
      return this.hasPrev.apply(this, arguments);
    },

    /**
     * Check if there is next page available.
     *
     * This method doesn't trigger any external request. The resolution is based on the result of the last query.
     *
     * @example
     * var collection = Resource.query();
     * if (collection.hasNext()) {
     *   collection.nextPage();
     * }
     *
     * @return {Boolean} has next page
     */
    hasNext: function() {
      return !! this.next_page;
    }
  };

  return Collection;
}

module.exports = CollectionFactory;