Skip to content

Instantly share code, notes, and snippets.

@aaronmccall
Last active June 4, 2016 03:07
Show Gist options
  • Save aaronmccall/ad3a1e7f9f572951b3a1 to your computer and use it in GitHub Desktop.
Save aaronmccall/ad3a1e7f9f572951b3a1 to your computer and use it in GitHub Desktop.
Example of oboe.js + pagination in &-collection

This was implemented for a real world application that was pulling in a large amount of data from multiple databases (SQL and MongoDB).

In order to improved TTFP (time to first page of results) and thus user-perceived performance, the API was converted to stream the results objects as it completed them.

Oboe.js allowed us to take advantage of that streaming API by parsing the incoming JSON stream on the browser-side.

var _ = require('underscore');
var Collection = require('ampersand-rest-collection');
// Streaming JSON parser
var oboe = require('oboe');
var qs = require('qs');
// requestAnimationFrame wrapper built with async iteration in mind
// helps reduce UI thread congestion when initializing large amounts
// of client-side models
var raf = require('rifraf');
module.exports = Collection.extend({
initialize: function (models, opts) {
var options = opts || {};
this.page = options.page;
this.pageSize = options.pageSize;
},
sync: function (method, self, opts) {
if (method === 'read') {
var options = opts || {};
var url = options.url || _.result(this, 'url');
if (options.data) {
url += (url.indexOf('?') === -1) ? '?' : '&';
url += qs.stringify(options.data);
}
_.extend(options, {url: url}, _.result(this, 'ajaxConfig'));
var pageSize = (this.pageSize || 50);
var readLength = (this.page || 1) * pageSize;
return oboe(options)
// The pattern below means emit array members as they are completed
.node('![*]', raf.delayed(function _handleModel(model) {
// Don't let the view know that we've gotten models until…
self.add(model, {silent: true});
// we reach the end of the requested page.
if (self.length >= readLength && (self.length % pageSize === 0)) {
raf.request(self.trigger.bind(self, 'reset', self, options));
}
}, 33))
// The delay amount was tweaked for best tradeoff between UI blocking / parsing performance
// across our user base, which included iPhones from 4 up and iPads gen 1 up in addition to
// desktop and laptop computers up to several years old.
.done(function _handleDone(resp) {
self.trigger('reset', self, options);
self.trigger('sync', self, resp, options);
});
} else {
return Collection.prototype.sync.apply(this, arguments);
}
}
});
var ItemView = require('./myItemView');
var OboePagedCollection = require('../collections/oboePagedCollection');
var View = require('ampersand-view');
module.exports = View.extend({
/* define your template, etc. */
initialize: function () {
this.collection = new OboePagedCollection(null, {page: this.page, pageSize: this.pageSize});
this.collection.fetch();
},
render: function () {
this.renderWithTemplate();
this.collectionView = this.renderCollection(
this.collection,
ItemView,
this.queryByHook('collectionContainer')
);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment