Skip to content

Instantly share code, notes, and snippets.

@emileber
Last active September 22, 2016 20:31
Show Gist options
  • Save emileber/7c3b472fa45d8a208d9a8317bb2f38ae to your computer and use it in GitHub Desktop.
Save emileber/7c3b472fa45d8a208d9a8317bb2f38ae to your computer and use it in GitHub Desktop.
Backbone infinite scroll collection inspired by Backbone.paginator
var InfiniteCollection = Backbone.Collection.extend({
state: {
pageSize: 5, // default value for a page
},
queryParams: {
pageSize: "limit",
pagesLeft: null,
total: null,
sortKey: null,
order: null,
skip: function() {
return this.length;
}
},
/**
* Default to client side sorting.
*/
constructor: function(models, options) {
this.fullCollection = this; // FIXME
_.defaults(this.queryParams, InfiniteCollection.prototype.queryParams);
_.defaults(this.state, InfiniteCollection.prototype.state);
_.extend(this.state, { total: 0 });
return InfiniteCollection.__super__.constructor.call(this, models, options);
},
getFirstPage: function(opt) {
opt = _.extend({ fetch: true, reset: true }, opt);
if (opt.reset) this.reset(null, _.extend({ silent: true }, opt));
return this.fetch(opt);
},
getNextPage: function(opt) {
return this.fetch(_.extend({
add: true,
remove: false,
merge: true
}, opt));
},
parse: function(resp) {
_.extend(this.state, this.parseState(resp));
return resp.collection;
},
parseState: function(resp) {
var left = resp.total - resp.collection.length - this.length;
return {
total: resp.total,
pagesLeft: Math.ceil(left / this.state.pageSize)
};
},
hasLeft: function() {
return this.state.total - this.length > 0;
},
getTotal: function() {
return this.hasLeft() ? this.state.total : this.length;
},
/**
* Map the query params to the data object used by the underlying ajax lib
* to construct the query string.
* @param {Object} data to extend
* @return {Object} of all the data to be inserted as a querystring.
*/
getQueryStringData: function(data) {
data = data || {};
var state = this.state;
_.each(this.queryParams, function(param, queryParamKey) {
if (_.isString(param)) {
var key = queryParamKey;
queryParamKey = param;
param = state[key];
} else if (_.isFunction(param)) param = param.call(this);
if (param != null && _.isUndefined(data[queryParamKey])) {
data[queryParamKey] = param;
}
}, this);
return data;
},
fetch: function(options) {
options = options || {};
var data = options.data || {},
url = options.url || this.url || "",
state = this.state;
if (_.isFunction(url)) url = url.call(this);
// dedup query params
var queryStringIndex = url.indexOf('?');
if (queryStringIndex !== -1) {
_.extend(data, queryStringToParams(url.slice(queryStringIndex + 1)));
url = url.slice(0, queryStringIndex);
}
options.url = url;
// map the query params to the data object used by the underlying ajax lib
// to construct the query string
options.data = this.getQueryStringData(data);
return InfiniteCollection.__super__.fetch.call(this, options);
},
});
/**
* https://github.com/backbone-paginator/backbone.paginator/blob/93294f4865055245e5207008c6bd35be661c5c69/lib/backbone.paginator.js#L79
*/
function queryStringToParams(qs) {
var keyValue, key, value, ls, params = {},
decode = decodeURIComponent;
var keyValueArray = qs.split('&');
for (var i = 0, length = keyValueArray.length; i < length; i++) {
var param = keyValueArray[i];
keyValue = param.split('=');
value = keyValue[1];
if (value == null) value = true;
key = decode(keyValue[0]);
value = decode(value);
ls = params[key];
if (_.isArray(ls)) ls.push(value);
else if (ls) params[key] = [ls, value];
else params[key] = value;
}
return params;
}
var collection = new Notes();
// react to new chunks of model being fetched from the server.
this.listenTo(collection, {
"update": onUpdate
});
function onUpdate(collection, options) {
var added = options.changes.added;
if (added && added.length > 0) {
// render `added` model array
}
}
// automatically fetch with url: api/notes/?limit=5&skip=0
collection.getFirstPage();
// automatically fetch with url: api/notes/?limit=5&skip=5
collection.getNextPage({
context: this,
success: function() { /* same as other backbone function */ },
}).always(callback);
function callback(){
// do something after the new models were fetched.
}
//
// Example of how to use the infinite collection.
//
var Notes = InfiniteCollection.extend({
model: Backbone.Model,
state: {
pageSize: 5,
sortKey: 'date',
order: 1,
},
// sort by date as UTC string (reverse chronological order)
comparator: function(a, b) {
return -a.get('date').localeCompare(b.get('date'));
},
url: "api/notes",
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment