keeweb/app/scripts/framework/collection.js

252 lines
6.5 KiB
JavaScript
Raw Normal View History

2019-09-17 19:01:24 +02:00
import EventEmitter from 'events';
const SymbolEvents = Symbol('events');
2019-09-19 17:42:53 +02:00
const SymbolArray = Symbol('array');
2019-09-17 19:01:24 +02:00
2019-09-19 17:42:53 +02:00
function emitSet(target, value, prevValue) {
2019-09-17 19:01:24 +02:00
const emitter = target[SymbolEvents];
if (!emitter.paused) {
const updates = { added: [], removed: [] };
if (prevValue) {
2019-09-19 17:42:53 +02:00
emitter.emit('remove', prevValue, target);
2019-09-17 19:01:24 +02:00
updates.removed.push(prevValue);
}
if (value) {
2019-09-19 17:42:53 +02:00
emitter.emit('add', value, target);
2019-09-17 19:01:24 +02:00
updates.added.push(value);
}
2019-10-27 10:35:36 +01:00
emitter.emit('change', updates, target);
2019-09-17 19:01:24 +02:00
}
}
2019-09-19 17:42:53 +02:00
function emitRemoved(target, removed) {
2019-09-17 19:01:24 +02:00
const emitter = target[SymbolEvents];
if (!emitter.paused) {
for (const item of removed) {
2019-09-19 17:42:53 +02:00
emitter.emit('remove', item, target);
2019-09-17 19:01:24 +02:00
}
2019-10-27 10:35:36 +01:00
emitter.emit('change', { added: [], removed }, target);
2019-09-17 19:01:24 +02:00
}
}
2019-09-19 17:42:53 +02:00
function checkType(target, value) {
const modelClass = target.constructor.model;
if (!modelClass) {
throw new Error(`Model type not defined for ${target.constructor.name}`);
}
if (!(value instanceof modelClass)) {
const valueType = value && value.constructor ? value.constructor.name : typeof value;
throw new Error(`Attempt to write ${valueType} into ${target.constructor.name}`);
}
}
const ProxyDef = {
set(target, property, value) {
2019-09-17 19:01:24 +02:00
const numProp = parseInt(property);
if (isNaN(numProp)) {
2019-09-18 23:37:57 +02:00
target[property] = value;
return true;
2019-09-17 19:01:24 +02:00
}
2019-09-19 17:42:53 +02:00
checkType(target, value);
const array = target[SymbolArray];
const prevValue = array[property];
2019-09-17 19:01:24 +02:00
if (prevValue !== value) {
2019-09-19 17:42:53 +02:00
array[property] = value;
emitSet(target, value, prevValue);
2019-09-17 19:01:24 +02:00
}
return true;
2019-09-19 17:42:53 +02:00
},
get(target, property) {
if (typeof property !== 'string') {
return target[property];
}
const numProp = parseInt(property);
if (isNaN(numProp)) {
return target[property];
}
return target[SymbolArray][property];
2019-09-17 19:01:24 +02:00
}
};
2019-09-19 17:42:53 +02:00
class Collection {
2019-09-18 23:37:57 +02:00
constructor(items) {
2019-09-17 19:01:24 +02:00
const emitter = new EventEmitter();
emitter.setMaxListeners(100);
const properties = {
2019-09-19 17:42:53 +02:00
[SymbolEvents]: { value: emitter },
[SymbolArray]: { value: [] }
2019-09-17 19:01:24 +02:00
};
Object.defineProperties(this, properties);
2019-09-18 23:37:57 +02:00
if (items) {
2019-09-19 17:42:53 +02:00
this.push(...items);
2019-09-18 23:37:57 +02:00
}
2019-09-19 17:42:53 +02:00
return new Proxy(this, ProxyDef);
}
get length() {
return this[SymbolArray].length;
}
set length(value) {
const array = this[SymbolArray];
let removed;
if (value < array.length) {
removed = array.slice(value);
}
array.length = value;
if (removed) {
emitRemoved(this, removed);
}
2019-09-17 19:01:24 +02:00
}
push(...items) {
if (items.length) {
2019-09-19 17:42:53 +02:00
for (const item of items) {
checkType(this, item);
}
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = true;
2019-09-19 17:42:53 +02:00
this[SymbolArray].push(...items);
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = false;
for (const item of items) {
this[SymbolEvents].emit('add', item, this);
}
this[SymbolEvents].emit('change', { added: items, removed: [] }, this);
}
}
pop() {
this[SymbolEvents].paused = true;
2019-09-19 17:42:53 +02:00
const item = this[SymbolArray].pop();
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = false;
if (item) {
this[SymbolEvents].emit('remove', item, this);
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
}
2019-10-27 10:35:36 +01:00
return item;
2019-09-17 19:01:24 +02:00
}
shift() {
this[SymbolEvents].paused = true;
2019-09-19 17:42:53 +02:00
const item = this[SymbolArray].shift();
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = false;
if (item) {
this[SymbolEvents].emit('remove', item, this);
this[SymbolEvents].emit('change', { added: [], removed: [item] }, this);
}
2019-10-27 10:35:36 +01:00
return item;
2019-09-17 19:01:24 +02:00
}
unshift(...items) {
if (items.length) {
2019-09-19 17:42:53 +02:00
for (const item of items) {
checkType(this, item);
}
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = true;
2019-09-19 17:42:53 +02:00
this[SymbolArray].unshift(...items);
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = false;
for (const item of items) {
this[SymbolEvents].emit('add', item, this);
}
this[SymbolEvents].emit('change', { added: items, removed: [] }, this);
}
}
splice(start, deleteCount, ...items) {
2019-09-19 17:42:53 +02:00
for (const item of items) {
checkType(this, item);
}
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = true;
2019-09-19 17:42:53 +02:00
const removed = this[SymbolArray].splice(start, deleteCount, ...items);
2019-09-17 19:01:24 +02:00
this[SymbolEvents].paused = false;
for (const item of removed) {
this[SymbolEvents].emit('remove', item, this);
}
for (const item of items) {
this[SymbolEvents].emit('add', item, this);
}
if (removed.length || items.length) {
this[SymbolEvents].emit('change', { added: items, removed }, this);
}
}
on(eventName, listener) {
this[SymbolEvents].on(eventName, listener);
}
once(eventName, listener) {
this[SymbolEvents].once(eventName, listener);
}
off(eventName, listener) {
this[SymbolEvents].off(eventName, listener);
}
2019-09-17 21:39:06 +02:00
get(id) {
2020-06-01 16:53:51 +02:00
return this.find((model) => model.id === id);
2019-09-17 21:39:06 +02:00
}
remove(idOrModel) {
for (let i = 0; i < this.length; i++) {
while (i < this.length && (this[i].id === idOrModel || this[i] === idOrModel)) {
this.splice(i, 1);
}
}
}
2019-09-19 17:42:53 +02:00
2019-09-20 22:26:32 +02:00
sort() {
2019-10-27 10:35:36 +01:00
return this[SymbolArray].sort(this.comparator);
2019-09-20 22:26:32 +02:00
}
2019-09-19 17:42:53 +02:00
fill() {
throw new Error('Not implemented');
}
copyWithin() {
throw new Error('Not implemented');
}
2019-09-19 22:07:14 +02:00
toJSON() {
return this[SymbolArray].concat();
}
2019-09-19 17:42:53 +02:00
}
const ProxiedArrayMethods = [
Symbol.iterator,
'concat',
'entries',
'every',
'filter',
'find',
'findIndex',
'flat',
'flatMap',
'forEach',
'includes',
'indexOf',
'join',
'keys',
'lastIndexOf',
'map',
'reduce',
'reduceRight',
'reverse',
'slice',
'some',
'values'
];
for (const method of ProxiedArrayMethods) {
Object.defineProperty(Collection.prototype, method, {
value: function proxyMethod(...args) {
return this[SymbolArray][method](...args);
}
});
2019-09-17 19:01:24 +02:00
}
export { Collection };