Created
March 1, 2016 10:15
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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