@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
139 lines (116 loc) • 3.91 kB
JavaScript
import {calcSplices, projectArraySplices} from './array-change-records';
import {getChangeRecords} from './map-change-records';
import {subscriberCollection} from './subscriber-collection';
export class ModifyCollectionObserver {
constructor(taskQueue, collection) {
this.taskQueue = taskQueue;
this.queued = false;
this.changeRecords = null;
this.oldCollection = null;
this.collection = collection;
this.lengthPropertyName = collection instanceof Map || collection instanceof Set ? 'size' : 'length';
}
subscribe(context, callable) {
this.addSubscriber(context, callable);
}
unsubscribe(context, callable) {
this.removeSubscriber(context, callable);
}
addChangeRecord(changeRecord) {
if (!this.hasSubscribers() && !this.lengthObserver) {
return;
}
if (changeRecord.type === 'splice') {
let index = changeRecord.index;
let arrayLength = changeRecord.object.length;
if (index > arrayLength) {
index = arrayLength - changeRecord.addedCount;
} else if (index < 0) {
index = arrayLength + changeRecord.removed.length + index - changeRecord.addedCount;
}
if (index < 0) {
index = 0;
}
changeRecord.index = index;
}
if (this.changeRecords === null) {
this.changeRecords = [changeRecord];
} else {
this.changeRecords.push(changeRecord);
}
if (!this.queued) {
this.queued = true;
this.taskQueue.queueMicroTask(this);
}
}
flushChangeRecords() {
if ((this.changeRecords && this.changeRecords.length) || this.oldCollection) {
this.call();
}
}
reset(oldCollection) {
this.oldCollection = oldCollection;
if (this.hasSubscribers() && !this.queued) {
this.queued = true;
this.taskQueue.queueMicroTask(this);
}
}
getLengthObserver() {
return this.lengthObserver || (this.lengthObserver = new CollectionLengthObserver(this.collection));
}
call() {
let changeRecords = this.changeRecords;
let oldCollection = this.oldCollection;
let records;
this.queued = false;
this.changeRecords = [];
this.oldCollection = null;
if (this.hasSubscribers()) {
if (oldCollection) {
// TODO (martingust) we might want to refactor this to a common, independent of collection type, way of getting the records
if (this.collection instanceof Map || this.collection instanceof Set) {
records = getChangeRecords(oldCollection);
} else {
//we might need to combine this with existing change records....
records = calcSplices(this.collection, 0, this.collection.length, oldCollection, 0, oldCollection.length);
}
} else {
if (this.collection instanceof Map || this.collection instanceof Set) {
records = changeRecords;
} else {
records = projectArraySplices(this.collection, changeRecords);
}
}
this.callSubscribers(records);
}
if (this.lengthObserver) {
this.lengthObserver.call(this.collection[this.lengthPropertyName]);
}
}
}
export class CollectionLengthObserver {
constructor(collection) {
this.collection = collection;
this.lengthPropertyName = collection instanceof Map || collection instanceof Set ? 'size' : 'length';
this.currentValue = collection[this.lengthPropertyName];
}
getValue() {
return this.collection[this.lengthPropertyName];
}
setValue(newValue) {
this.collection[this.lengthPropertyName] = newValue;
}
subscribe(context, callable) {
this.addSubscriber(context, callable);
}
unsubscribe(context, callable) {
this.removeSubscriber(context, callable);
}
call(newValue) {
let oldValue = this.currentValue;
this.callSubscribers(newValue, oldValue);
this.currentValue = newValue;
}
}