Skip to content

Instantly share code, notes, and snippets.

@cryo-warden
Created March 1, 2016 10:15
Show Gist options
  • Save cryo-warden/738343529502a5a11d8b to your computer and use it in GitHub Desktop.
Save cryo-warden/738343529502a5a11d8b to your computer and use it in GitHub Desktop.
Adds two-way array-binding methods to Knockout v3.0.0beta. Updates may be needed to use this with more modern versions of Knockout.
var log = function () {
if (false && window.DEVMODE) {
return console.log.apply(console, arguments);
}
};
var mapArrayChanges =
ko.utils.mapArrayChanges = function (sourceObs, targetObs, fn, handlerState) {
return sourceObs.subscribe(function (events) {
if (handlerState.busy) { return; }
try {
handlerState.busy = true;
var targetValue = targetObs.peek();
if (!_.isArray(targetValue)) {
targetValue = [];
}
log('map: ', events, String(fn));
var offsetIndex = 0;
ko.utils.arrayForEach(events, function (event) {
if (event.status === 'added') {
targetValue.splice(event.index, 0, fn(event.value));
offsetIndex += 1;
} else if (event.status === 'deleted') {
targetValue.splice(event.index + offsetIndex, 1);
offsetIndex -= 1;
} else {
log(event);
}
});
if (targetValue === targetObs.peek()) {
targetObs.notifySubscribers(targetValue);
} else {
targetObs(targetValue);
}
} finally {
handlerState.busy = false;
}
}, null, 'arrayChange');
};
ko.computed.fn.map =
ko.observable.fn.map =
ko.observableArray.fn.map = function (mapFn) {
var result = ko.observableArray(ko.utils.arrayMap(this.peek(), mapFn));
var handlerState = { busy: false };
// create one-way binding
var disposable = mapArrayChanges(this, result, mapFn, handlerState);
result.dispose = function () {
disposable.dispose();
};
return result;
};
ko.computed.fn.mapBind =
ko.observable.fn.mapBind =
ko.observableArray.fn.mapBind = function (mapFn, inverseMapFn) {
var result = ko.observableArray(ko.utils.arrayMap(this.peek(), mapFn));
var handlerState = { busy: false };
// create two-way binding
var disposable = mapArrayChanges(this, result, mapFn, handlerState);
var disposableInverse = mapArrayChanges(result, this, inverseMapFn, handlerState);
result.dispose = function () {
disposable.dispose();
disposableInverse.dispose();
};
return result;
};
var filteredIndex = function (includedFlags, stopIndex) {
var resultIndex = 0;
for (var i = 0; i < stopIndex; i++) {
if (includedFlags[i]) {
resultIndex += 1;
}
}
return resultIndex;
};
ko.computed.fn.filter =
ko.observable.fn.filter =
ko.observableArray.fn.filter = function (fn) {
var result = ko.observableArray([]);
var includedFlags = [];
var array = this.peek();
for (var i = 0, il = array.length, included; i < il; i++) {
included = fn(array[i]);
includedFlags[i] = included;
if (included) {
result.push(array[i]);
}
}
var subscription = this.subscribe(function (events) {
log('filter: ', includedFlags, result.peek(), events, String(fn));
var offsetIndex = 0;
ko.utils.arrayForEach(events, function (event) {
var included;
if (event.status === 'added') {
included = fn(event.value);
includedFlags.splice(event.index, 0, included);
if (included) {
result.splice(filteredIndex(includedFlags, event.index), 0, event.value);
}
offsetIndex += 1;
} else if (event.status === 'deleted') {
included = includedFlags[event.index + offsetIndex];
includedFlags.splice(event.index + offsetIndex, 1);
if (included) {
result.splice(filteredIndex(includedFlags, event.index + offsetIndex), 1);
}
offsetIndex -= 1;
} else {
log(event);
}
});
}, null, 'arrayChange');
result.dispose = function () {
subscription.dispose();
};
return result;
};
var arrayFindIndex = function (array, fn, startIndex) {
for (var i = startIndex, il = array && array.length || 0; i < il; i++) {
if (fn(array[i])) {
return i;
}
}
return -1;
};
ko.computed.fn.find =
ko.observable.fn.find =
ko.observableArray.fn.find = function (fn) {
var array = this.peek();
var index = arrayFindIndex(array, fn, 0);
var result = ko.observable(index >= 0 ? array[index] : void 0);
var arrayObs = this;
var subscription = this.subscribe(function (events) {
var offsetIndex = 0;
ko.utils.arrayForEach(events, function (event) {
if (index < 0) {
if (event.status === 'added' && fn(event.value)) {
index = event.index;
result(event.value);
}
return;
}
if (event.index + offsetIndex > index) { return; }
if (event.status === 'added') {
if (fn(event.value)) {
index = event.index;
result(event.value);
} else {
index += 1;
}
offsetIndex += 1;
} else if (event.status === 'deleted') {
if (event.index + offsetIndex === index) {
var array = arrayObs.peek();
index = arrayFindIndex(array, fn, index);
result(index >= 0 ? array[index] : void 0);
} else {
index -= 1;
}
offsetIndex -= 1;
} else {
log(event);
}
});
}, null, 'arrayChange');
result.dispose = function () {
subscription.dispose();
};
return result;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment