actually implemented filtered map

This commit is contained in:
Bruno Windels 2020-10-05 18:18:44 +02:00
parent 5ae1be9a9c
commit 943467cf67

View file

@ -17,56 +17,127 @@ limitations under the License.
import {BaseObservableMap} from "./BaseObservableMap.js"; import {BaseObservableMap} from "./BaseObservableMap.js";
export class FilteredMap extends BaseObservableMap { export class FilteredMap extends BaseObservableMap {
constructor(source, mapper, updater) { constructor(source, filter) {
super(); super();
this._source = source; this._source = source;
this._mapper = mapper; this._filter = filter;
this._updater = updater; /** @type {Map<string, bool>} */
this._mappedValues = new 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) { onAdd(key, value) {
const mappedValue = this._mapper(value); if (this._filter) {
this._mappedValues.set(key, mappedValue); const included = this._filter(value, key);
this.emitAdd(key, mappedValue); this._included.set(key, included);
if (!included) {
return;
}
}
this.emitAdd(key, value);
} }
onRemove(key, _value) { onRemove(key, value) {
const mappedValue = this._mappedValues.get(key); if (this._filter && !this._included.get(key)) {
if (this._mappedValues.delete(key)) { return;
this.emitRemove(key, mappedValue);
} }
this.emitRemove(key, value);
} }
onChange(key, value, params) { onChange(key, value, params) {
const mappedValue = this._mappedValues.get(key); if (this._filter) {
if (mappedValue !== undefined) { const wasIncluded = this._included.get(key);
const newParams = this._updater(value, params); const isIncluded = this._filter(value, key);
if (newParams !== undefined) { this._included.set(key, isIncluded);
this.emitChange(key, mappedValue, newParams);
} 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() { onSubscribeFirst() {
for (let [key, value] of this._source) { this.update();
const mappedValue = this._mapper(value);
this._mappedValues.set(key, mappedValue);
}
super.onSubscribeFirst(); super.onSubscribeFirst();
} }
onUnsubscribeLast() { onUnsubscribeLast() {
super.onUnsubscribeLast(); super.onUnsubscribeLast();
this._mappedValues.clear(); this._included = null;
} }
onReset() { onReset() {
this._mappedValues.clear(); this.update();
this.emitReset(); this.emitReset();
} }
[Symbol.iterator]() { [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 => {
},
} }
} }