Skip to content

Instantly share code, notes, and snippets.

@gaearon
Created March 9, 2015 13:43
Show Gist options
  • Save gaearon/0b2200aa8128caf16377 to your computer and use it in GitHub Desktop.
Save gaearon/0b2200aa8128caf16377 to your computer and use it in GitHub Desktop.
PaginatedList with invalidation
'use strict';
var { union, find, without, clone } = require('underscore'),
invariant = require('react/lib/invariant');
class PaginatedList {
constructor(ids) {
this._ids = ids || [];
this._pageCount = 0;
this._nextPageUrl = null;
this._isExpectingPage = false;
this._isInvalidated = false;
}
getIds() {
return this._ids;
}
getPageCount() {
return this._pageCount;
}
isExpectingPage() {
return this._isExpectingPage;
}
getNextPageUrl() {
return this._nextPageUrl;
}
isLastPage() {
return this._nextPageUrl === null;
}
isInvalidated() {
return this._isInvalidated;
}
find(predicate) {
return find(this._ids, predicate);
}
prepend(id) {
this._ids = union([id], this._ids);
}
remove(id) {
this._ids = without(this._ids, id);
}
contains(id) {
return this._ids.indexOf(id) !== -1;
}
invalidate() {
// Empty is as good as invalidated
if (this._pageCount === 0) {
return;
}
// TODO: We'd like to support invalidating in-flight but in order to do that
// `receivePage` needs to be sure whether specific response is next or invalidated page
// and the whole chunk of consuming code will have to be refactored. Leave it for now.
if (this._isExpectingPage) {
console.warn('Cannot invalidate while fetching page. Invalidation ignored.');
return;
}
this._isInvalidated = true;
}
expectPage() {
invariant(!this._isExpectingPage, 'Cannot call expectPage twice without prior cancelPage or receivePage call.');
this._isExpectingPage = true;
}
cancelPage() {
invariant(this._isExpectingPage, 'Cannot call cancelPage without prior expectPage call.');
this._isExpectingPage = false;
this._isInvalidated = false;
}
receivePage(newIds, nextPageUrl) {
invariant(this._isExpectingPage, 'Cannot call receivePage without prior expectPage call.');
nextPageUrl = nextPageUrl || null;
if (this._isInvalidated) {
this.receiveInvalidatedFirstPage(newIds, nextPageUrl);
} else {
this.receiveNextPage(newIds, nextPageUrl);
}
this._isExpectingPage = false;
this._isInvalidated = false;
}
receiveNextPage(newIds, nextPageUrl) {
this._ids = union(this._ids, newIds);
this._pageCount++;
this._nextPageUrl = nextPageUrl;
}
receiveInvalidatedFirstPage(firstPageIds, nextPageUrl) {
var isCommonId = id => this._ids.indexOf(id) > -1,
firstPageIdsBackwards = clone(firstPageIds).reverse(),
lastCommonId = find(firstPageIdsBackwards, isCommonId),
lastCommonIdIndex = this._ids.indexOf(lastCommonId);
// Only attempt to merge if there is a common item
if (lastCommonIdIndex > -1) {
this._ids = union(firstPageIds, this._ids.slice(lastCommonIdIndex + 1));
} else {
this._ids = firstPageIds;
this._pageCount = 1;
this._nextPageUrl = nextPageUrl;
}
}
}
module.exports = PaginatedList;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment