diff --git a/src/observable/map/FilteredMap.js b/src/observable/map/FilteredMap.js index 59d8efa1..20e5c568 100644 --- a/src/observable/map/FilteredMap.js +++ b/src/observable/map/FilteredMap.js @@ -17,56 +17,127 @@ limitations under the License. import {BaseObservableMap} from "./BaseObservableMap.js"; export class FilteredMap extends BaseObservableMap { - constructor(source, mapper, updater) { + constructor(source, filter) { super(); this._source = source; - this._mapper = mapper; - this._updater = updater; - this._mappedValues = new Map(); + this._filter = filter; + /** @type {Map} */ + this._included = null; + } + + setFilter(filter) { + this._filter = filter; + this.update(); + } + + update() { + // TODO: need to check if we have a subscriber already? If not, we really should not iterate the source? + if (this._filter) { + this._included = this._included || new Map(); + for (const [key, value] of this._source) { + this._included.set(key, this._filter(value, key)); + } + } else { + this._included = null; + } } onAdd(key, value) { - const mappedValue = this._mapper(value); - this._mappedValues.set(key, mappedValue); - this.emitAdd(key, mappedValue); + if (this._filter) { + const included = this._filter(value, key); + this._included.set(key, included); + if (!included) { + return; + } + } + this.emitAdd(key, value); } - onRemove(key, _value) { - const mappedValue = this._mappedValues.get(key); - if (this._mappedValues.delete(key)) { - this.emitRemove(key, mappedValue); + onRemove(key, value) { + if (this._filter && !this._included.get(key)) { + return; } + this.emitRemove(key, value); } onChange(key, value, params) { - const mappedValue = this._mappedValues.get(key); - if (mappedValue !== undefined) { - const newParams = this._updater(value, params); - if (newParams !== undefined) { - this.emitChange(key, mappedValue, newParams); - } + if (this._filter) { + const wasIncluded = this._included.get(key); + const isIncluded = this._filter(value, key); + this._included.set(key, isIncluded); + + if (wasIncluded && !isIncluded) { + this.emitRemove(key, value); + } else if (!wasIncluded && isIncluded) { + this.emitAdd(key, value); + } else if (!wasIncluded && !isIncluded) { + return; + } // fall through to emitChange } + this.emitChange(key, value, params); } onSubscribeFirst() { - for (let [key, value] of this._source) { - const mappedValue = this._mapper(value); - this._mappedValues.set(key, mappedValue); - } + this.update(); super.onSubscribeFirst(); } onUnsubscribeLast() { super.onUnsubscribeLast(); - this._mappedValues.clear(); + this._included = null; } onReset() { - this._mappedValues.clear(); + this.update(); this.emitReset(); } [Symbol.iterator]() { - return this._mappedValues.entries()[Symbol.iterator]; + return new FilterIterator(this._source, this._included); + } +} + +class FilterIterator { + constructor(map, _included) { + this._included = _included; + this._sourceIterator = map.entries(); + } + + next() { + // eslint-disable-next-line no-constant-condition + while (true) { + const sourceResult = this._sourceIterator.next(); + if (sourceResult.done) { + return sourceResult; + } + const key = sourceResult.value[1]; + if (this._included.get(key)) { + return sourceResult; + } + } + } +} + +import {ObservableMap} from "./ObservableMap.js"; +export function tests() { + return { + "filter preloaded list": assert => { + const source = new ObservableMap(); + source.add("one", 1); + source.add("two", 2); + source.add("three", 3); + const odds = Array.from(new FilteredMap(source, x => x % 2 !== 0)); + assert.equal(odds.length, 2); + + }, + "filter added values": assert => { + + }, + "filter removed values": assert => { + + }, + "filter changed values": assert => { + + }, } }