define(
[
'backbone',
'underscore',
'jquery'
], function(
Backbone,
_,
$
) {
'use strict';
/**
* Wrap a collection and repeat its contents infinitely.
* Will generate unique ids for the repeated models to avoid duplication.
*
* ```js
* var sourceCollection = new Backbone.Collection( ... );
* var repeatableCollection = new RepeatableCollection([], {
* collection: sourceCollection
* });
* ```
*/
var RepeatableCollection = Backbone.Collection.extend({
initialize: function(models, options) {
this.options = _.defaults({}, options, {per_page: 20});
this.source = this.options.collection;
this.repeating = false;
this.currentIndex = 0;
delete this.options.collection;
},
/**
* Fetch models. If no more models are available, the current page will be filled by replicas of the original collection's items.
*/
fetch: function(options) {
// fetch only one page at a time
if (this._fetching) {
return this._fetching;
}
options = _.extend(this.options, options);
var promise = $.Deferred();
var rC = this;
ensureItems(this, options).done(function() {
var models = duplicate(rC.source.models.slice(rC.currentIndex, rC.currentIndex + options.per_page));
var diff = rC.currentIndex + options.per_page - rC.source.length;
if (diff > 0) {
// end of source data
models = models.concat(duplicate(rC.source.models.slice(0, diff)));
rC.currentIndex = diff;
rC.repeating = true;
} else {
rC.currentIndex += options.per_page;
}
rC.add(models);
promise.resolve(models);
if (options.success) { options.success(rC, models, options); }
rC.trigger('sync', rC, models, options);
});
var fetching = this._fetching = promise;
this._fetching.done(function() {
delete rC._fetching;
});
return fetching;
}
});
return RepeatableCollection;
// helper functions
function ensureItems(rC, options, promise) {
if (!promise) { promise = new $.Deferred(); }
if (rC.repeating || rC.source.length >= rC.currentIndex + options.per_page) {
promise.resolve();
} else {
var length = rC.source.length;
rC.source.fetch({reset: false}).done(function() {
if (rC.source.length === length) {
rC.repeating = true;
}
ensureItems(rC, options, promise);
});
}
return promise;
}
function duplicate(models) {
return _.map(models, function(model) {
model = model.toJSON();
model.id = _.uniqueId(model.id + '_');
return model;
});
}
});