UNPKG

@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
import {calcSplices, projectArraySplices} from './array-change-records'; import {getChangeRecords} from './map-change-records'; import {subscriberCollection} from './subscriber-collection'; @subscriberCollection() 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]); } } } @subscriberCollection() 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; } }