diff --git a/src/observable/index.js b/src/observable/index.js index 3a250fde..5444e27e 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -6,6 +6,7 @@ import BaseObservableMap from "./map/BaseObservableMap.js"; export { default as ObservableArray } from "./list/ObservableArray.js"; export { default as SortedArray } from "./list/SortedArray.js"; export { default as MappedList } from "./list/MappedList.js"; +export { default as ConcatList } from "./list/ConcatList.js"; export { default as ObservableMap } from "./map/ObservableMap.js"; // avoid circular dependency between these classes diff --git a/src/observable/list/BaseObservableList.js b/src/observable/list/BaseObservableList.js index 97e50b20..cdab32f3 100644 --- a/src/observable/list/BaseObservableList.js +++ b/src/observable/list/BaseObservableList.js @@ -3,26 +3,26 @@ import BaseObservableCollection from "../BaseObservableCollection.js"; export default class BaseObservableList extends BaseObservableCollection { emitReset() { for(let h of this._handlers) { - h.onReset(); + h.onReset(this); } } // we need batch events, mostly on index based collection though? // maybe we should get started without? emitAdd(index, value) { for(let h of this._handlers) { - h.onAdd(index, value); + h.onAdd(index, value, this); } } emitUpdate(index, value, params) { for(let h of this._handlers) { - h.onUpdate(index, value, params); + h.onUpdate(index, value, params, this); } } emitRemove(index, value) { for(let h of this._handlers) { - h.onRemove(index, value); + h.onRemove(index, value, this); } } @@ -30,7 +30,7 @@ export default class BaseObservableList extends BaseObservableCollection { // been removed from its fromIdx emitMove(fromIdx, toIdx, value) { for(let h of this._handlers) { - h.onMove(fromIdx, toIdx, value); + h.onMove(fromIdx, toIdx, value, this); } } diff --git a/src/observable/list/ConcatList.js b/src/observable/list/ConcatList.js new file mode 100644 index 00000000..49d30ce1 --- /dev/null +++ b/src/observable/list/ConcatList.js @@ -0,0 +1,130 @@ +import BaseObservableList from "./BaseObservableList.js"; + +export default class ConcatList extends BaseObservableList { + constructor(...sourceLists) { + super(); + this._sourceLists = sourceLists; + this._sourceUnsubscribes = null; + } + + _offsetForSource(sourceList) { + const listIdx = this._sourceLists.indexOf(sourceList); + let offset = 0; + for (let i = 0; i < listIdx; ++i) { + offset += this._sourceLists[i].length; + } + return offset; + } + + onSubscribeFirst() { + this._sourceUnsubscribes = []; + for (const sourceList of this._sourceLists) { + this._sourceUnsubscribes.push(sourceList.subscribe(this)); + } + } + + onUnsubscribeLast() { + for (const sourceUnsubscribe of this._sourceUnsubscribes) { + sourceUnsubscribe(); + } + } + + onReset() { + // TODO: not ideal if other source lists are large + // but working impl for now + // reset, and + this.emitReset(); + let idx = 0; + for(const item of this) { + this.emitAdd(idx, item); + idx += 1; + } + } + + onAdd(index, value, sourceList) { + this.emitAdd(this._offsetForSource(sourceList) + index, value); + } + + onUpdate(index, value, params, sourceList) { + this.emitAdd(this._offsetForSource(sourceList) + index, value, params); + } + + onRemove(index, value, sourceList) { + this.emitRemove(this._offsetForSource(sourceList) + index, value); + } + + onMove(fromIdx, toIdx, value, sourceList) { + const offset = this._offsetForSource(sourceList); + this.emitMove(offset + fromIdx, offset + toIdx, value); + } + + get length() { + let len = 0; + for (let i = 0; i < this._sourceLists.length; ++i) { + len += this._sourceLists[i].length; + } + return len; + } + + [Symbol.iterator]() { + let sourceListIdx = 0; + let it = this._sourceLists[0][Symbol.iterator](); + return { + next: () => { + let result = it.next(); + while (result.done) { + sourceListIdx += 1; + if (sourceListIdx >= this._sourceLists.length) { + return result; //done + } + it = this._sourceLists[sourceListIdx][Symbol.iterator](); + result = it.next(); + } + return result; + } + } + } +} + +import ObservableArray from "./ObservableArray.js"; +export async function tests() { + return { + test_length(assert) { + const all = new ConcatList( + new ObservableArray([1, 2, 3]), + new ObservableArray([11, 12, 13]) + ); + assert.equal(all.length, 6); + }, + test_iterator(assert) { + const all = new ConcatList( + new ObservableArray([1, 2, 3]), + new ObservableArray([11, 12, 13]) + ); + const it = all[Symbol.iterator](); + assert.equal(it.next().value, 1); + assert.equal(it.next().value, 2); + assert.equal(it.next().value, 3); + assert.equal(it.next().value, 11); + assert.equal(it.next().value, 12); + assert.equal(it.next().value, 13); + assert(it.next().done); + }, + test_add(assert) { + const list1 = new ObservableArray([1, 2, 3]); + const list2 = new ObservableArray([11, 12, 13]); + const all = new ConcatList(list1, list2); + let fired = false; + all.subscribe({ + onAdd(index, value) { + fired = true; + assert.equal(index, 4); + assert.equal(value, 11.5); + } + }); + list2.insert(1, 11.5); + assert(fired); + }, + + }; +}