Skip to content

Instantly share code, notes, and snippets.

@fidel-karsto
Created July 29, 2016 13:48
Show Gist options
  • Save fidel-karsto/5e9a5f646234dd5eefab21988f1e7196 to your computer and use it in GitHub Desktop.
Save fidel-karsto/5e9a5f646234dd5eefab21988f1e7196 to your computer and use it in GitHub Desktop.
ObservableArray.getAsyncTwin() for Knockout
/**
* Adds a `getAsyncTwin()` method to the prototype of Knockout's ObservableArray
* returning a new ObservableArray that will be a continuous non-instant copy of the original.
* Whenever the original ObservableArray changes, the copy will be populated
* in chunks of a given size and with a given delay between the chunks until it is identical to the original.
*
* Intended to allow rendering the first (visible) part of huge lists faster before all the rest.
*
* Example:
*
* var hugeList = ko.observableArray(someHugeArray),
* hugeListToRender = hugeList.getAsyncTwin({
* chunkSize: 100,
* chunkDelay: 1
* });
*
* <ul data-bind="foreach: hugeListToRender">..</ul>
*/
define(['knockout', 'util', 'LOG'], function(ko, util, LOG) {
'use strict';
/**
* @param opts (Object)
* - chunkSize (number|function) number of items to copy per update interval OR a function to determine
* the number based on the new total size,
* e.g. function(total){ return (total <= 100) ? 20 : 10 }
* - chunkDelay (number|function) delay in millis between the chunks OR a function to determine the delay
* based on the new total size,
* e.g. function(total){ return (total <= 100) ? 20 : 10 }
*/
ko.observableArray.fn.getAsyncTwin = function(opts) {
util.assertObject(opts, 'Invalid options object for observableArray.getAsyncTwin');
if (typeof opts.chunkSize === 'number') {
util.assertNumberInRange(opts.chunkSize, 1, 1000000, 'Illegal chunkSize number for observableArray.getAsyncTwin');
} else {
util.assertFunction(opts.chunkSize, 'Invalid chunkSize option for observableArray.getAsyncTwin');
}
if (typeof opts.chunkDelay === 'number') {
util.assertNumberInRange(opts.chunkDelay, 1, 60000, 'Illegal chunkDelay number for observableArray.getAsyncTwin');
} else {
util.assertFunction(opts.chunkDelay, 'Invalid chunkDelay option for observableArray.getAsyncTwin');
}
var sourceObsArray = this,
twinObsArray = ko.observableArray(),
chunkSize = -1, // determined on update start
chunkDelay = -1, // determined on update start
timer = null,
continueUpdate = function(clearArray) {
if (timer) {
clearTimeout(timer);
timer = 0;
}
if (clearArray) {
twinObsArray().length = 0;
}
var sourceArray = sourceObsArray(),
twinArray = twinObsArray(),
firstItemIndex = twinArray.length,
lastItemIndex = Math.min(firstItemIndex + chunkSize - 1, sourceArray.length - 1);
if (firstItemIndex <= lastItemIndex) {
LOG.debug('Adding items %s to %s', firstItemIndex, lastItemIndex);
for (var i = firstItemIndex; i<= lastItemIndex; i++) {
twinArray[i] = sourceArray[i];
}
if (twinArray.length < sourceArray.length) {
LOG.debug('Scheduling next chunk in %s millis', chunkDelay);
timer = setTimeout(continueUpdate, chunkDelay);
} else {
LOG.debug('asyncTwin complete [total=%s]', twinArray.length);
}
} else {
LOG.debug('Empty chunk for asyncTwin#continueUpdate');
}
twinObsArray.valueHasMutated();
},
startUpdate = function() {
if (timer) {
clearTimeout(timer);
timer = 0;
}
var newSize = sourceObsArray().length;
chunkSize = (typeof opts.chunkSize === 'function') ? opts.chunkSize(newSize) : opts.chunkSize;
chunkDelay = (typeof opts.chunkDelay === 'function') ? opts.chunkDelay(newSize) : opts.chunkDelay;
LOG.debug('asyncTwin#startUpdate [total=%s|chunkSize=%s|chunkDelay=%s]', newSize, chunkSize, chunkDelay);
continueUpdate(true);
};
sourceObsArray.subscribe(startUpdate);
startUpdate();
return twinObsArray;
};
return ko;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment